Figure 14.1 shows the design used in the
The DebugPrint driver write routine stores the trace events in its own EventList. The
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
Figure 14.1
The
• Code added to the test driver to produce events
• The DebugPrint driver
• The
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
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
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.
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
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,
If
A system thread must terminate itself using
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;