Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)

In this section I want to help you get started debugging with WinDBG by covering some of the key commands you'll have to become familiar with to effectively debug with the Command window. My focus will be on how you can use these commands better and tricks for helping you solve debugging challenges; I won't rehash the existing documentation. I strongly suggest that you also read the documentation on these commands.

Looking at and Evaluating Variables

Peeking at the local variables is the domain of the DV (Display Local Variables) command. One thing that's a little confusing about using WinDBG is seeing local variables up the stack. It actually takes a couple of commands to do what clicking in the Call Stack window does automatically.

The first step is to use the K (Display Stack Backtrace) command with the N modifier to see the call stack with the frame numbers in the far left column of each stack entry. (By the way, my favorite stack display command is KP, which shows the stack and, for each entry, the values of parameters to the function.) The frame numbers are regular in that the top of the stack is always 0, the next item is 1, the next value down is 2, and so on. Those frame numbers are important because you need them to specify to the .FRAME (Set Local Context) command to move down the stack. Therefore, to view the local variables in the function that called the current function, you use the following command sequence. To move the context back to the top of the stack, simply issue a .frame 0 command.

.frame 1 dv

The DV command returns enough information to give you the basic gist of what's happening in the local variables. The following output is the result of the DV command when debugging the PDB2MAP.EXE program from Chapter 12.

cFuncFMT = CResString cIM = CImageHlp_Module szBaseName = Array [260] pMark = cccccccc dwBase = 0x400000 bEnumRet = 0xcccccccc argc = 2 argv = 00344e18 fileOutput = 00000000 szOutputName = Array [260] iRetValue = 0 bRet = 1 hFile = 000007c8 cRS = CResString

To see more, you need to use the DT (Display Type) command. The DT command is quite amazing in that it can do some very advanced tricks such as walking linked lists and grinding through arrays. Fortunately, you can pass a -? parameter to the DT command to quickly get some help while you're in the middle of the debugging wars.

One additional trick I'll mention about the DT command is that it can also search for symbol types. Instead of passing the name or address of a variable, you pass a parameter in the format module!type, where type either is a full type name or contains a wildcard to search for subexpressions. For example, to see any types that start with "IMAGE" in PDB2MAP, the command is this: dt pdb2map!IMAGE*. If you pass the full type, you'll see all the fields that make up the type if it's a class or structure, or the underlying base type if it's a typedef.

The last of the evaluation commands is ?? (Evaluate C++ Expression), which you'll use to check pointer arithmetic and to handle other C++ evaluation needs. Make sure you read the documentation about working with expressions because the process isn't as straightforward as you would expect. Now that you can view and evaluate all your variables, it's time to turn to executing, stepping, and stopping your program.

Executing, Stepping, and Tracing

As you've probably figured out by now, pressing F5 continues execution when you're stopped in WinDBG. You might not have noticed it, but pressing F5 simply causes a G (Go) command. What's neat about the G command is that you can also specify an address as the parameter. WinDBG uses that address as a one-time breakpoint, so you'll run to that location. Have you ever noticed that pressing Shift+F11, the Step Out command, executes the G command followed by an address (sometimes in the form of an expression)? That address is the return address at the top of the stack. You can do the same thing in the Command window, but instead of having to calculate the return address manually, you can use the $ra pseudoregister as the parameter to have WinDBG do the grunt work of finding the return address. There are other pseudoregisters, but you can't use them all in user mode. Search for "Pseudo-Register Syntax" in WinDBG help to find the rest of the pseudoregisters. To clarify, I want to mention that these WinDBG pseudoregisters are unique to WinDBG and aren't usable in Visual Studio .NET.

To handle tracing and stepping, use the T (Trace) and P (Step) commands, respectively. Just to remind you, tracing will step into any function calls encountered, whereas stepping will step over those function calls. One aspect that makes WinDBG different from Visual Studio .NET is that WinDBG doesn't automatically switch between stepping source code lines and assembly instructions just because you happen to have the focus at a Source and Disassembly window. By default, WinDBG steps source lines when lines are loaded for the current executing location. If you want to step by assembly instructions, either uncheck Source Mode on the Debug menu or use the .LINES (Toggle Source Line Support) command with the –d parameter.

As with the G command, T and P are what the F11 (also F8) and F10 keystrokes jam into the Command window. Also, you can pass either an address to step/trace to or, interestingly, the number of steps/traces to make. This comes in very handy because it's sometimes easier than setting a breakpoint. In essence, it's a manual "run-to-cursor" type command.

Two relatively new commands for stepping and tracing are the TC (Trace to Next Call) and PC (Step to Next Call) commands. The difference with these commands is that they step/trace up until the next CALL instruction. The only difference between TC and PC is that with the PC command, if the instruction pointer is sitting on a CALL instruction, the CALL will execute until it returns. A TC command will step into the CALL and stop on the next CALL. I find TC and PC useful when I want to move past any work the function does but not leave the function.

Trace and Watch Data

One of the biggest problems with tracking down performance issues is that some code is nearly impossible to read and see exactly what it does. For example, Standard Template Library (STL) code creates one of the largest performance problems we see when debugging other programmers' applications. Release builds jam in so many inline functions, and general STL code is nearly impossible to read, so analysis by reading isn't feasible. But because STL allocates so much memory behind the scenes and acquires various synchronization locks left and right, it's vital to have some way to see what a function that uses STL is really doing. Fortunately, WinDBG has an answer to this conundrum and it's one of the key differences between WinDBG and Visual Studio .NET: the WT (Trace and Watch Data) command. This command is so powerful that it's a key reason you need to take the time to learn WinDBG.

What the WT command does is show you in a hierarchical display every function a single function calls! At the end of the tracing, the WT command displays exactly which functions were called along with how many times each was called. Additionally—and this is extremely important for performance—the WT command shows you how many kernel mode transitions your code made. The key to great performance is to avoid kernel mode transitions if you can, so the fact that the WT command is one of the few ways you can see this information makes it doubly valuable.

As you can imagine, all of this tracing can generate tons of stuff in the Command window, which you probably want to save to a file. Fortunately, the WinDBG developers met the need for saving this information with a complete logging system. Opening a log file is as simple as passing the name of the log file you want as a parameter to the .LOGOPEN (Open Log File) command. Alternatively, you can append to an existing log file with .LOGAPPEND (Append Log File). When finished logging, call .LOGCLOSE (Close Log File).

Using WT effectively so that you get meaningful output without getting more than you can wade through takes a little bit of planning. WT traces until it hits the current return address. This means you must carefully position the instruction pointer on one of two places before you call WT. The first place is directly on the call instruction of the function you want to execute. You have to do this at the assembly-language level, so you'll need to either set a breakpoint on the call instruction directly or set the stepping to assembly mode and step to the call instruction. The second place is on the first instruction for the function. You can either step to the PUSH EBP or set a breakpoint on the open curly brace for the function in a Source window.

Before I jump into the parameter options for WT, I want to discuss the output. To keep things simple, I created a small program that has just a few functions that call themselves. It is WTExample included with this book's sample files. I set a breakpoint at the first instruction in wmain and issued a WT command to get the output on Windows XP SP1, as shown in Listing 8-1. (Note that I trimmed some spaces and wrapped some lines to get the listing to fit within the page width.)

Listing 8-1: WinDBG wt output

0:000> wt Tracing WTExample!wmain to return address 0040139c 3 0 [ 0] WTExample!wmain 3 0 [ 1] WTExample!Foo 3 0 [ 2] WTExample!Bar 3 0 [ 3] WTExample!Baz 3 0 [ 4] WTExample!Do 3 0 [ 5] WTExample!Re 3 0 [ 6] WTExample!Mi 3 0 [ 7] WTExample!Fa 3 0 [ 8] WTExample!So 3 0 [ 9] WTExample!La 3 0 [10] WTExample!Ti 6 0 [11] WTExample!Do2 3 0 [12] kernel32!Sleep 3 0 [13] kernel32!SleepEx 18 0 [14] kernel32!_SEH_prolog 15 18 [13] kernel32!SleepEx 16 0 [14] ntdll! RtlActivateActivationContextUnsafeFast 20 34 [13] kernel32!SleepEx 15 0 [14] kernel32!BaseFormatTimeOut 26 49 [13] kernel32!SleepEx 3 0 [14] ntdll!ZwDelayExecution 2 0 [15] SharedUserData! SystemCallStub 1 0 [14] ntdll!ZwDelayExecution 31 55 [13] kernel32!SleepEx 3 0 [14] kernel32!SleepEx 14 0 [15] ntdll! RtlDeactivateActivationContextUnsafeFast 4 14 [14] kernel32!SleepEx 36 73 [13] kernel32!SleepEx 9 0 [14] kernel32!_SEH_epilog 37 82 [13] kernel32!SleepEx 4 119 [12] kernel32!Sleep 8 123 [11] WTExample!Do2 2 0 [12] WTExample!_RTC_CheckEsp 11 125 [11] WTExample!Do2 2 0 [12] WTExample!_RTC_CheckEsp 13 127 [11] WTExample!Do2 5 140 [10] WTExample!Ti 2 0 [11] WTExample!_RTC_CheckEsp 7 142 [10] WTExample!Ti 5 149 [ 9] WTExample!La 2 0 [10] WTExample!_RTC_CheckEsp 7 151 [ 9] WTExample!La 5 158 [ 8] WTExample!So 2 0 [ 9] WTExample!_RTC_CheckEsp 7 160 [ 8] WTExample!So 5 167 [ 7] WTExample!Fa 2 0 [ 8] WTExample!_RTC_CheckEsp 7 169 [ 7] WTExample!Fa 5 176 [ 6] WTExample!Mi 2 0 [ 7] WTExample!_RTC_CheckEsp 7 178 [ 6] WTExample!Mi 5 185 [ 5] WTExample!Re 2 0 [ 6] WTExample!_RTC_CheckEsp 7 187 [ 5] WTExample!Re 5 194 [ 4] WTExample!Do 2 0 [ 5] WTExample!_RTC_CheckEsp 7 196 [ 4] WTExample!Do 5 203 [ 3] WTExample!Baz 2 0 [ 4] WTExample!_RTC_CheckEsp 7 205 [ 3] WTExample!Baz 5 212 [ 2] WTExample!Bar 2 0 [ 3] WTExample!_RTC_CheckEsp 7 214 [ 2] WTExample!Bar 5 221 [ 1] WTExample!Foo 2 0 [ 2] WTExample!_RTC_CheckEsp 7 223 [ 1] WTExample!Foo 6 230 [ 0] WTExample!wmain 2 0 [ 1] WTExample!_RTC_CheckEsp 8 232 [ 0] WTExample!wmain 240 instructions were executed in 239 events (0 from other threads) Function Name Invocations MinInst MaxInst AvgInst SharedUserData!SystemCallStub 1 2 2 2 WTExample!Bar 1 7 7 7 WTExample!Baz 1 7 7 7 WTExample!Do 1 7 7 7 WTExample!Do2 1 13 13 13 WTExample!Fa 1 7 7 7 WTExample!Foo 1 7 7 7 WTExample!La 1 7 7 7 WTExample!Mi 1 7 7 7 WTExample!Re 1 7 7 7 WTExample!So 1 7 7 7 WTExample!Ti 1 7 7 7 WTExample!_RTC_CheckEsp 13 2 2 2 WTExample!wmain 1 8 8 8 kernel32!BaseFormatTimeOut 1 15 15 15 kernel32!Sleep 1 4 4 4 kernel32!SleepEx 2 4 37 20 kernel32!_SEH_epilog 1 9 9 9 kernel32!_SEH_prolog 1 18 18 18 ntdll! RtlActivateActivationContextUnsafeFast 1 16 16 16 ntdll! RtlDeactivateActivationContextUnsafeFast 1 14 14 14 ntdll!ZwDelayExecution 2 1 3 2 1 system call was executed Calls System Call 1 ntdll!ZwDelayExecution

The beginning part of the output (displaying the hierarchical tree) is the call information. In front of each call, WinDBG displays three numbers. The first number is the assembly instruction count that the function executed before calling the next function. The second number is undocumented, but looks to be a running total of assembly-language instructions executed in the tracing on returns. The final number in brackets is the current nesting level for the hierarchical tree.

The second portion of the output is a summary display, which is a little more understandable. In addition to providing a summary of each function called, it shows the function call count as well as the minimum number of assembly-language instructions called in an invocation, the maximum number of assembly-language instructions called in an invocation, and the average number of instructions called. The final lines of the summary display show how many system calls occurred. You can see that WTExample eventually calls Sleep to force a transition to kernel mode. The fact that you can get the number of kernel-mode transitions is extremely cool.

As you can imagine, using the WT command can produce a huge amount of output and can really slow down your application since each line of output requires a couple of cross process transitions between the debugger and debuggee to get the information. If you want to see the all-important summary information, passing –nc as a parameter to WT will suppress the hierarchy. Of course, if you're interested in just the hierarchy, pass –ns as the parameter. To see the return value register (EAX in x86 assembly language), use the –or parameter; and to see the address, source, and line information (if available) for each call, use the –oa parameter. The final parameter is –l, which allows you to set the maximum depth of calls to display. Using –l can be helpful when you want to see the high points of what's executed or keep the display to just the functions in your program.

I strongly encourage you to look at key loops and operations in your own programs with the WT command so that you can truly see what's happening under the covers. I don't know how many performance problems and language and technology misuse problems I've been able to track down by being able to see what's actually executing. The WT command is one of the best tools in my debugging toolbox.

Common Debugging Question: Some of my C++ names are gigantic. Is there any way I can get WinDBG to help so that I don't get carpal tunnel syndrome from typing them each time?

Fortunately, WinDBG now supports text aliasing. Use the AS (Set Alias) command to define a user named alias and an expansion equivalent. For example, you can use as LL kernel32!LoadLibraryW to set the string "LL" to expand to kernel32!LoadLibraryW whenever you enter it on the command line. You can see any aliases you have defined with AL (List Aliases) as well as delete existing aliases with AD (Delete Alias).

Another place you can define what the documentation refers to as fixed-name aliases is, strangely enough, the R (Registers) command. The fixed-name aliases are $u0, $u1, …, $u9. Although user-named aliases are a little easier to remember, fixed-name aliases expand even when no white space is around the name. To define a fixed-name alias, you must put a period in front of the u: r $.u0=kernel32!LoadLibraryA. The only way to see what a fixed-name alias is defined as is to use the .ECHO (Echo Comment) command: .echo $u0.

Breakpoints

WinDBG offers all the same breakpoints as Visual Studio .NET plus some that are unique to WinDBG. What's important is that WinDBG offers much more power and complete fine-tuning of exactly when the breakpoints trigger and what occurs after a breakpoint triggers. Older versions of WinDBG had a very nice dialog box that made setting advanced breakpoints very easy. Unfortunately, that dialog box is not in the rewritten version of WinDBG we have now, so we must do all the breakpoint setting manually.

General Breakpoints

The first breakpoint concept I want to address concerns the two commands that you use to set breakpoints: BP and BU. Both commands take identical parameters and modifiers. You can think of the BP version as a hard breakpoint that WinDBG always associates with an address. If the module containing that breakpoint is unloaded, WinDBG removes the BP breakpoint from the list of breakpoints. BU breakpoints, on the other hand, are associated with a symbol, so WinDBG tracks on the symbol instead of an address. If the symbol moves, the BU breakpoint moves. That means a BU breakpoint will remain active but disabled when the module unloads from the process, but will immediately reactivate when the module comes back into the process, even if the operating system has to relocate the module. A big difference between BP breakpoints and BU breakpoints is that WinDBG saves BU breakpoints in WinDBG workspaces, but BP breakpoints aren't saved there. Finally, when setting breakpoints in the Source code window using F9, WinDBG sets BP breakpoints. My recommendation is to use BU breakpoints instead of BP breakpoints.

There is a limited Breakpoints dialog box (click the Edit menu and then Breakpoints), but I prefer to manipulate breakpoints from the Command window because I find it easier. The BL (Breakpoint List) command allows you to see all the breakpoints that are currently active. You can read the documentation on the output of BL, but I want to point out that the first field is the WinDBG breakpoint number and the second field is a letter that indicates the breakpoint status: d (disabled), e (enabled), and u (unresolved). You can enable and disable breakpoints with the BE (Breakpoint Enable) and BD (Breakpoint Disable) commands, respectively. Passing an asterisk (*) to each of these commands will apply the enable or disable to all breakpoints. Finally, you can enable and disable specific breakpoints by number using the BE and BD commands.

The syntax for setting an x86 user-mode breakpoint is this:

[~Thread] bu[ID] [Address [Passes]] ["CommandString"]

If you type BU by itself, WinDBG sets a breakpoint on the current instruction pointer. The thread modifier is simply the WinDBG thread number, which makes setting per-thread breakpoints trivial. If you'd like to set the WinDBG breakpoint ID, follow BU by the number of the breakpoint. If a breakpoint with that number exists, WinDBG will replace the existing breakpoint with the new one you're setting. The address field is any valid WinDBG address expressed in the address syntax I described at the beginning of the "Debugging Situations" section. In the passes field, you indicate how many times you'd like this breakpoint skipped before stopping. The passes field is a greater than or equal comparison, and the maximum value is 4,294,967,295. As with Visual Studio .NET native debugging, passes are decremented only if running full speed past the breakpoint, not stepping or tracing.

The final field you can set with breakpoints is the amazing, magical command string. That's right, you can associate commands with a breakpoint! This opens up a world of excellent bugslaying techniques that you can add to your arsenal. Probably the best way to demonstrate this awesome power is with a story about how I used this technique to solve an almost impossible bug. I had a situation in which the bug manifested itself only after a long series of data cases went through a specific section of code. As usual, it took quite a while for the right conditions to hit, so there was no way I could simply spend a day or week looking at the variable states each time I hit the breakpoint. (Unfortunately, I wasn't paid by the hour for this job!) I needed a way to log all the variable values so that I could inspect the data flow through the system. Since you can concatenate multiple commands with a semicolon, I built up a huge command that logged out all the variables using DT and ??. I also sprinkled in a few .ECHO commands so that I could see where I was and have a common string that I could look for each time the breakpoint triggered. I finished off the command string with a ";G", so the breakpoint continued executing after dumping the variable values. Of course, I turned on logging and just let the process run until it crashed. After looking over the log, I immediately saw the pattern and quickly fixed the bug. If it weren't for WinDBG's excellent breakpoint extensibility, I would have never found this bug!

One command in particular, J (Execute If – Else), is particularly well suited for use in a breakpoint command string. This command gives you the ability to conditionally execute commands based on a particular condition. In other words, J gives you a conditional breakpoint capability in WinDBG. The format of the J command is this:

j expression 'if true command' ; 'if false command'

The expression is any expression that the WinDBG expression evaluators can handle. The values in single quotation marks indicate the command strings for true or false evaluation. Make sure you always use the single quotation marks around the command strings because you can embed semicolons to do big operations on values. It's also perfectly valid to include sub J commands inside the true and false command strings. One thing that isn't clear in the documentation is what to do when you want to leave one of the true or false conditions empty (i.e., you don't want to execute any commands for that condition): you simply enter two single quotation marks side by side for the omitted condition.

Memory Access Breakpoints

In conjunction with the excellent execution breakpoints, WinDBG also has the phenomenal BA (Break On Access) command, which allows you to stop when any piece of memory is read or written in your process. Visual Studio .NET offers only memory change breakpoints, and you have to use Mike Morearty's hardware breakpoint class to have access to all the power supplied by the Intel x86 hardware breakpoints. However, WinDBG has all the power built right in!

The format for Intel x86 user-mode memory breakpoints is as follows:

[~Thread] ba[ID] Access Size [Address [Passes]] ["CommandString"]

As you can see from the format, the BA command offers quite a bit more power than simply stopping on a memory access. As you can with the BP and BU commands, you can specify to stop only when specific threads are doing the touching, set a pass count, and associate that wonderful command string with any accesses. The access field is a single character field that indicates whether you want to stop on read/write (r), write (w), or execute (e). Since the x86 CPU family doesn't have an option to set memory as execute only, specifying execute will simply set a BP style breakpoint. The size field indicates how many bytes you want to watch. Since the BA command uses the Intel Debug Registers to do their magic, you're limited to watching 1, 2, or 4 bytes at a time, and you're limited to four BA breakpoints. Like when setting Data breakpoints in Visual Studio .NET, you need to keep in mind the memory alignment issues, so if you want to watch 4 bytes, the memory address must end in 0, 4, 8, or C. The address field is the address on which you want to break on access. While WinDBG is a little more forgiving about using variables, I still much prefer to use the actual hexadecimal addresses to ensure the breakpoint is set on the exact place I want it.

If you'd like to see the BA command in action, you can use the MemTouch program included with this book's sample files for experimenting. The program simply allocates a local piece of memory, szMem, which it passes to one function that touches the memory and another function that reads the memory. You'll have to set the BA breakpoint on the address of szMem in order to locate the break. To get the address of a local variable, use the DV command. Once you've got the address, you can use that value with the BA command. To learn what you can do with commands, you might want to use the command string "kp;g" so that you can see the stack at the time of the access and then continue execution.

Exceptions and Events

WinDBG's breakpoints offer a tremendous amount of power, and the ability to finely control the debuggee doesn't stop there. WinDBG offers advanced handling of exceptions and events as well. The exceptions are all the hard exceptions like access violations that cause your programs to crash. The events are for the standard events passed to debuggers by the Microsoft Win32 Debugging API. This means that, for example, you can have WinDBG break whenever a module is loaded so that you can gain control before the entry point for that module executes.

There are commands for manipulating the exceptions and events from the Command window, SX, SXD, SXE, SXI, SXN (Set Exceptions), but they are quite confusing. Fortunately, WinDBG offers a nice dialog box to make manipulating them easier. The Event Filters dialog box is accessible from the Debug, Event Filters menu and is shown in Figure 8-5.

Figure 8-5: The Event Filters dialog box

However, even with a dialog box to help you, it's still a little confusing to figure out what happens with an exception because WinDBG uses some odd terminology in the SX* commands and the Event Filters dialog box. The Execution group box in the lower right-hand corner of the dialog box indicates how you want WinDBG to handle the exception. Table 8-2 explains the meanings of the values in the Exception group box. When reading over the table or looking at the defaults in the Event Filter dialog box, keep in mind the discussion of the ContinueDebugEvent API back in Chapter 4, because the Exceptions group is indicating what you want WinDBG to pass to that API.

Table 8-2: Exception Break Status

Status

Description

Enabled

When the exception occurs, execution occurs and the target will break into the debugger.

Disabled

The first time the exception occurs, the debugger will ignore it. The second time it occurs, execution will halt and the target will break into the debugger.

Output

When the exception occurs, it won't break into the debugger. However, a message informing the user of this exception will be displayed.

Ignore

When the exception occurs, the debugger will ignore it. No message will be displayed.

You can ignore the Continue group in the lower right-hand corner. It's only important when you want different handling on breakpoint, single step, and invalid handle exceptions. If you add your own structured exception handling (SEH) errors to the list, leave the Continue option at the default, Not Handled. That way any time the exception comes through WinDBG, WinDBG will properly pass the exception directly back to the debuggee. You don't want the debugger eating exceptions other than those it caused, such as a breakpoint or a single step.

After selecting a particular exception, the most important button on the dialog box is the Commands button. The name alone should give you a hint about what it does. Clicking on the Commands button brings up the Filter Command dialog box shown in Figure 8-6. The first edit control is misnamed and should be labeled First-Chance Exception.

Figure 8-6: Filter Command dialog box

In the Filter Command dialog box, you can enter WinDBG commands to execute when the debuggee generates a particular exception. When I discussed using the Visual Studio .NET Exception dialog box in the "Exception Monitoring" section of Chapter 7, I showed how you should set C++ exceptions to stop on the first chance exception so that you could monitor where your programs did the throws, and after pressing F10, the catch. The problem is that Visual Studio .NET stops each time a C++ exception occurs, so you have to sit there pressing F5 over and over when your application has numerous C++ throws.

What's great about WinDBG and the ability to associate commands with the exceptions is that you can use a command to log out all the important information and, most usefully, continue execution so that you don't have to monitor the run. To set up C++ exception handling, select C++ EH Exception from the list of exceptions in the Event Filter dialog box and click the Commands button. In the Filter Command dialog box, enter kp;g in the Command edit box to have WinDBG log a stack walk and continue execution. Now you'll have a call stack each time a throw occurs, and WinDBG will keep right on executing. By the way, to see the last event or exception that occurred in a process, use the .LASTEVENT (Display Last Event) command.

Controlling WinDBG

Now that you've seen the important commands for debugging, I want to turn to a few meta commands that I haven't already covered. You can use these to control or make better use of WinDBG while debugging. Those I discuss are by no means a complete list of all the commands but rather a list of cool meta commands I use on a daily basis when debugging with WinDBG.

The simplest but extremely useful command is .CLS (Clear Screen). This allows you to clear the Command window so that you can start fresh. Since WinDBG can spew a tremendous amount of information, which takes memory to store, it's good to clean the slate occasionally.

If you're dealing with Unicode strings in your application, you'll want to set the display to show USHORT pointers as Unicode strings. The .ENABLE_UNICODE (Enable Unicode Display) command issued with a parameter of 1 will set everything up so that the DT command displays your strings correctly. If you'd like to set the locale so that Unicode strings display correctly, the .LOCALE (Set Locale) command takes the locale as a parameter. If you're dealing with bit manipulation and want to see the bit values, the .FORMATS (Show Number Formats) command will display the value passed as a parameter in all number formats, including binary.

Another extremely useful command is .SHELL (Command Shell), which allows you to start up an MS-DOS window from the debugger and redirect output to the Command window. Debugging on the same machine the debuggee is running on and pressing Alt+Tab might be an easier approach, but the beauty of .SHELL is that when doing remote debugging, the MS-DOS window runs on the remote machine. You can also use the .SHELL command to run a single external program, redirecting output, and return to the Command window. After issuing a .SHELL command, the Command window input line says INPUT>, indicating that the MS-DOS window is waiting for input. To end the MS-DOS window and return to the Command window, use either the MS-DOS exit command or, more preferably, the .SHELL_QUIT (Quit Command Prompt) command because it will terminate the MS-DOS window even when the window is frozen.

The final meta command I'll mention is one I've wanted in a debugger for years but has only now shown up. When writing error handling, you usually know that by the time you're executing the error handling, your process is in serious trouble. You also know 9 times out of 10 that if you hit a particular piece of error handling, you're probably going to look at specific variable values or the call stack, or will want to record specific information. What I've always wanted was a way to code the commands I would normally execute directly into my error handling. By doing that, the commands would execute, enabling the maintenance programmers and me to debug a problem faster. My idea was that since OutputDebugString calls go through the debugger, you could embed the commands into an OutputDebugString. You'd tell the debugger what to look for at the front of the OutputDebugString text, and anything after it would be the commands to execute.

What I've just described is exactly how WinDBG's .OCOMMAND (Expect Commands from Target) command works. You call .OCOMMAND, identifying the string prefix to look for, at the front of any OutputDebugString calls. If the command is present, WinDBG will execute the rest of the text as a command string. Obviously, you'll want to be careful with the string you use or WinDBG could go nuts trying to execute OutputDebugString calls all through your programs. I like to use WINDBGCMD: as my string. I love this command and sprinkle WinDBG command strings all over my programs!

When using .OCOMMAND, you need to follow the command string with a ";g" or WinDBG stops when the command ends. In the following function, I ensure that the commands all end with ";g" so that execution continues. To get the commands to execute, I issue a .ocommand WINDBGCMD: as the program starts.

void Baz ( int ) { // To see the following convert into WinDBG commands, issue the // command ".ocommand WINDBGCMD:" inside WinDBG OutputDebugString ( _T ( "WINDBGCMD: .echo \"Hello from WinDBG\";g" )); OutputDebugString ( _T ( "WINDBGCMD: kp;g" ) ) ; OutputDebugString ( _T ("WINDBGCMD: .echo \"Stack walk is done\";g")) ; }

Категории