| 1. ASSERT() often. | |
| 2. Use Windows tracing features. | |
| 3. Log, log, log! | |
| 4. Combine techniques. | |
| 5. Bug check if irreparable corruption has occurred. | |
| 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.
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. |
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. |
| • | 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.) |
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.
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.
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.
See the Windows Driver Development Kit (DDK):
| • | Driver Development Tools
| ||||
| • | Kernel Mode Driver Architecture
|