Debugging a Device Driver

The most important thing to remember when testing new device drivers is to maintain adequate backups and a viable fallback position. Don't modify the CONFIG.SYS file and install the new driver on your fixed disk before it is proven! Be prudent——create a bootable floppy disk and put the modified CONFIG.SYS file and the new driver on that for debugging. When everything is working properly, copy the finished product to its permanent storage medium.

The easiest way to test a new device driver is to write a simple assembly-language front-end routine that sets up a simulated request packet and then performs FAR CALLs to the strat and intr entry points, exactly as MS-DOS would. You can then link the driver and the front end together into a .COM or .EXE file that can be run under the control of CodeView or another debugger. This arrangement makes it easy to trace each of the command-code routines individually, to observe the results of the I/O, and to examine the status codes returned in the request header.

Tracing the installed driver when it is linked into the MS-DOS system in the normal manner is more difficult. Breakpoints must be chosen carefully, to yield the maximum possible information per debugging run. Because current versions of MS-DOS maintain only one request header internally, the request header that was being used by the driver you are tracing will be overwritten as soon as your debugger makes an output request to display information. You will find it helpful to add a routine to your initialization subroutine that displays the driver's load address on the console when you boot MS-DOS; you can then use this address to inspect the device-driver header and set breakpoints within the body of the driver.

Debugging a device driver can also be somewhat sticky when interrupt handling is involved, especially if the device uses the same interrupt-request priority level (IRQ level) as other peripherals in the system. Cautious, conservative programming is needed to avoid unexpected and unreproducible interactions with other device drivers and interrupt handlers. If possible, prove out the basic logic of the driver using polled I/O, rather than interrupt-driven I/O, and introduce interrupt handling only when you know the rest of the driver's logic to be solid.

Typical device-driver errors or problems that can cause system crashes or strange system behavior include the following:

Failure to set the linkage address of the last driver in a file to -1

Overflow of the MS-DOS stack by driver-initialization code, corrupting the memory image of MS-DOS (can lead to unpredictable behavior during boot; remedy is to use a local stack)

Incorrect break-address reporting by the initialization routine (can lead to a system crash if the next driver loaded overwrites vital parts of the driver)

Improper BPBs supplied by the build BPB routine, or incorrect BPB pointer array supplied by the initialization routine (can lead to many confusing problems, ranging from out-of-memory errors to system boot failure)

Incorrect reporting of the number of bytes or sectors successfully transferred at the time an I/O error occurs (can manifest itself as a system crash after you enter R to the Abort, Retry, Ignore? prompt)

Although the interface between the DOS kernel and the device driver is fairly simple, it is also quite strict. The command-code routines must perform exactly as they are defined, or the system will behave erratically. Even a very subtle discrepancy in the action of a command-code routine can have unexpectedly large global effects.