The NT Insider

Fundamentals: NT Drivers 102
(By: The NT Insider, Vol 5, Issue 3, May-Jun 1998 | Published: 15-Jun-98| Modified: 20-Aug-02)

In the precursor to this article, back at the end of last year, we discussed the pre-requisites to writing a driver. We said, basically, that because of NT?s protection scheme you may want to write a driver if you need to control a device from NT. We said that to develop drivers for NT, you needed Microsoft VC++, the Software Development Kit (SDK), and the Device Driver Kit (DDK) which were all available as part of a subscription to the Microsoft Developer?s Network (MSDN) Professional Edition.

In the first part of the article, we also said that you needed two systems on which to do your driver development: One system to do your editing, compiling, and linking and the other on which you?ll run and debug your evolving driver code. Finally, we discussed the differences between the Free (retail) and Checked (debug) builds of NT. We mentioned that the Free build was the build to install on your development system. We finished by providing a method that we use here at OSR to put just the checked components of interest on your test system. In our case, we discussed how to install just the checked HAL and NTOSKRNL files on the test system.

In this installment of our (never ending?) series, we?ll look at how to complete the set up of a driver development environment, as well as how to build and debug drivers under NT.

The Environment

Recall that to develop drivers under NT, you should typically use two systems: One system is a stable, well configured, system on which you do your development. The other system is a smaller test system. You control the operation of the test system from the development system via a null-modem cable connected between COM ports on the two systems. This configuration allows you to check and modify the state of memory, registers, and various system data structures in the test system from the debugger that you?ll be running on the development system.

While it is certainly possible to use NuMega?s (now Compuware) SoftIce for Windows NT and do driver development on a single system, we honestly can?t recommend this approach. This has nothing to do with NuMega?s product, which is fine. Rather, we just don?t savor the idea of having our (buggy) still-under-development device driver crash the system and (ooops!) just happen to accidentally wreck the file structure on that system in the process. And, yes, this has happened. My feeling is why risk it? Develop on one system, debug on another.

To set up this dual-system debugging arrangement, you need to do two things:

• Enable kernel debugging on your test system ? This will allow you to control the test system from the debugger on the development system.
• Set up and run the debugger on your development system ? This entails setting all the right options in the debugger to enable it to communicate with the kernel debugger running on the test system.

Enabling Kernel Debugging On The Test System

Kernel debugging support is part of all Windows NT systems, regardless of whether the system is Free or Checked. Ordinarily, kernel debugging is not enabled on a system, however. To enable it on your test system, you need to instruct Windows NT at boot time to turn on debugging support. On x86 architecture systems, you do this by editing the file BOOT.INI in the root directory of the boot volume.

BOOT.INI is the control file that directs the Windows NT boot process. When Windows NT V4 is initially installed on a system, the contents of BOOT.INI looks like that shown in Figure 1. Each line in the [operating systems] section of the file points to the SystemRoot of a Windows NT system that can be potentially started. During NT system startup, the "ntldr" program reads BOOT.INI and displays this list of systems. The user selects the operating system they wish to start from this list. If no selection is made, a default choice is taken after a timeout period.

Figure 1 ? The original Boot.ini

Debugging support is enabled in an NT system by adding switches to a particular operating system line in BOOT.INI. These switches tell NT that it should enable kernel debugging when that system is started, and instructs it as to how to communicate with the remote debugger. Some of the most common switches used to control kernel debugging options are listed below:

• /DEBUG ? Enables kernel debugging support and indicates that during system boot-up the system should try to make an attempt to connect with the remote kernel debugger.
• /DEBUGPORT=x ? Specifies that serial port "x" should be dedicated to use with the kernel debugger. Implies /DEBUG.
• /BAUDRATE=y ? Indicates the speed to which serial port "x" should be set. Obviously, this speed must be the same as that used by the kernel debugger on the development system.

The most typical way to enable kernel debugging for a system is to add an additional choice to the list of systems to start. This way, starting a system with kernel debugging is a choice that can be made at boot time. If kernel debugging support is desired, the system with kernel debugging support can be selected from the list shown at boot time. If kernel debugging support is not required, a system without kernel debugging support enabled can be started.

To add an additional choice to BOOT.INI, simply copy the line in the [operating systems] section that describes the operating system image that you want to boot. Put this copy on a line by itself in BOOT.INI and append the appropriate debugger control switches shown above. Figure 2 shows a version of BOOT.INI that has been updated to include a third operating system option to be started, which has debugging enabled.

Figure 2 ? Boot.ini with kernel debugging option

The next time NT is started on the test system with the BOOT.INI that appears in Figure 2, there will be three choices displayed of systems to start. The third choice will indicate that the debugger is enabled. To enable kernel debugging on the test system, reboot the system and select the choice with debugging enabled.

There is one trick that you have to be aware of when you edit BOOT.INI and that is by default, it is a Read-Only , System, Hidden file. Therefore, before you can edit BOOT.INI successfully, change its attributes (using Explorer or another appropriate utility) to remove at least the Read-Only attribute. The other attributes do not have to be changed. Further, after editing BOOT.INI there is no need to restore the Read-Only attribute.

Setting Up And Running The Debugger

Enabling kernel debugging on your test system merely allows that system to be controlled by a system running an appropriate kernel debugger. The next step in the process of getting a driver development environment established entails setting up and running the kernel debugger on your development system.

The standard remote debugger supplied as part of the Windows NT SDK is WinDbg (ostensibly this stands for "win De-bug", but it is pronounced "Wind bag" by all those who know and love it). This debugger includes kernel debugging support, which will let you control your test system from WinDbg running on your development system. To set WinDbg up with kernel debugging support, start WinDbg, and select Options? from the View Menu Item. In the WinDebug Options dialog box, select the Kernel Debugger tab (shown in Figure 3).

To enable kernel debugging, click the Enable Kernel Debugging check box in the Flags section of the WinDebug Options dialog box. In the Communications section of this same dialog box, set the appropriate options regarding your communications set up. Set the Baud Rate option to the baud rate for the null-modem connection to the kernel debugger. This must match the baud rate indicated in BOOT.INI (Here?s one of my personal bug-a-boos: WHY do they call it baud rate? It?s not necessarily the baud rate. It?s the number of bits per second. Oh, never mind?). Using the Port option, select the port on this machine that will be connected via the null modem cable to the test system. The Cache Size parameter indicates the initial size of WinDbg?s cache, which is used to hold values. The default is usually sufficient. Finally, set the Platform option to correctly indicate the processor architecture (x86 or Alpha) of the test system. When satisfied with your selections, click on the Symbols tab of the WinDebug Options dialog box to proceed to setting up the symbol search path.

From the Symbols tab, enter the path WinDbg should search for debugging symbols in the text box labeled Path used to search for the debugging symbols:. We recommend dedicating a directory on your development system to hold debugging symbols, such as C:\DEBUG. When you first set up WinDbg, this directory should be empty. We will talk about what goes into this directory later. The remaining options, such as Load Time and Symbol Suffix, in the Symbols tab of the WinDebug Options dialog box may be ignored.

Figure 3 ? WinDbg's Kernel Debugging Options

When you are satisfied with your choices, click OK. Then start WinDbg by selecting the Debug menu, and Go (or, alternatively, hit F5). WinDbg will display the message Kernel debugger waiting to connect on comX at Y baud, where (if you did things right) "X" is the com port number and "Y" is speed you indicated in the kernel debugger dialog box.

If your test system is already running, is connected to your development system via the null-modem cable, and has been booted with debugging enabled, you can try to get WinDbg and your test system to "synch up". This is done by either selecting Break from the Debug menu item, or by typing Control-C in WinDbg?s Command Window. If you have a good connection between WinDbg and your test system, and if the parameters in both BOOT.INI on your test system and in WinDbg are correct, WinDbg should respond to the control-break by displaying a message like: Kernel Debugger connection established on com3 @ 115200 baud and Kernel Version 1381 Checked loaded @ 0x80100000. This indicates you have a connection.

Note that the last message displayed in the WinDbg command window is Hard coded breakpoint hit. This is WinDbg?s cute little way of telling you that your test system is now stopped. At this point, you may enter other debugging commands (discussed later) via the Debug menu, or by typing directly into the command window. Enter the ks command to show the contents of the kernel stack. You should see a display that shows you a non-symbolic dump of the kernel stack. Note that at this point, the information displayed is not very helpful! We?ll fix that shortly. At this time, restart your test system by hitting F5, selecting the Debug menu and Go, or by typing the g command (followed by a carriage return, of course) in the Command window.

Setting Up Debugging Symbols

To enable you to get traces with symbolic names, and also to allow you to manipulate variables and other symbols in your driver code under development, WinDbg needs access to symbol table information. The operating system components, such as the kernel, the HAL, and the operating system supplied drivers, have the symbol table information removed from their executable images. The symbols for these operating system components are typically located on the operating system distribution disk, under the \Support\Debug\i386\Symbols directory. Note that the symbols for the free (retail) components appear on the standard (free, retail) distribution kit in this directory. The symbols for the checked components appear in this directory on the checked (debug) distribution kit. You must use the symbols for the build (that is, checked or free) that matches the components you have installed on your test machine. Thus, if you installed the checked versions of the NTOSKRNL.EXE and HAL.DLL as we recommended in the first article in this series, you need to provide the checked versions of the symbols (from the checked distribution kit) to the debugger.

All symbols files supplied with Windows NT have the file type .DBG. To make it a bit easier to find particular files (and because components that differ by only file type can?t be in the same directory) Microsoft separates the symbol files under the \Support\Debug\i386\Symbols directory by file type. Under this directory there are subdirectories named Acm, Com, Cpl, Dll, Drv, Exe, Scr, and Sys. Thus, the symbols for NTOSKRNL.SYS for the x86 architecture would be located in the file \Support\Debug\i386\Symbols\Sys\NTOSKRNL.DBG. The symbols for HAL.DLL would be located in \Support\Debug\i386\Symbols\Dll\HAL.DBG.

Now that you can find the appropriate symbols, copy the symbols for any components of interest from the appropriate distribution kit to your dedicated debug directory on your development system. This directory is the one that you entered the path to in the Symbols tab of the WinDebug Options dialog box, discussed earlier. Typically, you should always copy the symbols for the kernel (NTOSKRNL) and the HAL (remember to copy the correct version of the HAL symbols, and rename it HAL.DBG in the debug directory!).

Once your symbols have been correctly set-up, if you tell WinDbg to stop execution of the test system (by selecting Debug and then Break or by typing Control-C in the command window, you should be able to get a symbolic stack dump. If your system is anything like mine, it will show that you stopped in the module NT, in the routine RtlpBreakWithStatusInstruction, that was called from KiIdleLoop. Cool, eh?

In the event that you did not see a symbolic stack dump, try issuing the .RELOAD command (that?s a period immediately followed by the word reload, with no quotation marks of course!). This command causes WinDbg to attempt to scan for and reload any symbol table files.

At this point, you should have a kernel driver development and debugging environment completely set up. You have communications established and tested between your development system and your test machine. Now, it?s time to move on to actually building and debugging your driver.

Building Drivers

As discussed in the first installment of this article, one of the necessary items for building device drivers under Windows NT is the Device Driver Kit (DDK). The standard driver development environment at present is not integrated into the Visual Workbench. Thus, drivers are built from the command line. From the Windows NT DDK program group, select either the Checked Build Environment or Free Build Environment icon, depending on which type of driver you want to build. This will result in a command window being displayed. If you select Checked Build Environment, for example, any drivers built in the displayed command window will be Checked. That is, they will have the symbol DBG set equal to one, will be built with symbols in their executable images, and will be built with fewer compiler optimizations (to facilitate debugging).

Driver development is controlled by the BUILD utility. This utility, located in the \ddk\bin directory, provides a DDK-specific front-end to the driver building process. BUILD (and NMAKE, which it directly runs) reads inputs from files named SOURCES, DIRS, and Makefile. Build creates as outputs files named BUILD.LOG, BUILD.ERR, and BUILD.WRN. If all goes well, the ultimate result of executing the BUILD utility is creation of an executable version of your driver file. This file will have a .SYS file type.

BUILD must be run from the directory it is in to start processing. That directory must contain either a DIRS file, or a SOURCES file and a Makefile file.

The DIRS file contains a list of directories for build to process. These directories must all be subdirectories of the current directory. Figure 4 shows a sample DIRS file from the \ddk\src\comm directory of the NT V4 DDK. This file directs BUILD to look in the specified directories (which are named INTPAR, PARCLASS, and the like in the example) under the current directory. In each of these directories, BUILD will either find another DIRS file, or a SOURCES file and a Makefile file instructing it how to proceed.

Continuing with the example, in the INTPAR directory of the DDK (to be specific that?s the directory \ddk\src\comm\intpar") there is a SOURCES file that directs the BUILD utility how to build the source files contained in that directory. The SOURCES file is shown in Figure 5.

Figure 4 ? Sample DIRS File

Figure 5 ? Example SOURCES File

The sources file contains macros that instruct the BUILD utility about what to build, where to get inputs, and where to put outputs. Microsoft supplies some documentation for BUILD in the DDK Programmer?s Guide. While many of the BUILD macros are self-explanatory, some of the more common BUILD directives are listed in Figure 6 for easy reference.

 Command Use C_DEFINES= Used to provide #define switches to the compiler. For example, C_DEFINES=/DFT_BUILD defines the symbol FT_BUILD. INCLUDES= Set equal to the path indicating directories to search for INCLUDE files. MSC_WARNING_LEVEL= Allows warning switches to be set. For example, MSC_WARNING_LEVEL=/W4 enables warnings at level 4 NTTARGETFILE0= Creates the indicated file as part of pass zero of the build, by executing the commands in "makefile.inc" in the current directory SOURCES= Set to the list of source files in the current directory to be compiled. File names can be separated with spaces or tabs. TARGETNAME= Specifies the name for the executable file being built TARGETPATH= Indicates where the executable image should be placed. Note that $(BASEDIR) refers to the base directory where the DDK was installed (typically \ddk). TARGETTYPE= Indicates type of image being created. Typically one of DRIVER or DYNLINK Figure 6 ? Common BUILD Macros The file named Makefile is a standard file that must appear unchanged in every directory with a SOURCES file. Don?t change it. Don?t make up your own. To compile and link a driver, set your default directory in the appropriate DDK environment command prompt window to the directory at which you wish to start the build. Then, all you need to do is type BUILD, followed by a carriage return. When BUILD is run, it potentially creates several output files. BUILD always creates the file BUILD.LOG in the current directory. This file lists the detailed output from the compiler and linker. If BUILD encounters any warnings during the build process, it places the warning messages in the file BUILD.WRN. If BUILD encounters any errors during the build process, it places the error messages in the file BUILD.ERR. If there are no warnings and no errors, neither of these files is created. When you run BUILD, you?ll know that things worked correctly if BUILD tells you the appropriate number of executables was created, and if BUILD.ERR and BUILD.WRN do not exist (indicating that there were no errors and no warnings, respectively). The executable driver image is placed in the location indicated in the sources file. BUILD also tells you where it put your executable (look carefully? it is there!). BUILD usually will put your driver executable in the \ddk\lib directory. But it?s not as simple as you might think (things in NT driver writing rarely are, right?). BUILD automatically distributes executables in a hierarchy of sub-directories based on target process architecture and the free or checked status of the driver being built Thus, the executable image for a driver will be placed in targetpath\arch\status, where targetpath is the directory specified in the TARGETPATH= macro in the SOURCES file, arch is either i386 for x86 architecture systems or Alpha for Alpha systems, and status is free if building a free build of the driver, and checked if a checked version of the driver is being built. This may sound confusing, but an example or two should clear things up. Assume TARGETPATH= in the sources file specifies$(BASEDIR)\lib, as do most of the examples in the DDK, and that the DDK was installed into a directory named \ddk. Then, if a free x86 architecture driver is built, the executable will be placed in the \ddk\lib\i386\free directory. If a checked x86 architecture driver is built, the executable will wind up in the directory \ddk\lib\i386\checked. If a free build of driver is created for an Alpha architecture system, the driver executable will be located in \ddk\lib\alpha\free. If a checked version of an Alpha driver is created it will be located in \ddk\lib\alpha\checked. The default file type for a Windows NT driver is .SYS.

Well, that?s all for now, folks. In the next part of this article, we?ll discuss some driver debugging hints and tips. Hey, you experienced souls out there can help: Why not send us your favorite driver debugging hints and tips at ntinsider@osr.com. We?d love to hear from you. If you send a really cool tip, and include your hardcopy address, we?ll probably even send you the latest color OSR polo shirt (if Dan hasn?t already given them all to his fiancee, that is), or a highly in-demand OSR mousepad (made from 3M Precise Mousing Surface, no less!). So, send in those ideas. We?ll be sleeping, I mean, waiting!