setting managed breakpoints in windbg

Windbg is a native debugger and you can use it to set a breakpoint on a virtual address. Any managed code running within the process wouldn’t have a virtual address associated with it until it is JIT compiled. Thus setting a breakpoint on a managed function is a bit tricky in Windbg. You can set a breakpoint on managed methods using windbg only:

  • When you are performing a live debug & not on a post mortem dump file.
  • You have a .RUN file from a Time Travel Debug trace.

When I started learning how to set managed breakpoints, one of the first questions I had is: How to set a breakpoint on a specific line of code in a managed method – because that is what we usually do in other IDE environments like Visual Studio. This is somewhat very difficult to do because, though you can get the virtual address where your method starts using the SOS commands, you will need to know the exact offset from the method’s starting virtual address [The actual address which corresponds to your line of code] and it isn’t easy at all to co-relate that to your source code. You will need to have an extremely good understanding of IL code, un-assemble the function using !u command and then set the breakpoint on that address. I do not have that skill yet, but will surely put out a post once I figure that out. So over here, I will describe how to set a breakpoint on a managed method for .NET Framework 2.0.

STEP 1: So assuming you are doing a live debug, the first step is to attach to the process that you want to debug. You can use the attach option in Windbg user interface [File menu]. Then load the SOS debugger extension - !loadby SOS mscorwks

STEP 2: You need to know which method you want to set a breakpoint on. The SOS command you need is !dumpmt with the –md parameter. This lists out the method table. For example, Dump the method table of System.Timespan

    1: !dumpmt -md 0x7911228c 
    2: EEClass: 791121e4 
    3: Module: 790c2000 
    4: Name: System.TimeSpan 
    5: mdToken: 02000114  (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) 
    6: BaseSize: 0x10 
    7: ComponentSize: 0x0 
    8: Number of IFaces in IFaceMap: 3 
    9: Slots in VTable: 56 
   10: -------------------------------------- 
   11: MethodDesc Table 
   12:    Entry MethodDesc      JIT Name 
   13: 796d2710   7914fb28     NONE System.TimeSpan.ToString() 
   14: 793624d0   7914b950   PreJIT System.Object.Finalize() 
   15: 796c07f8   7914fb08     NONE System.TimeSpan.CompareTo(System.TimeSpan) 
   16: 796d2708   7914fb18     NONE System.TimeSpan.Equals(System.TimeSpan) 
   17: 79381054   79266eb8   PreJIT System.TimeSpan..ctor(Int64) 
   18: 7939f058   79266ec0   PreJIT System.TimeSpan..ctor(Int32, Int32, Int32) 
   19: 7939f07c   79266ed8   PreJIT System.TimeSpan.get_Ticks() 
   20: 794002c8   79266ee0   PreJIT System.TimeSpan.get_Days() 
   21: 794002e8   79266ee8   PreJIT System.TimeSpan.get_Hours() 
   22: 79400328   79266ef0   PreJIT System.TimeSpan.get_Milliseconds() 
   23: 7940036c   79266ef8   PreJIT System.TimeSpan.get_Minutes() 
   24: 794003ac   79266f00   PreJIT System.TimeSpan.get_Seconds() 
   25: 794003ec   79266f08   PreJIT System.TimeSpan.get_TotalDays() 
   26: 7940040c   79266f10   PreJIT System.TimeSpan.get_TotalHours() 
   27: 79380c10   79266f18   PreJIT System.TimeSpan.get_TotalMilliseconds()
   28:  

STEP 3: [Optional] Using the method descriptor command, !dumpmd you can view if the code is JITted. See line #7 below. You can skip this and go to STEP 4 directly using the corresponding MethodDesc value from the previous output.

    1: !dumpmd 79266f18 
    2: Method Name: System.TimeSpan.get_TotalMilliseconds() 
    3: Class: 791121e4 
    4: MethodTable: 7911228c 
    5: mdToken: 0600101e 
    6: Module: 790c2000 
    7: IsJitted: yes 
    8: m_CodeOrIL: 79380c10

STEP 4: Add the breakpoint using !bpmd –md command.

    1: !bpmd –md 79380c10

Another way…

Syntax: !bpmd <ModuleName> <FunctionName>

Example: !bpmd mscorlib.dll System.TimeSpan.get_TotalMilliseconds

Notes

  1. The method names are case sensitive.
  2. In many cases, the breakpoints you set may be indicated as “Pending breakpoints”, which is normal, because your method may not yet be JITted.

Once your breakpoints are set, you can execute the g command to let the process execute till it hits the breakpoint. Once it hits the breakpoint you can do other tasks like examine callstacks, stack objects, local variables etc.

Have fun!