I often find myself needing to debug production applications and .NET applications I did not write myself or third party components in my own applications. Often, when doing so, we find ourselves thinking “Geez, I wonder if this component or this application is invoking this method at this point”, or “I’d like to be able to break on exception System.Foo, so I can see what is happening”. You can’t install Visual Studio on productions server, and even if you could, it wouldn’t help if you’re not debugging your own application. Do note this post only applies to .NET 2.0
Fortunately, WinDBG can help, and it isn’t as hard as one would think!
First, load up WinDBG, and attach to the process we want to debug:
For this exercise, I made a small application that makes an instance of a class, and invokes two methods within it, one that returns a Hello World, and one that throws an exception:
class Program
{
static void Main(string[] args)
{
Console.ReadKey(true);
BrokenClass broken = new BrokenClass();
broken.HelloWorld();
broken.BrokenWorld();
}
}
namespace PhoenixMatrix.Windbg.Demo
{
public class BrokenClass
{
public string HelloWorld()
{
return "Hello World!";
}
public void BrokenWorld()
{
throw new NotSupportedException("Hello World!");
}
}
}
Simple enough. All right, lets get started. So we have our process running, and it is waiting on a user action (Read key). Then, we have multiple options, depending on what we want to do.
Option A – Break on all Exceptions
Without doing anything else, we use the following commands:
sxe clr
!loadby sos mscorwks
What this does, is instructing WinDBG to break on all CLR exceptions, both first chance (the ones that didn’t bubble up yet) and second chance (the ones that did bubble up, usually because there was no catch blocks waiting for them). The second command, !loadby sos mscorwks, will load up a special set of extensions especially made to debug managed code. To be sure we’re loading the version that matches the .NET framework we are using, we load sos.dll at the same location mscorwks (part of the .NET runtime) was loaded. If we run:
g
our process now continues, we hit any key, and sure enough, WinDBG breaks on an exception. Now let see where it stopped (from now on I will be showing the output of WinDBG, bolding and underlining the command I executed)
0:000> !pe
Exception object: 01cd8988
Exception type: System.NotSupportedException
Message: Hello World!
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131515
!pe, shortcut for !PrintException, will give us the details of the exception. Now there’s no stacktrace because this is a first chance exception. Lets hit “g” to go a step further. The exception will enter second chance stage, and we can print it out for real this time:
0:000> !pe
Exception object: 01cd8988
Exception type: System.NotSupportedException
Message: Hello World!
InnerException: <none>
StackTrace (generated):
SP IP Function
002FEE38 001C01A6 PhoenixMatrix_Windbg_Demo!PhoenixMatrix.Windbg.Demo.BrokenClass.BrokenWorld()+0x46
002FEE48 001C00CE PhoenixMatrix_Windbg_Demo!PhoenixMatrix.Windbg.Demo.Program.Main(System.String[])+0x5e
StackTraceString: <none>
HResult: 80131515
Now we have the stack trace, complete with method information. This is great if we want to break on any and all exception, but what if we wanted to break on a particular one?
Option B – Break on a specific exception
Instead of simply using sxe clr, we can create a breakpoint on a particular exception, as follow (Note that it is possible at this point WinDBG will give a big output complaining about lack of symbols. You can ignore that safely, i just removed them from my output below):
0:003> !soe -create System.NotSupportedException 1
Breakpoint set
The !soe (Stop On Exception) lets us create a breakpoint on a particular exception. Note that you must fully qualify the name of the exception, with the namespace. The number (1 in this case) after the exception is a “speudo register”, which can be used in more advanced WinDBG scripted commands to examine the result of the breakpoint. For now we don’t need that, but it is still needed to set a normal breakpoint. Now, lets allow our application to continue with “g”, hit a key, and see what we get.
0:000> !pe
Exception object: 01de8988
Exception type: System.NotSupportedException
Message: Hello World!
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131515
Great! same result as last time! However, if other exceptions had been thrown first (common in an ASP.NET application, for example), we would have ignored name, and only stopped on System.NotSupportedException. Now, this is great for exceptions. But what if we wanted to break on a method?
Option C – Break on a method name
It is possible to have a breakpoint on an actual method. First, we need to do the initial steps again (attack to the process, load sos), and run the following:
0:003> !bpmd PhoenixMatrix.Windbg.Demo.exe PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld
Found 1 methods...
MethodDesc = 003e3060
Adding pending breakpoints...
That one is a mouthful. !bpmd (break point method description) lets us break on a method within a module. In C#, the module is virtually always the fully qualified name of the executable or the dll (so, the assembly) the method is in. So we type !bpmd, followed by the full module name, followed by the fully qualified method name, complete with namespace and parent class. We also see this added a “pending” breakpoint in our case: that is because the method has not been JITted, or compiled to native code, yet. Now lets allow our program to continue.
0:003> g
(16b8.14bc): CLR notification exception - code e0444143 (first chance)
JITTED PhoenixMatrix.Windbg.Demo!PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld()
Setting breakpoint: bp 00990120 [PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld()]
Breakpoint 0 hit
eax=003e3060 ebx=0038ecfc ecx=01c18950 edx=01c1895c esi=004b5308 edi=00000000
eip=00990120 esp=0038ecb4 ebp=0038ecd0 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
00990120 55 push ebp
And here we see that the breakpoint worked, and stopped on the now JITted HelloWorld method. We can also pull the stacktrace, to see how that method was invoked:
0:000> !clrstack
OS Thread Id: 0x14bc (0)
ESP EIP
0038ecb4 00990120 PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld()
0038ecb8 009900c2 PhoenixMatrix.Windbg.Demo.Program.Main(System.String[])
0038eeec 71cf1b4c [GCFrame: 0038eeec]
While using !bpmd is great in cases where we are sure of the method’s name and module, do keep in mind that the compiler will happily mangle the method names when it suits its needs (such as public property definitions, which are compiled to get/set methods), and that it is possible to define custom modules in certain languages (such as VB.NET), so it is not always trivial.
Option D – Break directly on the method
With the following commands, we can fetch the actual pointer to the method, and break on it, as well as browse the methods in the class, letting us more easily select the one we want. First, we need to find our class within the available modules.
0:003> !name2ee * PhoenixMatrix.Windbg.Demo.BrokenClass
Module: 706f1000 (mscorlib.dll)
--------------------------------------
Module: 002c2c5c (PhoenixMatrix.Windbg.Demo.exe)
Token: 0x02000003
MethodTable: 002c3080
EEClass: 002c134c
Name: PhoenixMatrix.Windbg.Demo.BrokenClass
!name2ee will get us the class information within a module. Using the * as the second parameter will search all modules, but if we wanted, we could give it the fully qualified module name instead (in this case, PhoenixMatrix.Windbg.Demo.exe). The command will go through the modules, and if it finds the Class, outputs its details. The part we’re interested in is the MethodTable, so we can output all of the class’s methods.
0:003> !dumpmt -md 002c3080
EEClass: 002c134c
Module: 002c2c5c
Name: PhoenixMatrix.Windbg.Demo.BrokenClass
mdToken: 02000003 (C:\PhoenixMatrix.Windbg.Demo.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
708b6aa0 70734924 PreJIT System.Object.ToString()
708b6ac0 7073492c PreJIT System.Object.Equals(System.Object)
708b6b30 7073495c PreJIT System.Object.GetHashCode()
70927410 70734980 PreJIT System.Object.Finalize()
002cc045 002c3078 NONE PhoenixMatrix.Windbg.Demo.BrokenClass..ctor()
002cc03d 002c3060 NONE PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld()
002cc041 002c306c NONE PhoenixMatrix.Windbg.Demo.BrokenClass.BrokenWorld()
Now this is a lot of information, but !dumpmt simply dumps out the method table’s information. The last part is what we’re interested in, the list of method. Notice that we can see the JIT state of the methods (remember when I said HelloWorld was not JITted yet? We see it now, though that doesn’t stop us from setting a breakpoint on it). Lets take the MethodDesc code of the method we want to break on, HelloWorld (so the second code from the left)
0:003> !bpmd -md 002c3060
MethodDesc = 002c3060
Adding pending breakpoints...
The breakpoint is successfully set. If we run the application, we can see it will correctly break on HelloWorld, as per the clrstack again:
0:000> !clrstack
OS Thread Id: 0x1074 (0)
ESP EIP
002bf264 008e0120 PhoenixMatrix.Windbg.Demo.BrokenClass.HelloWorld()
002bf268 008e00c2 PhoenixMatrix.Windbg.Demo.Program.Main(System.String[])
002bf4a0 71cf1b4c [GCFrame: 002bf4a0]
And there you have it, the primary ways of setting breakpoints using WinDBG on managed code. These little tricks have tons of applications, such as finding out the behavior of third party components, debugging third party applications (SharePoint comes to mind), as well as doing the diagnostic of nasty production issues that you simply cannot reproduce. And since WinDBG doesn’t need to load all symbols the way Visual Studio does before letting you use it, it can be a real timesaver, once you get the hang of it.