Figure 14.1 shows the design used in the DebugPrint software. The figure shows that more than one test driver can run at the same time. In each test driver, the trace output is first stored internally in an EventList doubly-linked list. A DebugPrint system thread in the driver code reads the EventList and writes the events to the DebugPrint driver.

The DebugPrint driver write routine stores the trace events in its own EventList. The DebugPrint Monitor application issues read requests to the DebugPrint driver to read the trace events. These are then displayed to the user in the DebugPrint Monitor window.

An alternative design might have removed the DebugPrint driver. The test driver DebugPrint system thread could then have written the trace events directly to a disk file. However, it is unclear whether it is possible to lock such a file so that 'simultaneous' accesses by the test drivers and DebugPrint Monitor would be handled properly. Writing to a disk file would also be slower.

Figure 14.1 DebugPrint design

The DebugPrint software, therefore, consists of the following three different pieces of code.

• Code added to the test driver to produce events

• The DebugPrint driver

• The DebugPrint Monitor application

Test Driver Code

As explained in Chapter 6, a driver writer has to add DebugPrint.c[31] and DebugPrint.h source files to their driver project to support DebugPrint calls. These routines ensure that the trace statement output is sent to the DebugPrint driver. It should eventually be possible to put all the code in DebugPrint.c into a static library or DLL that is linked with test drivers.

The main job performed by the DebugPrint code in each test driver is to write events to the DebugPrint driver. The kernel provides several routines to call other drivers, including ZwCreateFile, ZwWriteFile, and ZwClose. The documentation for these functions says that these functions can only be called at PASSIVE_LEVEL IRQL. This means that they cannot be called directly from some sorts of driver code.

In my initial design, I ignored this issue. However, I soon ran into another problem that was more difficult to track down. Eventually, I worked out that calls to ZwWriteFile have to be made in the same process context as ZwCreateFile. The DDK documentation did not make this point prominently. In my initial design, I used ZwCreateFile in the test driver DriverEntry routine. However, the print routines using ZwWriteFile could naturally be called in dispatch routines, which are usually not running in the same process context.

Thread and process contexts are not normally a problem for most device drivers. When you process a normal user request, the IRP has all the information you need. The fact that you may be running in an arbitrary thread context does not effect the job you have to do. The Zw… file access functions are one case when the process context is significant. If you use 'neither' Buffered I/O nor Direct I/O or use METHOD_NEITHER IOCTLs, the thread context is also important.

System Threads

The solution to both these problems is to use a system thread. A driver can create a system thread (or threads) that runs in kernel mode. This thread runs at PASSIVE_LEVEL IRQL. If the DebugPrint system thread makes all the Zw… calls, the 'same process' problem is fixed. A doubly-linked list is used to store the events for processing by the system thread. Inserting events into this list can be done safely at IRQL levels up to and including DISPATCH_LEVEL This means that most types of driver code can generate DebugPrint trace output.

The DebugPrintInit routine calls PsCreateSystemThread (at PASSIVE_LEVEL IRQL) to create its system thread as shown in the partial code in Listing 14.1. PsCreateSystemThread is passed the name of the function to run and a context to pass to it. The DebugPrint system thread function is defined as follows.

void DebugPrintSystemThread(IN PVOID Context)

DebugPrintInit passes just a NULL context to the thread function. Some drivers may wish to create one thread per device, and so will usually pass a pointer to the device extension as the context. A DebugPrint test driver has just one system thread for all its devices.

The DebugPrint system thread does not need a high priority. Therefore, DebugPrintSystemThread calls KeSetPriorityThread straightaway to set its priority to the lowest real-time priority. It uses KeGetCurrentThread to get a pointer to its own thread object.

If PsCreateSystemThread succeeds, it returns a handle to the thread. Later, I shall show that a driver can wait for certain objects to be set. It can wait for the completion of a system thread if it has a pointer to the system thread object, not a handle. Use ObReferenceObjectByHandle to retrieve the thread object pointer. If this succeeds, call ZwClose to close the thread handle. Technically speaking, the call to ZwClose reduces the reference count to the thread handle. When the thread completes, its handle reference count will be decremented to zero.

A system thread must terminate itself using PsTerminateSystemThread. I am not sure what happens if a system thread function simply returns without calling PsTerminateSystemThread. The main driver code cannot force a system thread to terminate. For this reason, a global Boolean variable called ExitNow is used. This is set true when the driver wants the system thread to exit.

DebugPrintClose waits for the ThreadExiting event to be set into the signalled state by the thread as it exits. This ensures that the thread has completed by the time DebugPrintClose exits. DebugPrintClose is commonly called from a driver unload routine. All driver code must have stopped running when the unload routine exits, as the driver address space may soon be deleted. Make sure that any other call-backs are disabled, e.g., interrupt handlers, Deferred Procedure Calls, and timers.

Listing 14.1 DebugPrint test driver thread and event handling

KEVENT ThreadEvent;

KEVENT ThreadExiting;

PVOID ThreadObjectPointer=NULL;

void DebugPrintInit(char* _DriverName) {

 // …

 ExitNow = false;

 KeInitializeEvent(&ThreadEvent, SynchronizationEvent, FALSE);

 KeInitia1izeEvent(&ThreadExiting, SynchronizationEvent, FALSE);

 HANDLE threadHandle;

 status = PsCreateSystemThread(&threadHandle, THREAD_ALL_ACCESS, NULL, NULL,  NULL, DebugPrintSystemThread, NULL);

 if (!NT_SUCCESS(status)) return;

 status = ObReferenceObjectByHandle(threadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, &ThreadObjectPointer, NULL);

 if (NT_SUCCESS(status)) ZwClose(threadHandle);

 // …

}

void DebugPrintClose() {

 // …

 ExitNow = true;

 KeSetEvent(&ThreadEvent, 0, FALSE);

 KeWaitForSingleObject(&ThreadExiting, Executive, KernelMode, FALSE, NULL);

 // …

}

void DebugPrintSystemThread(IN PVOID Context) {

 // Lower thread priority

 KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);

 // Make One second relative timeout

 LARGE_INTEGER OneSecondTimeout;

 OneSecondTimeout.QuadPart = –1i64 * 1000000i64 * 10i64;

 // Loop waiting for events or ExitNow

 while (true) {

  KeWaitForSingleObject(&ThreadEvent, Executive, KernelMode, FALSE, &0neSecondTimeout);

  // Process events

  // …

  if (ExitNow) break;

 }

 // Tidy up

 if (ThreadObjectPointer!=NULL) {

  ObDereferenceObject(&ThreadObjectPointer);

  ThreadObjectPointer = NULL;

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×