1. Using WinDbg with Featured Toaster
Featured Toaster
doesn’t have any known bugs, but WinDbg is still a useful tool to walk
through the source and see how the driver works. The walkthrough also
demonstrates the basics of how to use WinDbg.
The test computer should be running Windows with
kernel debugging enabled and be connected to the host computer. For
convenience, this section assumes that the COM ports are connected with
a null-modem cable.
The simplest way to use the debugger is to run a
test application that accesses the driver and hits a breakpoint,
causing the driver to break into the debugger. KMDF doesn’t include a
test application for Toaster, but the WDM test application, Toast.exe, works just as well with the KMDF version of the driver.
The source code for Toast.exe is located at WinDDK\BuildNumber\src\general\toaster\exe\toast. Use the same console window and build commands to build Toast.exe as for Featured Toaster. Unlike most projects, the output folder is not a subfolder of the project folder. Copy Toast.exe from the output folder WinDDK\BuildNumber\src\general\toaster\disk\chk_wxp_x86\i386 to a convenient folder on the test computer.
To get WinDbg ready to debug Featured Toaster:
1. | Launch WinDbg.
|
2. | On the File menu, click Symbol File Path, which causes the Symbols Search Path dialog box to appear. It should have paths for the Featured Toaster, Windows, KMDF run time, and wdfkd.dll symbols files.
|
3. | Select the Reload check box, which forces WinDbg to load the current symbols, and close the dialog box.
|
4. | On the File menu, click Source File Path, and add the path to the Featured Toaster’s source files.
|
5. | On the File menu, click Open Source File. Open Featured Toaster’s Toaster.c file.
|
6. | On the File menu, click Kernel Debug. This puts WinDbg into kernel debugging mode and establishes the connection with the test computer.
|
7. | Enter the appropriate baud rate and COM port in the Kernel Debugging dialog box, and click OK to start the debugging session.
|
8. | On the Debug menu, click Break, which forces a break and allows you to run debugging commands.
|
9. | Use the bp command as follows to set a breakpoint in Featured Toaster’s ToasterEvtIoRead routine.
|
To start debugging:
1. | Go to the test computer, launch a command window, and run Toast.exe. Type any character other than “q” to cause Toast.exe to send a read request to the driver. Toast.exe then calls Featured Toaster’s ToasterEvtIoRead routine. When the driver hits the breakpoint, it breaks into the debugger.
|
2. | On the Debug menu, select the Source Mode
check box if it isn’t already selected. This mode allows you to step
through the source code. Notice that the corresponding assembler
appears in the Command window. Even if you never write a line of assembler, it’s still useful to know something about it for debugging purposes.
|
3. | Set the cursor on a line of source code. On the Debug menu, click Run to Cursor. The selected line should be highlighted in blue.
|
4. | On the Debug menu, click Step Over to execute the next line. There’s also a Toolbar button for this purpose, located underneath the Window menu.
|
To get detailed information, call one of the debugger extensions. One useful WDF debugger extension is !wdfdriverinfo,
which returns general information about the driver. It takes the name
of the driver as a required argument plus a flag that controls exactly
what data is returned. 0xF0 returns essentially everything. The
following example shows the output from !wdfdriverinfo for Featured Toaster:
kd> !wdfdriverinfo wdfeatured 0xf0
Default driver image name: wdffeatured
WDF runtime image name: Wdf01000
FxDriverGlobals 0x829a3c80
WdfBindInfo 0xf7991b8c
Version v1.0 build(1234)
Driver Handles:
WDFDRIVER 0x7d6494f8 dt FxDriver 0x829b6b00
WDFDEVICE 0x7d4e1588 dt FxDevice 0x82b1ea70
Context 82b1ec30
Cleanup f7992dc0
WDF INTERNAL dt FxDefaultIrpHandler 0x82aca158
WDF INTERNAL dt FxPkgGeneral 0x829e8160
WDF INTERNAL dt FxWmiIrpHandler 0x82aa8cb0
WDF INTERNAL dt FxPkgIo 0x82b1cef0
WDFQUEUE 0x7d4e1250 dt FxIoQueue 0x82b1eda8
WDFQUEUE 0x7d552720 dt FxIoQueue 0x82aad8d8
WDF INTERNAL dt FxPkgFd0 0x82aa9d50
WDFCMRESLIST 0x7d4e6340 dt FxCmResList 0x82b19cb8
WDFCMRESLIST 0x7d4e1e10 dt FxCmResList 0x82b1e1e8
WDFCHILDLIST 0x7d639ba8 dt FxChildList 0x829c6450
WDFIOTARGET 0x7d6462d8 dt FxIoTarget 0x829b9d20
WDF INTERNAL dt FxWmiProvider 0x829b6698
WDF INTERNAL dt FxWmiInstanceExternal 0x82b1e098
WDFWMIPROVIDER 0x7d644088 dt FxWmiProvider 0x829bbf70
WDFWMIINSTANCE 0x7d560080 dt FxWmiInstanceExternal
0x82a9ff78 Context 82a9ffe8
WDFWMIPROVIDER 0x7d6436d0 dt FxWmiProvider 0x829c928
WDFWMIINSTANCE 0x7d550d78 dt FxWmiInstanceExternal
0x82aaf280
WDFWMIPROVIDER 0x7d6432a8 dt FxWmiProvider 0x829bcd50
WDFWMIINSTANCE 0x7d5295e0 dt FxWmiInstanceExternal
Ox82ad6a18 Context 82ad6a88
WDFFILEOBJECT 0x7d4edc18 dt FxFileObject 0x82b123e0
Many debugger commands and extensions require a handle to an object. For example, !wdfrequest takes a WDFREQUEST object handle and returns information about the object. To get such a handle, on the View menu, click Locals. Assuming that the debugger is still in the ToasterEvtIoRead routine, the associated WDFREQUEST object is name Request. The handle appears in the corresponding Value field in the Locals window.
2. Versioning and Dynamic Binding
When Windows loads a KMDF driver, the driver is dynamically bound to the KMDF run-time library (WdfMM000.sys).
Multiple drivers can share the same run-time library (DLL) image, and
the run-time libraries for two major versions can co-exist side-by-side.
When you build a KMDF driver, you link it with WdfDriverEntry.lib.
This library contains information about the KMDF version in a static
data structure that becomes part of the driver binary. The internal FxDriverEntry function in WdfDriverEntry.lib wraps the driver’s DriverEntry routine, so that when the driver is loaded, FxDriverEntry becomes the driver’s entry point. At load time, the following occurs:
FxDriverEntry calls the internal function WdfVersionBind (defined in wdfldr.sys) and passes the version number of the KMDF run-time library with which to bind.
The
loader determines whether the specified major version of the framework
library is already loaded. If not, it starts the service that
represents the framework library and loads the library and the driver.
If so, it adds the driver as a client of the service and returns the
relevant information to the FxDriverEntry
function. If the driver requires a newer version of the run-time
library than the one already loaded, the loader fails and logs the
failed attempt in the system event log.
FxDriverEntry calls the driver’s DriverEntry function, which in turn calls back to KMDF to create the KMDF driver object.
Although two major versions of KMDF can run
side-by-side simultaneously, two minor versions of the same major
version cannot. At installation, a more recent minor version of the
KMDF run-time library overwrites an existing, older minor version. If
the older version is already loaded when a user attempts to install a
driver with a newer version, the user must reboot the system.
For a boot driver, the loading scenario is different
because the KMDF run-time library must be loaded before the driver. At
installation, the co-installer reads the INF
(or the registry) to determine whether the driver is a boot driver. If
so, the co-installer both changes the start type of the KMDF service so
that the Windows loader starts it at boot time and sets its load order
so that it is loaded before the client driver.