Many Windows developers are unaware that Windows ships with its own built-in debuggerthe Microsoft NT Symbolic Debugger (ntsd). In this article, I describe how to use ntsd to debug a few straightforward problems. I also describe the Microsoft Application Verifier (AppVerif) tool and present some examples that illustrate both a strength and limitation of AppVerif when finding buffer overruns on the heap.
The ntsd command-line debugger is not as pretty as Visual Studio's integrated debugger. Despite this (or perhaps because of this), ntsd.exe and its cousins are arguably the debuggers of choice for developers at Microsoft who build the core of the Windows operating system
Although ntsd has historically shipped in-box with Windows NT right up through Windows XP, Microsoft is continually improving it. Consequently, I recommend downloading the most recent version of the Microsoft Debugging Tools for Windows package (www.microsoft.com/whdc/devtools/debugging), which includes ntsd, the Windows Debugger WinDbg, the Kernel Debugger KD, an SDK for writing debugger extensions, and the debugger.chm help file.
Example
Here's an example that illustrates how to use ntsd to debug a typical application crash. I use the word "crash" to refer to any situation where the application was terminated abnormally by the operating system. The most common cause of application crashes is where the application attempts to read from or write to an invalid memory location. This is called an "access violation" (AV). For example, the application may attempt to dereference a NULL pointer. Example 1 is designed to do just this.
void main(void) { char *p = 0; *p = 123; }
When I run this program (t1.exe), Windows terminates it at the point of the access violation and issues the expected message that there was a problem with my program. To debug this, I run ntsd.exe from the command line, passing the name of my application as an argument; for example, ntsd.exe -g t1.exe. With the -g option, ntsd.exe loads and immediately runs the application. Without the -g option, ntsd.exe loads the application, then immediately breaks before the application runs, requiring the g command to let the application continue. When the AV occurs, ntsd.exe breaks in and presents me with a debugger command window like Example 2.
Microsoft (R) Windows Debugger Version 6.6.0007.5 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: t1.exe Symbol search path is: SRV*c:\Files\websymbols*http://msdl.microsoft.com/downloa d/symbols Executable search path is: ModLoad: 00400000 0040f000 t1.exe ModLoad: 7c900000 7c9b0000 ntdll.dll ModLoad: 7c800000 7c8f4000 C:\WINDOWS\system32\kernel32.dll (ce4.ddc): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000000 ebx=7ffdb000 ecx=00320758 edx=00320000 esi=7c9118f1 edi=00011970 eip=0040101e esp=0012ff7c ebp=0012ff80 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206 *** WARNING: Unable to verify checksum for t1.exe t1!main+0xe: 0040101e c6007b mov byte ptr [eax],7Bh ds:0023:00000000=?? 0:000>
The initial debugger output reveals useful information. After displaying the command line used to start the application, it shows the search path for finding symbol files (PDBs). The symbol search path is important and, in this case, I had specified it beforehand by setting the _NT_SYMBOL_PATH system variable before running ntsd.exe. The symbol path shown here is SRV*c:\Files\websymbols*http://msdl.microsoft.com/download/symbols, which identifies a symbol server URL and the location where downloaded symbol files may be cached. That is, when ntsd needs to load symbols for a binary, it connects to the symbol server at the specified URL, and requests PDBs based on unique characteristics of the binary such as the name of the binary and its timestamp. The URL here is for the Microsoft public symbol server, which provides public Windows symbols for many versions of Windows. Of course, I also need to make sure that ntsd can find the symbols for my application. If I wish to add, say, the location "C:\Files" to the symbol path, then I can use the debugger command .sympath+ C:\Files. The command .symfix+ adds the URL for the Microsoft public symbol server if is not on the path. After changing the symbol search path, I always use the .reload command to force the debugger to reload all symbols.
When the debugger broke in, it displayed the state of the CPU registers, and the instruction that was being executed. The line t1!main() indicates the name of the executing module and the C function that was being executed, while mov byte ptr [eax], 7Bh indicates the faulting assembler instruction. The eax register currently contains a value of zero, so the program triggered an access violation by attempting to write into memory address zero.
So far, the debugger has provided me with useful information about what went wrong before I have even issued any commands. Example 3 shows a few debugger commands typically used after a break. The first command, the Display Stack Backtrace command k, shows the call stack for the current thread. The Toggle Source Line Support command .lines causes the debugger to switch between showing line numbers and not showing them in the output of future commands. Issuing the k command again gives a stack, but this time it references the relevant source file and line number for the code for which it has private symbols. The call stack shows that the AV occurred near line 4 in source file t1.c. The Display Local Variables command dv shows the value of variable p as 0x00000000. The last command in Example 3 is the Display Type command dt p, which shows the address in which the local variable p is stored, and that p is of type char *. If a variable references a C struct data type or a C++ class, then the dt command also attempts to display the names and values for each field.
0:000> k ChildEBP RetAddr 0012ff80 004010de t1!main+0xe 0012ffc0 7c816fd7 t1!mainCRTStartup+0xb4 0012fff0 00000000 kernel32!BaseProcessStart+0x23 0:000> .lines Line number information will be loaded 0:000> k ChildEBP RetAddr 0012ff80 004010de t1!main+0xe [t1.c @ 4] 0012ffc0 7c816fd7 t1!mainCRTStartup+0xb4 0012fff0 00000000 kernel32!BaseProcessStart+0x23 0:000> dv p = 0x00000000 "" 0:000> dt p Local var @ 0x12ff7c Type char* (null)
Arguably the most useful feature of the ntsd debugger is the !analyze debugger extension. In general, this is one of the first commands I issue after a crash because it often does a lot of the initial debugging work for me. Example 4 (available at www.ddj.com/code/) shows the results of using !analyze -v where the -v option triggers verbose output. This command extension analyzes, summarizes, and displays the cause of the break. In this case, the debugger has access to full symbolic debugging information; therefore, !analyze also shows a snippet of the original source code, with an arrow pointing at the line where the break occurred.