Testing drivers is a large and complicated subject. This section touches on only a few KMDF-specific issues.
There are two basic approaches to testing:
Static testing by using tools that analyze the source code for errors without actually executing it.
Dynamic
testing that puts an installed driver through its paces in hopes of
activating a bug and causing the driver to fail in some way.
Some related techniques, such as tracing tools,
record the actions of a driver. This section only provides a brief
introduction to the testing tools that WDF provides for KMDF drivers.
1. PREfast
PREfast is a static source code analysis tool that
detects certain classes of errors not easily found by a compiler.
PREfast steps through all possible execution paths in each function and
evaluates each path for problems by simulating execution. PREfast does
not actually execute code and cannot find all possible errors. However,
it can find errors that the compiler might not catch and that can be
difficult to find during debugging.
PREfast
is a general-purpose tool that can be used with any type of project.
WDF includes a customized version of PREfast that checks for
driver-specific issues such as the correct interrupt request level
(IRQL), use of preferred driver routines, and misuse of driver
routines. It also aggressively checks for memory and resource leaks.
PREfast is run in conjunction with a build. The
following is a simple example of how to run a PREfast build. For the
purposes of illustration, the command uses a typical set of build
flags, but any build flags can be used with PREfast. The second line
opens the PREfast viewer to display the error log.
prefast build –ceZ
prefast view
2. Static Driver Verifier
Static Driver Verifier (SDV) is a static
compile-time unit-testing tool that symbolically executes the source
code. SDV does deeper testing than PREfast and creates what is in
effect a hostile environment for the driver. It systematically tests
all code paths by looking for violations of usage rules. The symbolic
execution makes very few assumptions about the state of the operating
system or the initial state of the driver, so SDV can create scenarios
that are difficult to handle with traditional testing.
The set of rules that are packaged with SDV define
how device drivers should use the DDI. The categories of rules tested
include the following.
Category | Tests |
---|
IRP | Functions that use I/O request packets |
IRQL | Functions that use interrupt request levels |
PnP | Functions that use Plug and Play |
PM | Functions that use power management |
WMI | Functions that use Windows Management Instrumentation |
Sync | Functions that use synchronization, including spin locks, semaphores, timers, mutexes, and other methods of access control |
Other | Functions that are not fully described by any of the other categories |
3. KMDF Log
KMDF
includes an internal trace logger that is based on the Windows software
trace preprocessor (WPP). It tracks the progress of I/O request packets
(IRPs) through the framework and the corresponding WDFREQUEST
objects through the driver. The KMDF log maintains a record of recent
race events—currently, approximately the last 100—for each driver
instance. Each KMDF driver has its own log.
You can use WDF debugger extensions to view and save
the KMDF log during interactive debugging. The typical saved log file
is small (10 to 20 KB) and written in a binary format. You can also
make logs available as part of a small-memory dump for inspection after
a crash.
4. KMDF Verifier
KMDF Verifier operates on an installed and running driver. It complements Driver Verifier and supports a number of WDF-specific features. In addition, if the target driver is not loaded, KMDF Verifier can be turned on without rebooting the system. In general, you should run both Driver Verifier and KMDF Verifier during development.
KMDF Verifier
provides extensive tracing messages that supply detailed information
about activities within the framework. It tracks references to each WDF
object and builds a trace that can be sent to the debugger. In
particular, KMDF Verifier
Checks lock acquisition and hierarchies.
Ensures that calls to the framework occur at the correct IRQL.
Verifies correct I/O cancellation and queue usage.
Ensures that the driver and framework follow the documented contracts.
KMDF Verifier can
also simulate low-memory and out-of-memory conditions. It tests a
driver’s response to these situations to determine whether the driver
responds properly without crashing, hanging, or failing to unload.
5. Debugging a KMDF Driver
Debuggers are an essential development tool;
programs under development always have bugs, especially in the early
stages. Debuggers can also be used as learning tools, to step through sample code and understand in detail how it functions.
Debugging is normally done at run time to determine
why a driver is failing. The exception to this rule is that kernel
debuggers can also be used to analyze crash-dump files. If you are new
to driver development, you will find kernel debugging a bit different
from application debugging. One immediately noticeable difference is
that kernel debugging requires three hardware components:
A host computer running WinDbg. This is typically the computer that is used to develop and build the driver.
A
test computer running an appropriate build of Windows with the driver
installed and kernel debugging enabled. Debugging is typically done
with a checked build of the driver because checked builds are much
easier to debug. Test computers also often run a checked build of
Windows.
A way for the two computers to
communicate. Historically, this was handled by connecting serial ports
on the host and test computers with a null-modem cable. An alternative
is to use USB or IEEE 1394 cables.
The kernel debugging tools are available as a
separate package from WHDC that includes the debugging tools,
documentation, and some related files. The procedures for setting up
systems for kernel debugging are covered in detail in the debugging
documentation.
The debugging package includes two kernel debuggers,
WinDbg and KD. They have essentially the same capabilities, but WinDbg
has a graphical user interface (GUI) that many developers find
convenient. The examples we use are from WinDbg. We will cover a
walkthrough of a simple debugging session with Featured Toaster that demonstrates the basics of how to use WinDbg with a KMDF driver.
WinDbg is a debugger, not an IDE like Visual Studio.
It comes into play only after you have successfully built the driver
and installed it on a test machine. There are two basic ways to use
WinDbg:
Kernel debugging—In this mode, WinDbg is connected to an active test machine and can interact with a running driver.
Crash dump analysis—If the system crashes, you can use WinDbg to analyze the crash dump data to try to determine the cause.
When
you launch WinDbg, you must first point it to the driver’s source and
symbol files. To start a kernel debugging session, on the File menu, click Kernel Debug.
You won’t be able to do much until the system breaks into the debugger.
This essentially stops the test computer and turns its operation over
to WinDbg. The following are common ways to cause a test system to
break into the debugger:
You instruct WinDbg to force a break. This can be done from the UI, on the Debug menu by clicking Break, or by clicking the corresponding toolbar button. You can also run the break command.
You
use WinDbg to dynamically insert breakpoints into the running driver.
This approach is quite flexible because it allows breakpoints to be
inserted, disabled, enabled, or removed during the debugging session.
You
insert DbgBreakPoint statements in the driver’s source code. This
approach is simpler but less flexible because the driver must be
recompiled and reinstalled to change a breakpoint.
The
driver bug checks and crashes the test computer. At this point, you can
use WinDbg to examine crash dump data, but the computer must be
rebooted before it can run again. You can force a system crash by
running the .crash command.
6. Kernel Debugging
With the first two cases in the previous discussion,
after the driver breaks into the debugger, you can do most of the usual
debugging procedure: examine variables, step through lines of code,
examine the call stack, and so on.
Much of the interaction with WinDbg is through the command-line interface. There are two basic types of commands:
Debugger commands
are native to the debugger and are used to obtain basic information.
Commands are typically one or two letter strings, often followed by one
or more arguments. For example, k and related commands display a thread’s stack frame and some related information. When you are finished, use the g
command to break out of the debugger and return the driver and test
computer to normal operation. Some simple commands have corresponding
menu items or toolbar buttons, but many can be run from the command
line.
Debugger extensions
extend the basic set of debugger commands. A number of them are
included with the debugger package and are launched from the command
window in much the same way as debugger commands. The first character
of a debugger extension is always an exclamation point (!), to
distinguish it from a debugger command. For example, a particularly
useful debugger extension is !analyze,
which is used to analyze crash dumps. In addition to the debugger
extensions that are included with the debugger package, it is also
possible to write custom debugger extensions.
The debugging Help
file includes a complete reference for debugging commands, standard
debugger extensions, and the API that is used to create custom
extensions. In the next section, we will discuss some debugger
extensions that were created specifically for KMDF drivers.
If a driver bug causes a system crash, the computer
must be rebooted before it can run again. However, if WinDbg is running
and connected when the test computer crashes, the system breaks into
the debugger and you can analyze the crash dump immediately. You can
also configure your test computer to attempt to create a crash dump
file when it crashes. If the file is successfully created, you can load
it into WinDbg and analyze the crash after the fact. WinDbg doesn’t
have to be connected to the test computer for this purpose.
7. KMDF Driver Features
Debugging a KMDF driver is similar in many ways to
debugging any Kernel Mode Driver. However, some debugging features are
specific to KMDF.
8.8.7.1. Registry Settings
A number of the WDF debugging features must be enabled by setting registry values for the driver’s Parameters\Wdf subkey. The driver key itself is named for the driver and located under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services.
Table 1 summarizes the values that can be added to the Wdf
subkey. The features are disabled by default. To enable most of these
features, create the associated value and set it to a nonzero number.
To enable handle tracking, set TrackHandles to a MULTI_SZ
string that contains the names of the objects that you want to track.
The settings do not take effect until the next time that the driver is
loaded. The simplest way to reload a driver is to use Device Manager to disable and then reenable the driver. Table 1 shows some of the settings.
Table 1. Registry Values
Category | Type | Tests |
---|
VerifierOn | REG_DWORD | Set to a nonzero value to enable the KMDF verifier. |
VerifyOn | | Set to a nonzero value to enable the WDFVERIFY macro. If VerifierOn is set, WDFVERIFY is automatically enabled. |
DbgBreakOnError | REG_DWORD | Set to a nonzero value to instruct the framework to break into the debugger when a driver calls WdfVerifierDbgBreakPoint. |
VerboseOn | REG_DWORD | Set to a nonzero value to capture verbose information in the KMDF logger. |
LogPages | REG_DWORD | Set
to a value from 1 to 10 to specify the number of memory pages that the
framework assigns to its logger. The default value is 1. |
VerifierAllocateFailCount | REG_DWORD | Set
to a nonzero value to test low-memory conditions. When
VerifierAllocateFailCount is set to n, the framework fails every
attempt to allocate memory for the driver’s objects after the nth
allocation. This value works only if VerifierOn is also set. |
TrackHandles | MULTI_SZ | Set
to a MULTI_SZ string that contains the names of one or more object
types to track handle references to those types. This feature can help
find memory leaks that are caused by unreleased references. To track
all objects types, set TrackHandles to “*”. |
ForceLogsInMiniDump | REG_DWORD | Set to a nonzero value to include the KMDF log in a small memory dump file if the system crashes. |
7.2. Symbols
You
must explicitly provide WinDbg with paths to all the relevant symbols
files. For KMDF drivers, this normally includes the symbols for the
driver, Windows, and the KMDF run time. The symbols file is named Wdf01000.pdb and is located under %WDF_ROOT%\Symbols\Build-Environment\wdf\sys.
There are six Build Environment folders, one for each of the standard
build types and architectures. For most debugging, you should use the
checked build for the appropriate architecture.