52964.fb2
This chapter looks at the second method of reporting information to system administrators, NT events. The last chapter looked at the first method, Windows Management Instrumentation (WMI), which ought to work in Windows 98, as well as the NT and Windows 2000 platforms.
Drivers can generate NT events in NT 3.51, NT 4, and Windows 2000. Events are stored in a system event log that can be viewed by a user mode Event Viewer.
The Wdm3 driver generates NT events in a few places. Although the Wdm3 example is a WDM device driver, NT events can and should be generated by NT style NT 3.51 and NT 4 drivers.
In NT 3.51, NT 4, and Windows 2000, drivers should report any problems to the system event log. Windows 98 WDM device drivers can make the relevant kernel calls, but they do nothing.
Once events are firmly in the event log, they are preserved even if a system crashes. Events can, therefore, be useful in some debugging circumstances (e.g., where DebugPrint information is lost as a driver crashes).
In NT 3.51 and NT 4, use the EventVwr tool to view events. In Windows 2000 use the Event Viewer System Log portion of the Computer Management Console. In both cases, you must double-click on a record to bring up the full details of the event, as shown in Figure 13.1.
The Event Detail tab shows most of the event information. Events are categorized as either Informational, Warning, or Error. The message text is taken from a resource in the driver's executable. A driver can specify some small extra strings that are inserted into the message text.
The Record Data tab shows (in hex) any additional data bytes that were passed by the driver. In Windows 2000, most drivers always seem to show at least 0x28 bytes of record data. Any data that your driver provides starts at offset 0x28.
Do not swamp the event log with superfluous information. Obviously, try to report errors in a meaningful way. Remember that the event log will only be useful when a problem arises. Some informational messages may be useful for displaying status information, such as network addresses.
If you are being clever, you could dynamically adjust the amount of information that you produce. You might start off by reporting transactions that need to be retried as warning messages. If these keep occurring, you could stop reporting these retry messages.
Other drivers inspect a registry value when they start up to determine the level of reporting. During debugging or diagnostic testing, the registry value could be set in such as way as to generate lots of useful reports. This may be the only way to obtain debugging information in the field.
Figure 13.1 Event Viewer in action
When you log an event, you pass an Event Id, a number specifying the event that you are reporting. You must include a message resource in your driver if you want the event viewer to display the appropriate description.
The Wdm3Msg.mc message file for the Wdm3 driver is shown in Listing 13.1. The MessageIdTypedef, SeverityNames, and FacilityNames sections are fairly standard. A facility identifies the type of driver. Most driver writers use the spare facility number of 0x7 for the Wdm3 facility. Microsoft defined facility numbers are defined in NTSTATUS.H.
The following blocks of lines define one message at a time. The contents of each line are self-explanatory. The actual event message is on one or more lines, ending with a line that contains just a period. The following escape codes have special meaning in the message text: %b is a space, %t is a tab, %v is a carriage return, and %n is a linefeed. In addition, %1 to %99 are where driver-supplied strings are inserted. Actually, %1 is always the driver name, so the driver strings start with 11.
Listing 13.1 Wdm3Msg.mc message file
MessageIdTypedef = NTSTATUS
SeverityNames = (
Success = 0x0:STATUS_SEVERITY_SUCCESS
Informational = 0x1:STATUS_SEVERITY_INFORMATIONAL
Warning = 0x2:STATUS_SEVERITY_WARNING
Error = 0x3:STATUS_SEVERITY_ERROR
)
FacilityNames = (
System = 0x0
Wdm3 = 0x7:FACILITY_WDM3_ERROR_CODE
)
MessageId=0x0001
Facility=Wdm3
Severity=Informational
SymbolicName=WDM3_MSG_LOGGING_STARTED
Language=English
Event logging enabled for Wdm3 Driver.
.
MessageId=+1
Facility=Wdm3
Severity=Informational
SymbolicName=WDM3_MESSAGE
Language=English
Message: %2.
.
The mc command is used to compile the message definition file. It produces three or more output files. In this case, these are the Wdm3Msg.rc resource script, the Wdm3Msg.h header file, and the MSG00001.BIN message data file. Further message files are produced if you support more than one language. The Wdm3Msg.rc resource script contains just a reference to the MSG00001.BIN message data file (or files), as follows.
LANGUAGE 0x9,0x1
1 11 MSG00001.bin
The Wdm3Msg.h header file contains the message symbolic names defined in a form that can be used by the driver code, as shown in Listing 13.2. The message ID, severity, and facility code have been combined, with the "customer" bit set to make a suitable NTSTATUS value. The main Wdm3 header, Wdm3.h, now also includes Wdm3Msg.h.
Listing 13.2 Wdm3Msg.h file
// MessageId: WDM3_MSG_LOGGING_STARTED
//
// MessageText:
//
// Event logging enabled for Wdm3 Driver.
//
#define WDM3_MSG_LOGGING_STARTED ((NTSTATUS)0x60070001L)
//
// MessageId: WDM3_MESSAGE
//
// MessageText:
//
// Message: %2.
//
#define WDM3_MESSAGE ((NTSTATUS)0x60070002L)
The message file must be compiled before the main driver code is built. The NTTARGETFILE0 macro in the SOURCES file is used to specify any prebuild steps.
NTTARGETFILE0=prebuiId
As described in Chapter 4, this invokes nmake on the makefile.inc makefile before the main compile. The prebuild step compiles the WMI MOF file and the event message definition file, as shown in Listing 13.3. The mc command is run, if necessary, using the –c option to set the "customer" bit and the –v option for verbose output.
Listing 13.3 New makefile.inc
prebuild: Wdm3Msg.h Wdm3.bmf
Wdm3.bmf: Wdm3.mof
mofcomp –B:Wdm3.bmf –WMI Wdm3.mof
Wdm3Msg.rc Wdm3Msg.h: Wdm3Msg.mc
mc –v –c Wdm3Msg.mc
PostBuildSteps: $(TARGET)
!if "$(DDKBUILDENV)"=="free"
rebase –B 0x10000 –X . $(TARGET)
!endif
copy $(TARGET) $(WINDIR)\system32\drivers
The final change to the build process is to make the main resource file, Wdm3.rc, include the message resource script, Wdm3Msg.rc. In Visual C++, select the View+Resource Includes… menu and add the following line to the "Read-only symbol directives" box.
#include "Wdm3Msg.rc"
In Windows 2000, I found that building the driver from a changed message definition file initially reported an error but then went on to compile successfully.
The final hurdle to overcome is registering your driver as an event source so that the event viewer knows where to find your message text resource. Two registry changes must be made.
First, the HKLM\System\CurrentControlSet\Services\EventLog\System key has an existing REG_MULTI_SZ value called Sources. Add the name of your driver's executable (without the extension) as a line in Sources.
In this same registry key, make a new subkey with this same driver name. In this subkey, add a REG_EXPAND_SZ value called EventMessageFile and set a REG_DWORD called TypesSupported with a value of 0x7. For Wdm3, set EventMessageFile to the following value.
%SystemRoot%\System32\IoLogMsg.dll;%SystemRoot%\System32\Drivers\Wdm3.sys
The Wdm3 installation INF file supposedly has the correct information to make these registry changes when a Wdm3 device is installed. Listing 13.4 shows the amendments made to the standard installation file. The AddService directive's last field specifies the name of the section containing the error logging registry values. There are optional fields to specify the log type (System, Security, or Application) and a log name.
The Wdm3.Service.EventLog section specifies the values for the EventMessageFile and TypesSupported values. The EventMessageFile entry is on one long line.
However, I found that this did not work completely in Windows 2000 Beta 3. "Wdm3" was correctly added to the Sources value and the HKLM\System\CurrentControlSet\Services\EventLog\System\Wdm3 key was correctly made, but no values were placed in the key.
It is simplest just to add these registry entries by hand. You will have to use RegEdt32 to use the required registry types.
Note: A revised version of installation file Wdm3\sys\Wdm3free.inf is available on the book's web site, www.phdcc.com/wdmbook. This updated version fixes the EventLog section problem.
For NT 3.51 and NT 4 drivers, you cannot use an INF installation file. Instead, you will have to amend your installation program to set up the registry entries. The example installation code, install.cpp (on the book's CD-ROM) shows how to do this job.
Listing 13.4 Wdm3free.inf installation file event logging sections
[Wdm3.Install.NT.Services]
AddService =
Wdm3, %SPSVCINST_ASSOCSERVICE%, Wdm3.Service, Wdm3.Service.EventLog
; …
[Wdm3.Service.EventLog]
HKR,,EventMessageFile,%FLG_ADDREG_TYPE_EXPAND_SZ%,
"%%SystemRoot%%\System32\IoLogMsg.dll;
%%SystemRoot%%\System32\drivers\Wdm3.sys"
HKR,,TypesSupported,%FLG_ADDREG_TYPE_DW0RD%,7
Listing 13.5 shows the routines that provide the event logging, InitializeEventLog, and LogEvent. Later, I shall describe the Wdm3EventMessage function that provides a simpler interface.
InitializeEventLog is simply used to store a pointer to the main DriverObject. It then calls LogEvent to send a WDM3_MSG_LOGGING_STARTED event. If all's well, this should be displayed in the Event Viewer with the corresponding description, "Event logging enabled for Wdm3 Driver".
LogEvent does the bulk of the work in logging an NT event. It may be called at DISPATCH_ LEVEL or lower. Its first task is to decide the size of the error log packet. It then calls IoAllocateErrorLogEntry to obtain a suitably sized packet. It then fills the packet and sends it off using IoWriteErrorLogEntry.
LogEvent has parameters for the message ID (from the list in Wdm3Msg.h) and optionally an IRP pointer. If the IRP pointer is given, various fields in the packet are filled in with details of the IRP. LogEvent can also accept DumpData and Strings as parameters, for insertion into the event packet.
The basic IO_ERR0R_L0G_PACKET structure contains one dump data ULONG, but no insertion strings. It is an extendible structure. Zero or more dump data ULONGs can be provided, followed immediately by any NULL terminated wide strings. This makes calculating and filling the packet size slightly involved. LogEvent saves each string length in a temporary array of integers called StringSizes. Note that the maximum packet size, ERROR_LOG_MAXIMUM_SIZE, is only 0x98 bytes, so do not try to pass large insertion strings.
Listing 13.5 InitializeEventLog and LogEvent routines
void InitializeEventLog(IN PDRIVER_OBJECT DriverObject) {
SavedDriverObject = DriverObject;
// Log a message saying that logging is started.
LogEvent(WDM3_MSG_LOGGING_STARTED, NULL, // IRP
NULL, 0, // dump data
NULL, 0); // strings
}
bool LogEvent(IN NTSTATUS ErrorCode, IN PIRP Irp, IN ULONG DumpData[], IN int DumpDataCount, IN PWSTR Strings[], IN int StringCount) {
if (SavedDriverObject==NULL) return false;
// Start working out size of complete event packet
int size = sizeof(IO_ERROR_LOG_PACKET);
// Add in dump data size.
// Less one as DumpData already has 1 ULONG in IO_ERROR_LOG_PACKET
if (DumpDataCount>0) size += sizeof(ULONG) * (DumpDataCount-1);
// Add in space needed for insertion strings (inc terminating NULLs)
int* StringSizes = NULL;
if (StringCount>0) {
StringSizes = (int*)ExAllocatePool(NonPagedPool, StringCount*sizeof(int));
if (StringSizes==NULL) return false;
// Remember each string size
for (int i=0; i<StringCount; i++) {
StringSizes[i] = (int)GetWideStringSize(Strings[i]);
size += StringSizes[i ];
}
}
if (size>ERROR_LOG_MAXIMUM_SIZE) // 0x98!
{
if (StringSizes!=NULL) ExFreePool(StringSizes);
return false;
}
// Try to allocate the packet
PIO_ERROR_LOG_PACKET Packet = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(SavedDriverObject, size);
if (Packet==NULL) {
if (StringSizes!=NULL) ExFreePool(StringSizes);
return false;
}
// Fill in standard parts of the packet
Packet->ErrorCode = ErrorCode;
Packet->UniqueErrorValue = 0;
// Fill in IRP related fields
Packet->MajorFunctionCode = 0;
Packet->RetryCount = 0;
Packet->FinalStatus = 0;
Packet->SequenceNumber = 0;
Packet->IoControlCode = 0;
if (Irp!=NULL) {
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
Packet->MajorFunctionCode = IrpStack->MajorFunction;
Packet->FinalStatus = Irp->IoStatus.Status;
if (IrpStack->MajorFunction==IRP_MJ_DEVICE_CONTROL || IrpStack->MajorFunction==IRP_MJ_INTERNAL_DEVICE_CONTROL)
Packet->IoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
}
// Fill in dump data
if (DumpDataCount>0) {
Packet->DumpDataSize = (USHORT)(sizeof(ULONG)*DumpDataCount);
for (int i=0; i<DumpDataCount; i++) Packet->DumpData[i] = DumpData[i];
} else Packet->DumpDataSize = 0;
// Fill in insertion strings after DumpData
Packet->NumberOfStrings = (USHORT)StringCount;
if (StringCount>0) {
Packet->StringOffset = sizeof(IO_ERROR_L0G_PACKET) + (DumpDataCount-1) * sizeof(ULONG);
PUCHAR pInsertionString = (PUCHAR)Packet + Packet->StringOffset;
// Add each new string to the end
for (int i=0; i<StringCount; i++) {
RtlMoveMemory(pInsertionString, Strings[i], StringSizes[i]);
pInsertionString += StringSizes[i];
}
}
// Log the message
IoWriteErrorLogEntry(Packet);
if (StringSizes!=NULL) ExFreePool(StringSizes);
return true;
}
To make LogEvent easier to use, Wdm3 provides a function called Wdm3EventMessage that simply takes an ANSI string as an argument. This is passed as a wide string to LogEvent to be inserted in the WDM3_MESSAGE message. Wdm3EventMessage is shown in Listing 13.6.
Listing 13.6 Wdm3EventMessage routines
void Wdm3EventMessage(const char* Msg) {
int MsgLen = GetAnsiStringSize(Msg);
int wMsgLen = MsgLen*2;
PWSTR wMsg = (PWSTR)ExAllocatePool(NonPagedPool,wMsgLen);
if (wMsg==NULL) return;
// Brutally make into a wide string
for (int i=0; i<MsgLen; i++) wMsg[i] = (WCHAR)(unsigned char)Msg[i];
PWSTR Strings[1] = { wMsg };
LogEvent(WDM3_MESSAGE, NULL, // IRP
NULL, 0, // dump data
Strings, 1); // strings
ExFreePool(wMsg);
}
The Wdm3EventMessage function is called in a few places in the Wdm3 driver as a test. Wdm3Unload sends a message "Unload", Wdm3AddDevice sends "AddDevice", and Wdm3Pnp sends "PnP xx" where "xx" represents the Plug and Play minor function code, in hex.
Installing and reinstalling a Wdm3 device should generate several events in the System event log. The events are generated in both the free and checked build versions. Remember to refresh the Event Viewer display to see any new events.
You should use NT events to report any problem events in NT 3.51, NT 4, and Windows 2000 drivers. The two relevant kernel function calls are just stubs in Windows 98, so you can safely generate events in WDM device drivers.