WinDbg

WinDbg

WinDbg is both a kernel- and user-mode debugger. It is pronounced Windbag, Win"d-b-g," or, more intuitively, WinDebug. For many developers, WinDbg is the center of the advanced debugging universe. It has been available for some time and has evolved to encompass an impressive array of commands. Some of these commands are admittedly bewildering but always interesting. I have taught the .NET Advanced Debugging Workshop at Microsoft for years, which includes coverage of WinDbg. I still learn something new and amazing about WinDbg almost every month.

The focus of this book is C# and managed code. This is not the ideal place to troll the depths of WinDbg. It would be fun, but probably not entirely relevant. However, some basic WinDbg commands are helpful even when debugging managed applications. Although WinDbg offers a basic user interface, most developers operate from the command line. For this reason, we will use the user interface in a limited manner.

WinDbg Basic Commands

When WinDbg is launched, the debugger can be attached to an application using commandline arguments. This requires the process identifier of the debuggee. Tlist is a utility installed with Debugging Tools for Windows to lists the process identifier of active processes.

The following is sample output from the Tlist utility. Applications are listed in execution sequence. The first column is the process identifier, the second column is the program, and the final column contains a description, if available.

C:\store>tlist
   0 System Process
   4 System
 784 smss.exe
 844 csrss.exe
 872 winlogon.exe
 916 services.exe
 928 lsass.exe
 736 KHALMNPR.exe     KHALHPP_MainWindow
1052 gcasDtServ.exe    GIANT AntiSpyware Data Service
1444 DVDRAMSV.exe
1488 inetinfo.exe
1524 mdm.exe
1748 sqlservr.exe
1828 nvsvc32.exe       NVSVCPMMWindowClass
1440 wscntfy.exe
2380 iPodService.exe
2620 alg.exe
3384 iTunes.exe        iTunes
3648 WINWORD.EXE       MarshallChap14_0830 - Microsoft Word
2892 cmd.exe           Visual Studio .NET Whidbey Command Prompt - tlist
2572 Store.exe         Store
2696 tlist.exe

The -p argument attaches WinDbg to an application based on the process identifier:

windbg -p 2572

You can also attach to a running process simply with the application name:

windbg -pn store.exe

Alternatively, WinDbg can be started without being attached to anything. You can later attach to an application using the File menu of the WinDbg user interface. From the File menu, select the Attach to a Process command. Choose the debuggee from the list of available processes. The Open Executable command on the same menu starts an application and immediately attaches the debugger. This is convenient if the application is not already running.

When using WinDbg, some essential commands are helpful. Most of the WinDbg commands are case insensitive, but there are exceptions. Table 13-2 lists the basic commands.

Table 13-2: Basic WinDbg Commands

Command

Description

g(o)

This command resumes execution of the debugged application.

Ctrl+Break

This keystroke interrupts the running application.

q(uit)

This command quits the debugger.

?

This command displays help documentation.

Now that the basic commands have been presented, we can discuss the more interesting commands.

Displaying the active threads and changing thread context are frequent requests for any debugger. In WinDbg, the tilde (~) command is for thread manipulation.

  • The ~ command without parameters lists the threads. Thread identifier, status, and address of the thread environment block include some of the information reported. If the thread context is elsewhere, the current thread is highlighted with the pound (#) prefix.

  • The ~n command displays information on the specified thread, where n is the thread number. Thread priority, status, priority class, and other thread-relevant information is presented.

  • The ~ns command changes the thread context to the thread that is indicated. The WinDbg prompt is updated to reflect the new thread context. When the context is changed, the context record of the new thread is displayed, which includes the register values.

The following demonstration lists the threads, displays information pertaining to Thread 2, and then selects Thread 3 as the current thread:

0:001> ~
   0  Id: a1c.9c0 Suspend: 1 Teb: 7ffde000 Unfrozen
. 1  Id: a1c.804 Suspend: 1 Teb: 7ffdd000 Unfrozen
   2  Id: a1c.ea0 Suspend: 1 Teb: 7ffdc000 Unfrozen
   3  Id: a1c.de0 Suspend: 1 Teb: 7ffdb000 Unfrozen
#  4  Id: a1c.c04 Suspend: 1 Teb: 7ffda000 Unfrozen
0:001> ~2
   2  Id: a1c.ea0 Suspend: 1 Teb: 7ffdc000 Unfrozen
      Start: mscorwks!Thread::intermediateThreadProc (79ee80cf)
      Priority: 2  Priority class: 32
0:001> ~3s
eax=4ec62ef0 ebx=0103fe7c ecx=0000d9c7 edx=7c90eb94 esi=00000000 edi=7ffdf000
eip=7c90eb94 esp=0103fe54 ebp=0103fef0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
7c90eb94 c3               ret

Stack Trace Commands

Stack traces are invaluable when debugging. Even for managed code, viewing the unmanaged call stack can be informative. As demonstrated later in this chapter, many of the answers and hints to abnormal conditions in managed code are found in the unmanaged world.

In WinDbg, variations of the k command present varied permutations of a stack trace. Optionally, you can follow a stack trace command with a number, which indicates the depth of the call stack.

Table 13-3 explains the common stack trace commands.

Table 13-3: Stack Trace Commands

Command

Description

k

This command lists the methods on the call stack. In addition, the child frame pointer and the return address of the calling method are listed in columns. No parameters are displayed.

kb

This command lists the call stack and the first three parameters of the methods.

kp

This command lists the call stack and the entire parameter list of the methods.

kn

This command lists the call stack with the frame number for each method. You can use the frame information to move between frames on the call stack using the frame directive.

The following is the call stack from a thread in the Store application. Three parameters are displayed because the kb command is used.

0:003> kb
ChildEBP RetAddr Args to Child
0103fe50 7c90e9ab 7c8094f2 00000002 0103fe7c ntdll!KiFastSystemCallRet
0103fe54 7c8094f2 00000002 0103fe7c 00000001 ntdll!ZwWaitForMultipleObjects+0xc
0103fef0 77d495f9 00000002 0103ff18 00000000 KERNEL32!WaitForMultipleObjectsEx+0x12c
0103ff4c 77d496a8 00000001 0103ffac ffffffff USER32!RealMsgWaitForMultipleObjectsEx+0x13e
0103ff68 4ec95846 00000001 0103ffac 00000000 USER32!MsgWaitForMultipleObjects+0x1f
0103ffb4 7c80b50b 00000000 00000000 0012e0d0 gdiplus!BackgroundThreadProc+0x59
0103ffec 00000000 4ec957ed 00000000 00000000 KERNEL32!BaseThreadStart+0x37

What if you want the stack trace of every thread? Each thread could be selected and a k command submitted. However, that approach becomes tedious if there are dozens of threads. The solution is '~*'. Commands prefixed with '~*' are applied to all threads of the application. This command performs a stack trace on every thread:

~* k

Display Memory Commands

Displaying memory is another important tool of debugging. The d command is the fundamental display memory command. There is a bounty of variants to the d command for dumping different types and quantities of memory. Display commands list rows and columns of data. Rows begin with a memory address. This is the starting address for the contiguous bytes displayed on that row. The memory is organized in row order. By default, bytes are organized in two-byte columns. Certain display commands add byte translation at the end of every row. This is a typical display of memory:

0:003> d 0103fe50
0103fe50  cc 33 66 00 ab e9 90 7c-f2 94 80 7c 02 00 00 00  .3f....|...|....
0103fe60  7c fe 03 01 01 00 00 00-00 00 00 00 00 00 00 00  |...............

0103fe70  00 00 00 00 02 00 00 00-00 00 00 00 94 06 00 00  ................
0103fe80  8c 06 00 00 cc 99 99 00-cc 99 cc 00 cc 99 ff 00  ................
0103fe90  cc cc 00 00 cc cc 33 00-cc cc 66 00 14 00 00 00  ......3...f.....
0103fea0  01 00 00 00 00 00 00 00-00 00 00 00 10 00 00 00  ................
0103feb0  cc ff 66 00 cc ff 99 00-cc ff cc 00 00 b0 fd 7f  ..f.............
0103fec0  00 c0 fd 7f ff 00 33 00-00 00 00 00 7c fe 03 01  ......3.....|...

This is the syntax of a display command:

  • dL address1 address2

The second letter (L) of the display command varies based on the actual display command, such as dc, dd, and du. The second letter is also case sensitive. address1 is the beginning address, whereas address2 is the ending address. Memory is displayed from address1 to address2. Omit the ending address, and a default number of bytes are displayed. If neither the beginning nor the ending memory address is provided, the command displays from the current address forward.

Table 13-4 lists some of the commands that display memory.

Table 13-4: Common Display Memory Commands

Command

Description

d

This command repeats the previous display command and defaults to the db command.

da

This command displays the ASCII interpretation of the memory.

dc

This command displays the data in four-byte columns.

dd

This command is the same as dc, except the byte translation is omitted at the end of each row.

du

This command displays the Unicode interpretation of the memory.

The following command has four-byte columns of memory, three columns per row, and a starting address. The /c option controls the number of columns displayed:

0:003> dd /c 3 0103fe50
0103fe50  006633cc 7c90e9ab 7c8094f2
0103fe5c  00000002 0103fe7c 00000001
0103fe68  00000000 00000000 00000000
0103fe74  00000002 00000000 00000694
0103fe80  0000068c 009999cc 00cc99cc
0103fe8c  00ff99cc 0000cccc 0033cccc
0103fe98  0066cccc 00000014 00000001
0103fea4  00000000 00000000 00000010
0103feb0  0066ffcc 0099ffcc 00ccffcc
0103febc  7ffdb000 7ffdc000 003300ff
0103fec8  00000000 0103fe7c

Breakpoints Memory Commands

Debugging requires being a detective, and breakpoints are important investigative tools. Breakpoints interrupt a program based on certain conditions, such as memory address, location, or event. Setting effective breakpoints is an art, which is obtained with experience.

Naturally, WinDbg offers several breakpoint commands, as detailed in Table 13-5.

Table 13-5: Common Display Memory Commands

Command

Description

bp

This is the standard breakpoint command. The program is interrupted when execution reaches the specified location.

This is an abbreviated syntax for the bp command:

  • bp location options

The /1 option is useful for defining one-off breakpoints. This type of breakpoint is automatically removed after being hit. Therefore, the breakpoint is reached only once.

ba

This command is break on access to a memory address.

This is the abbreviated syntax for the ba command:

  • ba options size address

options specifies the action to break on:

  • e — execute

  • r — read/write

  • w — write

  • i - input/output

size is the width of the memory address.

bc

This command clears a breakpoint. You can clear multiple breakpoints in a space- or comma-delimited list. Alternatively, specify a range with a hyphen.

bl

This command lists the available breakpoints. Multiple breakpoints can be specified as described for the bc command.

The following is a demonstration of the breakpoint command. The sxe command is the set exception command. This command requests that the debugger interrupt on an exception or other event. In this example, the sxe command asks the debugger to interrupt when the mscorwks module is loaded, which is where the Common Language Runtime (CLR) is found. For a compound statement, use a semicolon. In the following command, the sxe and g commands are combined into a compound statement.

The following command breaks when the mscorwks module is loaded (the application is then resumed):

0:000> sxe ld mscorwks;g
ModLoad: 77dd0000 77e6b000   C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e70000 77f01000   C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 77f60000 77fd6000   C:\WINDOWS\system32\SHLWAPI.dll
ModLoad: 77f10000 77f56000   C:\WINDOWS\system32\GDI32.dll
ModLoad: 77d40000 77dd0000   C:\WINDOWS\system32\USER32.dll
ModLoad: 77c10000 77c68000   C:\WINDOWS\system32\msvcrt.dll
ModLoad: 76390000 763ad000   C:\WINDOWS\system32\IMM32.DLL
ModLoad: 629c0000 629c9000   C:\WINDOWS\system32\LPK.DLL
ModLoad: 74d90000 74dfb000   C:\WINDOWS\system32\USP10.dll
ModLoad: 79e70000 7a3cf000   C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll

eax=00000000 ebx=00000000 ecx=009f0000 edx=7c90eb94 esi=00000000 edi=00000000
eip=7c90eb94 esp=0012f1c0 ebp=0012f2b4 iopl=0         nv up ei ng nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000296
ntdll!KiFastSystemCallRet:
7c90eb94 c3               ret

The following command sets a breakpoint on the ExecuteMainMethod method:

0:000> bp mscorwks!SystemDomain::ExecuteMainMethod;g
ModLoad: 78130000 781ca000   C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSVCR80.dll
ModLoad: 7c9c0000 7d1d4000   C:\WINDOWS\system32\shell32.dll
ModLoad: 773d0000 774d2000   C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-
Controls_6595b64144ccf1df_6.0.2600.2180_x-ww_a84f1ff9\comctl32.dll
ModLoad: 5d090000 5d127000   C:\WINDOWS\system32\comctl32.dll
ModLoad: 60340000 60348000   C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\culture.dll
ModLoad: 790c0000 79baa000   C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\cee6ddb
471db1c489d9b4c39549861b5\mscorlib.ni.dll
Breakpoint 0 hit
eax=0012ff38 ebx=00000002 ecx=00000000 edx=ffffffff esi=00000000 edi=00000000
eip=79efb428 esp=0012ff1c ebp=0012ff68 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mscorwks!SystemDomain::ExecuteMainMethod:
79efb428 55               push    ebp

After the breakpoint is hit, the kb command performs a stack trace. You see ExecuteMainMethod in the call stack:

0:000> kb
ChildEBP RetAddr  Args to Child
0012ff18 79efb3cb 00400000 00000000 b4ebfe93 mscorwks!SystemDomain::ExecuteMainMethod
0012ff68 79ef8bc8 00400000 b4ebfe4b 00080000 mscorwks!ExecuteEXE+0x59
0012ffb0 790122f6 00d9fa9c 79e70000 0012fff0 mscorwks!_CorExeMain+0x11b
0012ffc0 7c816d4f 00080000 00d9fa9c 7ffd8000 mscoree!_CorExeMain+0x2c
0012fff0 00000000 790122c2 00000000 78746341 KERNEL32!BaseProcessStart+0x23

Step Commands

After reaching a breakpoint, it is common to step through an application and evaluate the results. There are also plenty of other reasons and opportunities to step through an application. The best means of stepping through an application is by using the Debugging toolbar. (Choose the View menu and the Toolbar option to display the Debugging toolbar.)

Figure 13-6 shows the Step buttons on the Debugging toolbar: the Step In, Step Over, Step Out, and Run to Cursor buttons.

Image from book
Figure 13-6: Step buttons on the Debugging toolbar

WinDbg Directives WinDbg commands affect the application currently being debugged. The commands display, modify, or otherwise affect or inspect the debuggee. Conversely, WinDbg directives alter the debugging session. For example, the .load directive loads a debugging extension dynamic-link library (DLL). The .logopen directive opens a log file and echoes any subsequent activity to this file.

There are several WinDbg directions. Table 13-6 includes some of the available directives. Directives are prefixed with a dot (.).

Table 13-6: WinDbg Directives

Command

Description

.load dllname

This command loads a debugger extension DLL. Extension commands are exposed as the exported functions of the DLL. Developers of managed code routinely load the SOS (SOS.dll) debugger extension. Commands from debugger extensions are prefixed with an exclamation point (!).

.unload dllname

This command unloads a debugger extension.

.chain

This command lists the debugger extensions that are presently available.

.reload

This command reloads symbols and is usually requested after the symbol path has been updated. Normally, symbols are retrieved as needed. The /f option forces the immediate load of all symbols.

.logopen filename

This command opens a log file and echoes any subsequent activity to this file.

.logclose

This command closes the log file.

.kill

This command terminates the current debuggee and ends the debugging session.

.frame n

This command shifts the stack to the designated frame number or displays the current local context.

.srcpath

This command sets or displays the source code path.

.dump options filename

This command creates a user- or kernel-mode dump, which is used for postmortem analysis.

.dump filename creates a minidump.

The following command creates a minidump with full memory and handle information:

  • .dump /mfh filename

This command creates a full dump. The full dump command is available only in kernel-mode operations of WinDbg (which is not discussed in this chapter):

  • .dump /f