Improve Driver Debuggability

Updated: February 16, 2004
On This Page

1. ASSERT() often. 1. ASSERT() often.

2. Use Windows tracing features. 2. Use Windows tracing features.

3. Log, log, log! 3. Log, log, log!

4. Combine techniques. 4. Combine techniques.

5. Bug check if irreparable corruption has occurred. 5. Bug check if irreparable corruption has occurred.

Resources Resources

An often overlooked way to improve the quality of a driver is to make the driver easier to debug. Here are several tips that can ease debugging during both testing and retail use.

1. ASSERT() often.

The ASSERT() macro evaluates an expression and, if the expression is false, breaks into the debugger. ASSERT() works only on binaries that are created in the checked build environment.

Frequent use of ASSERT() can help you identify and debug problems caused by data that is not valid. In addition, ASSERT() statements provide useful documentation for anyone who might have to maintain your code later.

Use ASSERT():

Upon entry to a function to check the validity of parameters from trusted sources, such as other kernel-mode components. However, remember to probe any parameters that your driver receives from user-mode applications or from other untrusted sources before referencing them.

Before returning from a function to ensure that the data structures manipulated by the function are in the correct state.

Anywhere within a function to verify the consistency of global structures.

Top of pageTop of page

2. Use Windows tracing features.

Microsoft Windows operating systems support several ways to log driver-related errors and information. Use them!

Maintain a record of I/O errors in the system event log. IoWriteErrorLogEntry and device-type-specific error logging routines, such as NdisWriteErrorLogEntry, write entries in this log.

Use Event Tracing for Windows (ETW) or Windows software trace preprocessor (WPP) to log driver data in the event tracing log.

ETW provides a general framework for tracing driver-defined events and data. WPP software tracing is built on ETW. It includes built-in support for IP addresses, system IDs, time stamps, and other common data types. You can add additional data types specific to your driver. The tracedrv sample (src\general\tracedrv) in the Windows Driver Development Kit (DDK) shows how to use WPP software tracing.

Use DbgPrint, DbgPrintEx, KdPrint, or KdPrintEx to log information on the kernel debugger console. (DbgPrint and KdPrint are available on Microsoft Windows 2000 and later releases of Windows. DbgPrintEx and KdPrintEx are available on Microsoft Windows XP and later releases of Windows.)

Top of pageTop of page

3. Log, log, log!

Maintain an in-memory history log to help you determine what state your driver was in when a hang, crash, or bug check occurred. Record data that might be useful in debugging, such as the I/O request in progress, the number of bytes transferred, and so forth.

A simple way of recording data is to create a rolling in-memory log:

LONG MyTrackerIndex;
MY_TRACK_DATA MyTracker[1024];

VOID 
LogInTracker (
	MY_TRACK_DATA * Data
	) 
{
	LONG Index = InterlockedIncrement (&MyTrackerIndex);
	Index %= 1024;
	MyTracker[Index] = *Data;
}

At any given time, the log contains the most recent 1024 records. To dump the contents of the log in the debugger, create a debugger extension that is tailored to the data structures in the log.

Top of pageTop of page

4. Combine techniques.

A simple macro that combines several of the preceding techniques can work as a RETAIL_ASSERT, providing valuable data on problems with a retail build. For example:

#define MY_RUNTIME_ASSERT(expr,p1,p2)                         \
  if (MyRuntimeAssertsLevel && !(expr)) {                     \
    if (MyRuntimeAssertsLevel >= 1){                          \
	    MyLogRuntimeAssert ((p1),(p2),#expr,__FILE__,...)       \
  	    if (MyRuntimeAssert >= 2) {                             \
  	      DbgPrintEx (DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,  \
                     "MyDriver.sys: RETAIL ASSERT ...", ...); \
	  	 if (MyRuntimeAssert >= 3) {                           \
		   DbgBreakPoint ();                                   \
}}}}
#if DBG
ULONG MyRuntimeAssert = 3;
#else
ULONG MyRuntimeAssert = 1;
#endif

In this prototype macro, expr represents the expression that would be tested in a normal ASSERT(). The variable MyRuntimeAssertsLevel enables the logging, and the variable MyRuntimeAssert defines the level of logging to be performed. When the retail driver runs, errors are logged to a file. If debugging is enabled, the macro sends a string to the debugger and, depending on the value of MyRuntimeAssert, can break into the debugger.

Using this or a similar macro can help you track errors such as timing problems that might occur during normal operation but might not appear during debugging.

Top of pageTop of page

5. Bug check if irreparable corruption has occurred.

Issue a bug check if all of the following are true:

Critical data is hopelessly corrupt.

Recovery is impossible.

Your driver is essential to system operation.

It is better to bug check than to propagate the corruption elsewhere in the system, possibly causing a crash later in some unrelated component. Such a crash in an unrelated component is often difficult to diagnose and repair.

If possible, try to build restart logic into your driver. If your driver is not essential to system operation, you might be able to stop the driver and restart it, instead of issuing a bug check.

Top of pageTop of page

Resources

See the Windows Driver Development Kit (DDK):

Driver Development Tools

Tools for Testing Drivers ("WPP Software Tracing")

Tools for Debugging Drivers ("Debugging Routines for Drivers")

Kernel Mode Driver Architecture

Design Guide ("Driver Programming Techniques")


Top of pageTop of page