Before I came to work at Microsoft I worked as a professional developer on the Microsoft platform and I used to work with the Visual Studio (6 latterly) debugger which I thought was a pretty cool debugger.
When I joined Microsoft I found that a lot of people worked with WinDbg as their primary debugger and, for a while, I just didn’t get it as it seemed like an unnecessarily grungy tool for achieving things that Visual Studio could already do.
However, over a period of time I came to see the light and I’m now of the view that WinDbg is about the best debugger I’ve ever used (including those ones I used to work with on Unix platforms). WinDbg is just the most powerful debugger out there for user-mode debugging and I don’t do any other kind of debugging J
So, there is a lot of information out there already on WinDbg but I just wanted to try and highlight it here and provide a single-pager on it as it seems to be grossly overlooked as a debugger for managed code and, yet, it’s capable of a whole tonne of stuff that no single other debugger can do for you right now.
People are often put off WinDbg because of its interface and the vast array of commands so in this little series of postings I’ll try and demystify a little bit and show how you can debug a few common scenarios in managed code with it.
In this post, let’s just get to the point where we can use WinDbg to debug a regular process and do some work with it then in the next post(s) I’ll look at more stuff that you can do.
So, first things first, get hold of a copy of WinDbg from here and install it. If you want to follow along with this post then you can go and download it right now – the install takes about 2 minutes once you’ve got the 10MB download down from the site.
The first thing you’ll realise is how lightweight an installation WinDbg is and that (sometimes) means that you can install it into places that you’d never install a copy of Visual Studio or the Visual Studio remote debug set up. Indeed, often if you raise a support call with Microsoft you’ll get asked to install WinDbg and it’s automated helper (the "autodumper") in order to get a crash dump of your application to send back to Microsoft.
Before we get into debugging something I need to have a quick word or two about Symbols and Commands.
The first issue that we need to address in order to progress is that of symbols. If you want to get decent stack traces and dumps of variable values and so on out of the debugger then you need symbols for the modules that you’re debugging whether those modules are yours or someone else’s and whether those modules are managed or unmanaged (although there’s quite a lot more that you can do with managed code without symbols).
Symbols are typically either private symbols (include variable information), retail symbols (function information but not variable information) or export symbols (generally not so useful for debugging purposes). If you’re debugging with “export” symbols it’s not usually enough to actually work out what’s going on.
For your own code you’re responsible for building the symbol files (usually .PDB program databases) by setting the right flags on the compiler or choosing the right configuration in a project (this is true for VC++, VB6 and all the .NET languages). This will give you full private symbols.
When the debugger (and lots of other tools) want to find symbols they typically check the contents of an environment variable named _NT_SYMBOL_PATH which is used (just like PATH – i.e. a list of folder separated by semi-colons) to find symbol files. You can set _NT_SYMBOL_PATH prior to running WinDbg or you can use the debugger’s own symbol path settings to find symbol files. These settings are controlled via the File->Symbol File Path menu option or using the .sympath command.
For other people’s code you need to get symbols from them. For Microsoft code you’re in luck because the symbols are available from a public symbol server on the internet. In order to make use of the symbol server you should set your symbol path to be something like;
What this says is that the debugger should first check a local cache named C:\MyLocalSymbols and if symbols are not found there then go out to the Microsoft symbol server for symbols and, if found, download them and cache them in that folder. Note that symbols are downloaded based upon name, version and checksum information from the modules being debugged so the debugger usually knows if the symbols match the code properly.
If you wanted to combine this with a local location for your own code then you’d use something like;
so that the debugger would check your folder first and would then proceed to look in the cache and finally to the symbol server for symbols.
A core set of commands would be as below. I rarely venture beyond these as I’m not an advanced user of WinDbg by any means and this gets me by.
.hh – bring up the help file 🙂 The help file is fantastic so don’t skip on it – everything’s documented in there.
g – "GO!". That is, continue running.
bp (Address) – set a break point. Note that (address) can take many formats here including just a memory location but the most common format specifier here is to use syntax such as;
(remember that most Windows functions have an ASCII and a Unicode variant so we have KERNEL32!CreateFileA and KERNEL32!CreateFileW).
bl – list all breakpoints. Each breakpoint listed has a number in the list which you need for…
bc (number), be (number), bd (number) – these respectively clear, enable and disable breakpoints from the list.
kb, kp, kd etc. The commands beginning with "k" show the stack for the thread that the debugger is looking at. There are a few variants so check out the help for those. The simplest variant that I use is;
(200 is the maximum depth of stack trace that I’m looking for)
dw, db, ds, etc. The commands beginning with "d" show the contents of memory. One of the most common is "dt" which shows you the contents of memory laid out according to the specification of a type as long as the debugger can see the definition of a type.
sxe, sxd, sxi, sxn. The commands beginning with "sx" set the behaviour for what the debugger should do when an exception occurs. A good example here would be;
which is saying "I want to break into the debugger if there’s an access violation".
lm – lists the modules loaded by the program and what kind of symbols are loaded for the modules. Note that export symbols are not really symbols at all but are really the debugger guessing which function you’re in based upon the export table of the DLL. For COM servers in particular (which have only 4 exported functions or so) you’ll often find yourself appearing in DllRegisterServer if you only have export symbols. Good examples here would be;
lm v (verbose mode)
lm v mUSER* (verbose mode, matc any modules that begin with USER*)
x – Examine symbols. This is very, very useful as it shows you the set of symbols loaded for particular modules. A good example here would be;
x USER32!* (show me all the symbols loaded from the USER32 module)
x USER32!Create* (show me all the symbols loaded from the USER32 module that begin with "Create")
.cls – clear the screen. You’ll be needing this one!
.reload – this causes the symbol information to be reloaded for a particular module. The most common form of this would be to do something like;
.reload /f user32.dll
Where the /f overrides the debugger’s naturally lazy mode of working whereby it wouldn’t actually do the reload right there and then. Normally, this is a useful parameter to use.
~ – this is the tilde character and it lists all the threads in the process and shows their status. This can be combined with wildcards in order to execute particular commands on all threads in the process. The single most common one is probably to combine it with a “stack” command to do something like;
~* kb 200
which will show you the stack frames for all the threads in the process.
~N s – this changes the debugger’s focus to another thread. For example;
Will switch the debugger so that it’s focused on thread 7. Note that 7 here is the debugger’s thread number rather than the real thread ID and it comes from the list given by the ~ command.
Along with the core set of commands, WinDbg is capable of loading up debugger extensions to extend the core functionality of the debugger. These are “bang commands” in that they begin with an exclamation mark. You can manipulate the extensions that the debugger has loaded using the following commands;
.chain – this shows the extensions that the debugger has loaded
.load – this loads an extension DLL (e.g. .load SOS.DLL)
.unload – unloads an extension DLL
.setdll – this sets the “default” extension DLL.
A quick word about .setdll and how command processing works here. If you have an extension named UEXT.DLL which contains a command named help (and most extensions will have a help command) then you can run that command using;
And that works fine. If you find yourself using UEXT more than any other extension then you can use;
To make UEXT.DLL the default extension and then you can just use;
And that will now default to mean !UEXT.help because of the default extension setting.
A First Debugging Session
So, let’s finish up this post with a quick debugging session and next time around I’ll talk about debugging managed code rather than just any old process.
- Run up WinDbg and also run up a copy of NotePad.
- Within WinDbg attach to the NotePad process that you just ran by either using the File->Attach to Process menu or the F6 shortcut. This will give you a dialog where you can select a process. Note that the “Non Invasive” option would allow you to detach from the debugged process without killing it (if you’re on Windows XP or 2003) but it limits the commands that you can use so leave it unchecked.
- Don’t worry about the workspace information, let it go.
- The debugger should present some diagnostics as it loads information and will then stop with the command prompt. The debuggee (NotePad) is now halted.
- Let’s take a look around. Firstly, let’s look at the modules that are loaded by notepad. Execute a “lm v” to list all the modules and follow it up with a simple “lm”. On my system I get this;
start end module name
01000000 01014000 notepad (no symbols)
4ce00000 4cf02000 COMCTL32 (export symbols) C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2149_x-ww_a84b1f06\COMCTL32.dll
5ad70000 5ada7000 uxtheme (export symbols) C:\WINDOWS\system32\uxtheme.dll
605d0000 605d9000 mslbui (export symbols) C:\WINDOWS\system32\mslbui.dll
61220000 61232000 MSH_ZWF (export symbols) C:\Program Files\Microsoft Hardware\Mouse\MSH_ZWF.dll
73000000 73026000 WINSPOOL (export symbols) C:\WINDOWS\system32\WINSPOOL.DRV
74720000 7476b000 MSCTF (export symbols) C:\WINDOWS\system32\MSCTF.dll
763b0000 763f9000 comdlg32 (export symbols) C:\WINDOWS\system32\comdlg32.dll
77120000 771ac000 OLEAUT32 (export symbols) C:\WINDOWS\system32\OLEAUT32.DLL
771b0000 779c4000 SHELL32 (export symbols) C:\WINDOWS\system32\SHELL32.dll
779d0000 77a46000 SHLWAPI (export symbols) C:\WINDOWS\system32\SHLWAPI.dll
77a50000 77b8d000 ole32 (export symbols) C:\WINDOWS\system32\ole32.dll
77c10000 77c68000 msvcrt (export symbols) C:\WINDOWS\system32\msvcrt.dll
77cc0000 77d5b000 ADVAPI32 (export symbols) C:\WINDOWS\system32\ADVAPI32.dll
77d60000 77df1000 USER32 (export symbols) C:\WINDOWS\system32\USER32.dll
77e00000 77e93000 RPCRT4 (export symbols) C:\WINDOWS\system32\RPCRT4.dll
77ea0000 77ee5000 GDI32 (export symbols) C:\WINDOWS\system32\GDI32.dll
7c800000 7c8f2000 kernel32 (export symbols) C:\WINDOWS\system32\kernel32.dll
7c900000 7c9ae000 ntdll (export symbols) C:\WINDOWS\system32\ntdll.dll
- Let’s get some symbols because we can’t really work with export symbols. Set up your symbol path (either through the File->Symbol Path menu or through the .sympath command) so look something like this;
- Reload symbols for the loaded modules by using the “.reload /f” command – this may well take a little time as the symbols for all those modules that NotePad loaded trickle down from the internet to your local cache. This gets better with time as you pull down common symbols from the net.
- After the reload completes do another lm and make sure you got some symbols. My session now gives me;
start end module name
01000000 01014000 notepad (pdb symbols) e:\LocalSymbols\notepad.pdb\15800B8231AF4FDE85232D42B267D3E51\notepad.pdb
4ce00000 4cf02000 COMCTL32 (pdb symbols) e:\LocalSymbols\MicrosoftWindowsCommon-Controls-6.0.2600.2149-comctl32.pdb\4133208CAF9A4B93982A1F873FBC79261\MicrosoftWindowsCommon-Controls-6.0.2600.2149-comctl32.pdb
5ad70000 5ada7000 uxtheme (pdb symbols) e:\LocalSymbols\uxtheme.pdb\3A4D37B97BA34837AC589BDB15CC265D2\uxtheme.pdb
605d0000 605d9000 mslbui (pdb symbols) e:\LocalSymbols\MSLBUI.pdb\ED5CCEFBD83C4F929AAB9A1029C54DDD1\MSLBUI.pdb
61220000 61232000 MSH_ZWF (export symbols) C:\Program Files\Microsoft Hardware\Mouse\MSH_ZWF.dll
73000000 73026000 WINSPOOL (pdb symbols) e:\LocalSymbols\winspool.pdb\20EE1501116B4C4180ABE40B209DEC962\winspool.pdb
74720000 7476b000 MSCTF (pdb symbols) e:\LocalSymbols\msctf.pdb\F8F1C6E0E1CC4D1CB4936C12A6A6D4C92\msctf.pdb
763b0000 763f9000 comdlg32 (pdb symbols) e:\LocalSymbols\comdlg32.pdb\2BC57F8BE63A429FB4B439EA2854387A2\comdlg32.pdb
77120000 771ac000 OLEAUT32 (pdb symbols) e:\LocalSymbols\oleaut32.pdb\96D8A9A6CB704201A365F345B2A9F72D2\oleaut32.pdb
771b0000 779c4000 SHELL32 (pdb symbols) e:\LocalSymbols\shell32.pdb\FE0DC899DD78422AB29561FE8255E1C92\shell32.pdb
779d0000 77a46000 SHLWAPI (pdb symbols) e:\LocalSymbols\shlwapi.pdb\231AB6F0A19F4DBB9837440BCC167BEC2\shlwapi.pdb
77a50000 77b8d000 ole32 (pdb symbols) e:\LocalSymbols\ole32.pdb\656B303C367B4E679CA249E32CE572D92\ole32.pdb
77c10000 77c68000 msvcrt (pdb symbols) e:\LocalSymbols\msvcrt.pdb\8E17C9B9A55C4118A7D35025C5BE51871\msvcrt.pdb
77cc0000 77d5b000 ADVAPI32 (pdb symbols) e:\LocalSymbols\advapi32.pdb\57E2A801C610451AA66B7CD94F0910F72\advapi32.pdb
77d60000 77df1000 USER32 (pdb symbols) e:\LocalSymbols\user32.pdb\35948255FAC04CA99F5CF61959B45DC42\user32.pdb
77e00000 77e93000 RPCRT4 (pdb symbols) e:\LocalSymbols\rpcrt4.pdb\79C7047CB77B435DB01EB365C3AF17152\rpcrt4.pdb
77ea0000 77ee5000 GDI32 (pdb symbols) e:\LocalSymbols\gdi32.pdb\E7D5B033EFE5466E97DB048BE4C88FA62\gdi32.pdb
7c800000 7c8f2000 kernel32 (pdb symbols) e:\LocalSymbols\kernel32.pdb\5091FA1742544F2EA3D425642634F78E2\kernel32.pdb
7c900000 7c9ae000 ntdll (pdb symbols) e:\LocalSymbols\ntdll.pdb\7B2A2EF0E63F4848A8D309E84E47322C2\ntdll.pdb
- So, that’s modules and symbols. Let’s see if we can set some breakpoints. Suppose that I want to hit a breakpoint anytime that NotePad opens up a file. Let’s do that. Firstly, take a look at symbols in KERNEL32 to see if we can find the CreateFile function that NotePad uses.
- Issue a “x KERNEL32!CreateF*” and see what results we get back you should find the ASCII CreateFileA and the Unicode CreateFileW functions in there.
- Set a breakpoint on a function. Issue a “bp kernel32!CreateFileW” to set a breakpoint on that function.
- Issue a “GO” command “g” to continue the debuggee running.
- Note that when the debuggee is running you can always get back to the debugger by issuing a CTRL+BREAK to the debugger window when it will try and halt the debuggee for you and drop you back to the command prompt.
- Open a file with notepad. You’ll spot NotePad loading some more modules in order to do this operation and get that dialog onto the screen. Your breakpoint will more than likely hit before the file dialog comes up.
- Check-out where you are with your breakpoint – i.e. what the stack frame looks like. Issue a “kb 200” command. You should get results as below;
ChildEBP RetAddr Args to Child
0006be18 76671b2a 76671b74 00000020 00000003 kernel32!CreateFileW
0006be94 766724e2 000a6b40 0006bf4c 00000000 cscui!IsCSCEnabled+0x38
0006bea8 77a68b49 000a7084 77a51a60 0006bf44 cscui!DllGetClassObject+0x72
0006bec4 77a80f5e 000a7084 77a51a60 0006bf44 ole32!CClassCache::CDllPathEntry::DllGetClassObject+0x2d
0006bedc 77a80e9a 0006bef0 77a51a60 0006bf44 ole32!CClassCache::CDllFnPtrMoniker::BindToObjectNoSwitch+0x1f
0006bf08 77a81cc6 0006bf4c 00000000 0006c540 ole32!CClassCache::GetClassObject+0x38
0006bf84 77a806aa 77b76ca4 00000000 0006c540 ole32!CServerContextActivator::CreateInstance+0x106
0006bfc4 77a81e19 0006c540 00000000 0006ca8c ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7
0006c018 77a81d90 77b76ca8 00000000 0006c540 ole32!CApartmentActivator::CreateInstance+0x110
0006c038 77a8101e 77b76ca8 00000001 00000000 ole32!CProcessActivator::CCICallback+0x6d
0006c058 77a80fd5 77b76ca0 0006c39c 00000000 ole32!CProcessActivator::AttemptActivation+0x2c
0006c090 77a81e7a 77b76ca0 0006c39c 00000000 ole32!CProcessActivator::ActivateByContext+0x42
0006c0b8 77a806aa 77b76ca0 00000000 0006c540 ole32!CProcessActivator::CreateInstance+0x49
0006c0f8 77a81bc4 0006c540 00000000 0006ca8c ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7
0006c348 77a806aa 77b765d4 00000000 0006c540 ole32!CClientContextActivator::CreateInstance+0x8f
0006c388 77a805dc 0006c540 00000000 0006ca8c ole32!ActivationPropertiesIn::DelegateCreateInstance+0xf7
0006cb38 77a64eb1 000a2f08 00000000 00000001 ole32!ICoCreateInstanceEx+0x3c9
0006cb60 77a64e80 000a2f08 00000000 00000001 ole32!CComActivator::DoCreateInstance+0x28
0006cb84 77a65102 000a2f08 00000000 00000001 ole32!CoCreateInstanceEx+0x1e
0006cbb4 779d69a5 000a2f08 00000000 00000001 ole32!CoCreateInstance+0x37
- So, we can see the direct call-stack that led to our call to CreateFileW. If we had full symbols for these modules we could use a KP rather than a KB command and we’d get a list of parameters to the functions. As it is with what we have here we’d need to resort to disassembly to get the parameter information here and it’s beyond this posting.
- Continue the debuggee by issuing a “g” command and continue issuing “g” commands until NotePad finally puts its dialog on the screen – note how many times we hit this function here J If you’re interested, issue a few KB’s on some of these call chains and see what exactly it is that Notepad is doing.
- Break the debuggee by hitting CTRL+BREAK on the WinDbg window.
- Issue a “~” command to get a picture of the threads in the process. In my session I get 3 as below;
0 Id: 9bc.b58 Suspend: 1 Teb: 7ffdf000 Unfrozen
1 Id: 9bc.8a4 Suspend: 1 Teb: 7ffde000 Unfrozen
. 2 Id: 9bc.d4 Suspend: 1 Teb: 7ffdd000 Unfrozen
- Note that this display indicates that the debugger is focused on thread “2” which is really OS thread 8A4 in process 9BC (all hex I’m afraid).
- Check out where all the threads are by issuing a “~* kb 200”. You might not get anything too exciting here but it’s illustrative of how to use the technique.
- Finally, close off this session by having a look at some extension commands.
- List the set of extensions that you’ve got by issuing the “.chain” command.
- Have a look at the commands available in these extensions by issuing: !exts.help, !uext.help, !ntdsexts.help, !ext.help and see what commands are available to you.
- Try an extension command. For example, “!exts.cs” will display all the Critical Sections that the process has and their status as to whether they are locked or not. This can be a useful command in many server-side scenarios.
- End the debugging session with a “q” command.
That’s it. I’ll post again in the near future around how you can build on what I’ve just walked through here in order to debug managed code with WinDbg and take advantage of features within the debugger that don’t (yet) appear elsewhere.