// Ignore error returns

 // Free our event buffer

 ExFreePool(pEvent);

}

The code loops until all the events in EventList have been removed and sent to the DebugPrint driver. It removes the first entry from the doubly-linked list using ExInterlockedRemoveHeadList, passing pointers to the list head and the guarding spin lock. The return value is NULL if there is nothing left in the list.

ExInterlockedRemoveHeadList returns a pointer to the ListEntry field in the DEBUGPRINT_ EVENT structure. What is really needed is not this, but a pointer to the DEBUGPRINT_EVENT structure itself. For this particular structure, a simple cast would suffice. However, there is a way to deal correctly with the general case in which the LIST_ENTRY variable is not at the beginning of the structure. The system header files provide the appropriate macro, CONTAINING_RECORD. Pass the LIST_ENTRY pointer, the data type of your structure and the name of its LIST_ENTRY field. The returned value is the pointer to the DEBUGPRINT_EVENT structure.

Having got the correct event pointer in pEvent, the system thread extracts the length of the event data and writes the event data itself to the DebugPrint driver using ZwWriteFile. Finally, it frees the memory that was allocated for the DEBUGPRINT_EVENT structure, before checking to see if any more events are available.

The ClearEvents routine is called to clear any remaining events when the system thread finishes or DebugPrintClose is called. ClearEvents removes any events using ExInterlockedRemoveHeadList and frees the event memory.

Singly-Linked Lists

There are kernel functions for singly-linked lists, which are really stacks. Declare a list head as a variable of type SINGLE_LIST_ENTRY. Initialize it by setting its Next field to NULL. PushEntryList puts an entry onto the front of the list, while PopEntryList removes an entry from the front of the list. You must use the same technique as before to get the correct pointer from a popped entry: include a SINGLE_LIST_ENTRY field in the structure you put on the list and use CONTAINING_RECORD.

There are also interlocked versions of these routines, using a spin lock as before to ensure accesses are carried out safely.

Device Queues

A device queue is an enhanced form of a doubly-linked list, usually used for storing IRPs. These are covered in glorious detail in Chapter 16.

Final Pieces

Let's draw together the final pieces of the DebugPrint code for test drivers.

DebugPrintInit makes a copy of the driver name passed to it. Why does it bother to do this? Well, initially it did not. However, the call to DebugPrintInit usually comes from a DriverEntry routine. DriverEntry code is often marked as being discardable once the driver has been initialized. This meant that the code with the driver name string might be discarded. I found that this did indeed happen and so the original pointer referred to invalid memory. Watch out for this problem in your drivers.

Listing 14.4 shows how the system thread opens a connection to the DebugPrint driver using ZwCreateFile. It opens a connection to a DebugPrint driver device called DevicePHDDebugPrint. The DebugPrint driver also uses a driver interface so that user mode programs, such as DebugPrint Monitor, can find this device.

While drivers can use Plug and Play Notification to find other drivers that support a device interface, this is a complicated approach. Instead, the one and only DebugPrint device is given a kernel name of PHDDebugPrint. Calls to ZwCreateFile do not use Win32 symbolic link names for devices. Instead, as in this example, they must use the full kernel device name.

The filename must be specified as a UNICODE_STRING. RtlInitUnicodeString is used to initialize the DebugPrintName variable. The filename is set into an OBJECT_ATTRIBUTES structure using InitializeObjectAttributes. A pointer to this structure is finally passed to ZwCreateFile. Like its Win32 equivalent, you must specify access and share parameters to ZwCreateFile. There are a host of other options, so consult the documentation for full details. Finally, you get a file HANDLE to use in further I/O requests.

As mentioned earlier, subsequent calls to ZwReadFile, ZwWriteFile, ZwQueryInformationFile, and ZwClose must take place in the same thread context as the call to ZwCreateFile. All these file I/O routines must be called at PASSIVE_LEVEL.

A typical call to ZwWriteFile is illustrated in Listing 14.3. As well as the file handle, simply specify the data pointer and a transfer count. The completion details can be found in an IO_STATUS_BLOCK structure. Specify a file pointer byte offset.

The DebugPrint test driver code eventually closes the file handle using ZwClose just before it terminates.

The system thread code tried to open its connection to the DebugPrint driver for 5 minutes. This ensures that the DebugPrint driver has started during system startup.

Listing 14.4 DebugPrint test driver system thread file opening

// Make appropriate ObjectAttributes for ZwCreateFile

UNICODE_STRING DebugPrintName;

RtlInitUnicodeString(&DebugPrintName, _'\Device\PHDDebugPrint');

OBJECT_ATTRIBUTES ObjectAttributes;

InitializeObjectAttributes(&ObjectAttributes, &DebugPrintName, OBJ_CASE_INSENSITIVE, NULL, NULL);

// Open handle to DebugPrint device

IO_STATUS_BLOCK IoStatus;

HANDLE DebugPrintDeviceHandle = NULL;

NTSTATUS status = ZwCreateFile(&DebugPrintDeviceHandle,

 GENERIC_READ | GENERIC_WRITE,

 &ObjectAttributes, &IoStatus,

 0, // alloc size = none

 FILE_ATTRIBUTE_NORMAL,

 FILE_SHARE_READ|FILE_SHARE_WRITE,

 FILE_OPEN,

 0,

 NULL, // eabuffer

 0); // ealength

 if (!NT_SUCCESS(status) || DebugPrintDeviceHandle==NULL) goto exit1;

DebugPrint Driver

Referring to Figure 14.1, you can see that the job of the DebugPrint driver is to store trace events from all the drivers under test and make them available to the DebugPrint Monitor user mode application. As the figure illustrates, the test drivers only write to the DebugPrint driver, while the Monitor only reads.

This section will not look at all the DebugPrint driver code. The Plug and Play and Initialization code is largely the same as earlier WDM drivers. You should install just one DebugPrint device in the Other Devices category.

The interesting code is in the dispatch routines in Dispatch.cpp, along with a main header file DebugPrint.h. These files and the other source files can be found on the book's software disk.

Design

The DebugPrint driver uses a similar technique of storing all written trace events in a doubly-linked list called EventList. When the DebugPrint Monitor program starts, it reads all the available events. It then leaves one read request outstanding. When a new trace event is written by a test driver, the Monitor read request is satisfied straightaway. This design ensures that trace events get to the Monitor application as soon as possible.

This means that the DebugPrint driver has to be able to queue up incoming read requests. In fact, it only allows one read request to be queued. Any further read requests are rejected. As DebugPrint has an IRP queue, it must be prepared to cancel IRPs. The DbpCancelIrp routine described later does this job.

The DbpCreate and DbpClose routines simply complete their IRPs successfully. Note that these IRPs are issued from both test drivers and the Monitor application.

The DebugPrint driver uses Buffered I/O.

DebugPrint Devices

As mentioned previously, the DebugPrint driver uses both a device interface and a Windows 2000 device name. The DebugPrint Monitor application identifies a DebugPrint device using the device interface. The test drivers identify the DebugPrint device using the kernel name.

Listing 14.5 shows how a named Functional Device Object is created in DbpAddDevice in Pnp.cpp. The DebugPrintName variable is a UNICODE_STRING that is initialized with the desired kernel device name, DevicePHDDebugPrint. This is passed to the IoCreateDevice call.

Later in DbpAddDevice, the DebugPrint device interface is registered in the same way as before. The device interface GUID is defined in DbgPrtGUID.h as {ED6026A2- 6813-11d2-AE43-00C0DFE4C1F3}. This header file is also included by the DebugPrint Monitor project.

Listing 14.5 DebugPrint driver device creation

UNICODE_STRING DebugPrintName;

RtlInitUnicodeString(&DebugPrintName, L'\Device\PHDDebugPrint');

// Create our Functional Device Object in fdo

status = IoCreateDevice(DriverObject,

 sizeof(DEBUGPRINT_DEVICE_EXTENSION),

 &DebugPrintName,

 FILE_DEVICE_UNKNOWN,

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

0

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

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