Conventional Driver Event Logging

< BACK  NEXT >
[oR]

The original Windows NT architecture includes a mechanism that allows software components to keep a record of noteworthy events. This event-logging capability can help monitor the behavior of software that is under development or in production.

How Event Logging Works

The developers of Windows NT had several goals for the event-logging architecture. The first was to provide subsystems with the unified framework for recording information. This framework includes a simple yet flexible standard for the binary format of event-logging entries.

Another goal was to give system administrators an easy and consistent way to view these messages. As part of this goal, viewer utilities must be able to display event messages in the currently selected national language. Figure 13.3 shows the event-logging architecture.

Figure 13.3. Event-logging architecture.
graphics/13fig03.gif

The overall process for the generation and display of event messages is described by the following:

  1. All event messages take the form of packets in Windows 2000. When a kernel-mode driver wants to log an event, it first calls the I/O Manager to allocate a message packet from nonpaged pool.

  2. The driver fills this packet with various pieces of descriptive information. One of the key items is a 32-bit message code number that identifies the text to be displayed for this packet. Once the packet is ready, the driver returns it to the I/O Manager.

  3. The I/O Manager takes the message packet and sends it to the system event-logging thread. This thread accumulates packets and periodically writes them to the proper event-log file.

  4. The Event Viewer utility reads binary packets from the log files. To translate a packet's 32-bit message code into text, the Viewer goes to the Registry. There it finds the path names of one or more message files associated with the packet. These message files contain the actual message text (possibly in multiple languages), which the Viewer displays.

Working with Messages

As just described, a driver does not include the actual text for its messages in an event-log entry. Instead, it identifies messages using code numbers. The text associated with these code numbers takes the form of a message resource stored somewhere on disk. This section describes how these message code numbers work and explains how to generate custom message resources.

The code number identifying a specific message is a 32-bit value consisting of several fields. Figure 13.4 shows the layout of a message code.

Figure 13.4. Layout of a message-code number.
graphics/13fig04.gif

The Severity field of a message code is a 2-bit field which signifies success (0), warning (2), error (3), or informational (1) status. The I/O Manager provides a number of standard messages that a driver can use. The header file, NTIOLOGC.h, defines symbolic names for these message codes, all of which begin with IO_ERR_. The file can be browsed for a complete list of standard messages.

To use standard messages, a driver must be included in the list of event-logging system components within the Registry. The actual text file for these shared messages must also be supplied (IOLOGMSG.dll). The procedure for performing this registration is described later in this chapter.

A driver can also supply custom message text. To do this, the following steps should be followed:

  1. Write a message definition file that associates the message code with a specific text string.

  2. Compile this file using the message compiler (MC) utility.

  3. Incorporate the message resources generated by MC into the driver.

  4. Register the driver as an event-logging system component and identify the driver executable as the file containing the text for these private messages.

Writing Message Definition Files

To use the MC utility, a definition file describing all the driver messages must be written. This definition file is divided into two major sections.

Header section.

Keywords in the header define names for values that are used in the actual message definitions. Table 13.17 contains the keywords that can be used in the header section of a message definition file.

Message section.

This portion of the message definition file contains the actual text of the messages. Each message begins with the keywords listed in Table 13.18.

Table 13.17. Keywords Used in Header Section of Message Definition File
Header section keywords
Keyword Description
MessageIdTypedef = DataType Typecast applied to all message codes
SeverityNames = (name=number[:name]) Up to four severity values used in the message section
FacilityNames = (name=number[:name]) Facility names used in the message section
LanguageNames =(name=number[:filename]) Language names used in the message section

Table 13.18. Keywords Used in Message Section of Message Definition File
Message section keywords
Keyword Description
MessageID = [number | +number] 16-bit value assigned to this message
Severity = Severity name Severity level of the message
Facility = Facility name Facility generating the message
SymbolicName = SymbolicName Name of message code in generated header file
Language = LanguageName Language ID associated with the message

The message text itself begins after the last keyword. The text of a message can occupy several lines. A message is ended with a line containing only a single period character.

The message compiler ignores any white space or carriage returns in a message definition. Various escape sequences (listed in Table 13.19) can be included in the body of the message.

The %1-%99 escape codes represent Unicode strings (embedded in the event log packet) that are inserted in the message when the Event Viewer displays it. If a kernel-mode driver associates an event packet with its device object, %1 will automatically contain the OS name of the device; if the driver associates the packet with its driver object, %1 is blank. In either case, the first real insertion string is %2, the second %3, and so on.

A Simple Example

Here is the message definition file for a simple example.

Table 13.19. Escape Codes Used Within Message Text
Message formatting escape codes
If you use... THEN it is replaced with...
%b A single space character
%t A single tab character
%r%n Carriage return and linefeed
%1-%99 An insertion string

HEADER SECTION

The first part of the message definition file contains header information.

 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    RpcRuntime = 0x2:FACILITY_RPC_RUNTIME    RpcStubs = 0x3:FACILITY_RPC_STUBS    Io  = 0x4:FACILITY_IO_ERROR_CODE    MyDriver = 0x7:FACILITY_MY_ERROR_CODE ) 
MESSAGE SECTION

This section contains message text and identifiers. It defines the actual text to be associated with a message code number.

 MessageId=0x0001 Facility=MyDriver Severity=Informational SymbolicName=MSG_LOGGING_ENABLED Language=English Event logging enabled for MyDriver. . MessageId=+1 Facility=MyDriver Severity=Informational SymbolicName=MSG_DRIVER_STARTING Language=English MyDriver has successfully initialized. . 

Compiling a Message Definition File

Once written, the message definition file must be compiled using the MC tool supplied with the Platform SDK and the DDK. Table 13.20 shows the syntax of the MC command.

After a successful message definition file is compiled, the following files are generated:

  • filename.RC.

    This is a resource control script that identifies all the languages used in the message definition file. For each language, it also identifies the binary message file containing the message text.

  • filename.H

    This header file contains #define statements for all the message code numbers in the MC input file. The compiler also puts inline commentary in the header, including the text of the corresponding message.

  • MSGnnnnn.BIN.

    This binary file holds all the text for messages for one language. MC generates separate files for each national language used in the message definition file.

Table 13.20. Syntax of the MC Command
MC [-?cdosvw] [-herx argument] [-uU] filename.MC
Parameter Description
-c Set Customer bit in all message codes.
-d Use decimal definitions of facility and severity codes in header.
-o Generate OLE2 header file.
-s Insert symbolic name as first line of each message.
-v Generate verbose output.
-h pathname Location of generated header file. (Default is current directory.)
-e extension One-character to three-character extension for header file.
-r pathname Location of generated RC and binary message files.
-x pathname Location of generated debug file.
-u Input file is Unicode.
-U Message text in binary; output binary file should be Unicode.
filename Name of the message definition file to compile.

Adding Message Resources to a Driver

In most cases, the output from the MC compilation is included in the driver executable itself. Simply including the name of the .RC script file within the driver project (makefile) is sufficient.

Registering a Driver as an Event Source

The system Registry serves as the linkage between an event source and the message files needed to translate any message codes appearing in its log entries. To register a driver as an event source, the following changes must be made to the Registry under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog\System:

  1. Add the name of the driver's executable (without the .SYS extension) to a REG_MULTI_SZ value called Sources.

  2. Add a key with the same name as the driver.

  3. In this new key, create a REG_EXPAND_SZ value named EventMessageFile. It should contain the full path names of the message files used by the driver, separated with semicolons (;). If standard messages from NTIOLOGC.h are used, IOLOGMSGodll must also appear in the list.

  4. In the same key, create a REG_DWORD value named TypesSupported. A value of 0x7 indicates that all message types may be generated.

Generating Log Entries

Code to actually generate a message is relatively straightforward. It involves allocating an event message packet, filling it, and sending it to the system logging thread.

It is common practice to trigger the verbosity of driver event generation based on a driver-specific registry value. For example, under the Parameters subkey of the driver's service key, a value called EventLogLevel might determine the number and extent of actual messages generated. Thus, during debugging or during a production incident, full event logging could be enabled.

Allocating an Error-Log Packet

When a driver uncovers an event that needs reporting, it must prepare an error-log packet. There are three sections to an error-log packet.

  • A standard header

  • An array of driver-defined ULONGs (referred to as dump data)

  • One or more NULL-terminated Unicode insertion strings. These strings are not the counted UNICODE_STRING data structures used elsewhere.

Both the dump-data and insertion strings are variable in length and are optional. Figure 13.5 shows the structure of an error-log packet.

Figure 13.5. Layoout of an error-log packet.
graphics/13fig05.gif

Before an error-log packet can be allocated, its size must be determined. Sufficient space must be allocated for any dump-data and insertion strings. The size of the packet can be calculated using a variation of the following piece of code:

 PacketSize =      sizeof( IO_ERROR_LOG_PACKET ) +      (sizeof( ULONG ) * (DumpDataCount-1)) +      sizeof( InsertionStrings ); 

The requested size of the packet cannot exceed ERROR_LOG_MAXIMUM_SIZE.

The IoAllocateErrorLogEntry, described in Table 13.21, is used to allocate the packet. As seen, the packet can be associated either with the Driver object or with a specific Device object. The choice determines how the Event Viewer utility displays the message. Initialization and shutdown messages are typically Driver-level messages, while problems involving specific IRPs or hardware should be associated with a Device object.

Notice that the use of IoAllocateErrorLogEntry requires a thread context at or below DISPATCH_LEVEL IRQL. Therefore, if an ISR needs to log an error (a common occurrence), a CustomDpc routine must be used to perform the actual work.

Table 13.21. IoAllocateErrorLogEntry Function Prototype
PVOID IoAllocateErrorLogEntry IRQL <= DISPATCH_LEVEL
Parameter Description
IN PVOID IoObject Address of a Device object generating an error
Address of a Driver object reporting an error
IN UCHAR EntrySize Size in bytes of packet to be allocated
Return value PIO_ERROR_LOG_PACKET: success
NULL: allocation failure

Logging the Error

Once the packet is allocated, all the relevant fields must be filled. In addition to the fields listed in Table 13.22, any driver-specific data and strings must be added.

When the packet is ready, use IoWriteErrorLogEntry to send it to the system logging thread. The packet is no longer owned by the driver once this function is called, so the driver must not be touched again. As with packet allocation, this function can only be used at or below DISPATCH_LEVEL IRQL.

Table 13.22. Layout of an IO_ERROR_LOG_PACKET
IO_ERROR_LOG_PACKET, *PIO_ERROR_LOG_PACKET
Field Description
UCHAR MajorFunctionCode IRP_MJ_XXX code of current IRP
UCHAR RetryCount Zero-based count of consecutive retries
USHORT DumpDataSize Bytes of driver-specific data
USHORT NumberOfStrings Number of insertion strings
USHORT StringOffset Byte offset of first insertion string
USHORT EventCategory Event category from driver's message file
NTSTATUS ErrorCode IO_ERR_XXX (see NTIOLOGC.H)
ULONG UniqueErrorValue Indicates where in the driver the error occurred
NTSTATUS FinalStatus STATUS_XXX value from the IRP
ULONG SequenceNumber Driver-assigned number for current IRP
ULONG IoControlCode IOCTL_XXX if this is a DeviceIoControl request
LARGE_INTEGER DeviceOffset Device offset where error occurred, or zero
ULONG DumpData [1] Driver-specific data if DumpDataSize is zero

< BACK  NEXT >


The Windows 2000 Device Driver Book(c) A Guide for Programmers
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
ISBN: 0130204315
EAN: 2147483647
Year: 2000
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net