Current version

v1.10.4 (stable)


Main page
Archived news
Plugin SDK
Knowledge base
Contact info
Other projects


Blog Archive

WinDbg cheat sheet

Microsoft WinDbg is part of the Debugging Tools for Windows package and is a fairly powerful, and free, debugger. I like to keep it around because it's much quicker to obtain and install than Visual Studio and is sometimes more helpful for debugging crashes in cases where Visual Studio acts oddly or is otherwise unable to extract the needed information. Although it has a GUI, it's mostly a command-line debugger, and as such is used somewhat differently than the Visual Studio debugger. I've found that WinDbg is less user friendly and harder to use for interactive source debugging, but much more powerful for difficult or post-mortem situations. One useful advantage is that if the Windows GUI gets wedged -- which tends to happen when hooks with global mutexes go awry -- you can still use CDB, the command-line version of WinDbg, because console windows are specially handled by CSRSS and often don't lock up with the rest of the UI.

Anyway, the cheat sheet:

The first step you need to do generally is to get symbols hooked up for your executable or crash dump so you're not flying blind. When one or more of these are missing, use .exepath and .sympath to set the executable and symbol paths. It's very helpful to also use .symfix+ to hook up the Microsoft symbol server so you get symbols and stack unwinding information for Windows DLLs. If the debugger is stubborn and doesn't want to load symbols because it thinks they don't match, you can use .symopt+ 0x40 to set the "load anything" flag.

If you're not sure if you've got all symbols, the lm command will list all modules in the process and their symbol load status. lm -v will also display additional data including paths, timestamps, and versions, which can help you go hunt down the right symbols from the build archive.

Type ~ to dump a list of all threads. The tilde is also a prefix for thread selectors at the beginning of commands. Two useful selectors are ~n to select thread n temporarily, and ~* to select all threads. ~s changes the current thread, so to switch to thread 4, you'd type ~4s.

r will dump the current registers, in case you missed it from the attach -- and as WinDbg helpfully tells you, .ecxr will switch to the context frame of the exception that occurred, if it was captured in a minidump. If you attached the debugger because the application appears to have deadlocked, !locks will dump out a list of critical sections currently held and which threads hold them. This usually identifies the reference cycle immediately.

The command to dump call stacks is k. Generally the first thing you should do after attaching to a process or loading a crash dump and then setting up symbols is to type ~*k to dump call stacks for all threads. kb will also attempt to dump raw parameters for each call. Unfortunately, the call stack on an optimized x86 executable is often incorrect. Sometimes you can dump past a sticking point or uncover hidden calls by forcing a different starting stack address with =, e.g. k =12fc00.

db dumps bytes, dw dumps words, dd dumps dwords, etc. One of my favorite commands is dds, which dumps an array of dwords and attempts to decode each one as a pointer to a symbol. If I suspect that the automated call stack is incorrect, I do dds esp L20 and then try to reconstruct the correct call stack from any apparent return addresses in the output. It's also useful for finding vtable pointers of C++ objects. For strings, da dumps ASCII strings and du dumps Unicode ones.

When you have a valid call stack, you can use .frame to switch between the entries on the call stack, and dv to dump local variables... or you could just use the WinDbg UI for that. ? evaluates an expression using the default expression syntax (usually MASM), and ?? evaluates using C++ syntax. I prefer to use ?? instead of the watch window, because WinDbg unfortunately has a habit of checking symbols over the network a lot when evaluating expressions, and if you use the watch window it can repeatedly hang the debugger for long periods of time, whereas with ?? you have more precise control over when it happens.

Breakpoints can be set with bp, cleared with bc, and listed with bl. These are all boring PC-based breakpoints, though -- use ba to set data breakpoints, which IMO are highly underrated. t traces, and g goes (resumes execution). Ctrl+Break interrupts the application again. WinDbg doesn't bring the app to the foreground on resuming execution like Visual Studio does, which can avoid annoying loops where every time you hit go, it immediately repaints and hits your breakpoint again.

After you've gotten all the information you can locally, there are a few options. You can just give up with q, or you can try again with .restart, if you started the debuggee under WinDbg. If the program you attached to is just hung, you can use .detach and then attach Visual Studio to it. You can also use .dump to create a minidump for further analysis -- this is especially helpful since you can load the minidump offline in either WinDbg or Visual Studio, and if you create a "big minidump" with .dump /ma, you can pretty much see and do just about anything in the dump that you could locally, short of actually resuming execution.

That's about all I can think of for WinDbg essentials, although of course it comes with a nice help file that details everything I missed here. I highly recommend looking into WinDbg and adding it to your debugging arsenal, especially if you're one of those people who has to diagnose crash dumps sent by users. The last point I'd like to make is that if you're working on Windows XP, the NT system debugger (NTSD) is the ancestor of WinDbg; it's missing a lot of features in comparison, but it's always there. If you find yourself in a pinch and don't even have WinDbg available, you can attach ntsd.exe to the failed process, using -p to specify the process ID and -pv to force a non-intrusive attach if necessary, and then write a minidump with .dump.


This blog was originally open for comments when this entry was first posted, but was later closed and then removed due to spam and after a migration away from the original blog software. Unfortunately, it would have been a lot of work to reformat the comments to republish them. The author thanks everyone who posted comments and added to the discussion.