Setting breakpoints in .net code using !bpmd
From time to time I get questions like "our process spawns a lot of threads, how do we know who created them?" or "can I tell how many times we call this method?". You can answer both of these questions by setting breakpoints, and below is a simple sample of how this is done in windbg with sos...
My demo application (MyApplication.exe) has a class MyThreadClass that has a method CreateAThread, this creates a thread and starts running some method on that thread. What I am going to show here is how you can set a breakpoint when a thread is started to identify that the method CreateAThread started it, and automate it so that you don't have to click go after each time you hit the breakpoint...
1. Attach to the process with Windbg (File/Attach to process)
2. load sos (.loadby sos mscorwks)
3. Set the breakpoint on System.Threading.Thread..ctor in mscorlib.dll (the constructor for Thread)
0:005> !bpmd mscorlib.dll System.Threading.Thread..ctor
Found 4 methods...
MethodDesc = 79256ac8
Setting breakpoint: bp 793B01CC [System.Threading.Thread..ctor(System.Threading.ThreadStart)]
MethodDesc = 79256ad0
Setting breakpoint: bp 794064C4 [System.Threading.Thread..ctor(System.Threading.ThreadStart, Int32)]
MethodDesc = 79256ad8
Setting breakpoint: bp 794064F4 [System.Threading.Thread..ctor(System.Threading.ParameterizedThreadStart)]
MethodDesc = 79256ae0
Setting breakpoint: bp 79406510 [System.Threading.Thread..ctor(System.Threading.ParameterizedThreadStart, Int32)]
This sets up 4 different breakpoints, one for each overload of the Thread constructor. Note that what it actually does is set up a native breakpoint at the addresses where these constructors are JITted eg. bp 793B01CC.
At this point our list of breakpoints looks like this
0:000> bl
0 e 793b01cc 0001 (0001) 0:**** mscorlib_ni+0x2f01cc
1 e 794064c4 0001 (0001) 0:**** mscorlib_ni+0x3464c4
2 e 794064f4 0001 (0001) 0:**** mscorlib_ni+0x3464f4
3 e 79406510 0001 (0001) 0:**** mscorlib_ni+0x346510
So far so good, this works well since these methods are already JITted so we know the address of the code.
4. Set a breakpoint on MyApplication.MyThreadClass.CreateAThread (not used yet, therefore not JITted)
0:005> !bpmd MyApplication.exe MyApplication.MyThreadClass.CreateAThread
Found 1 methods...
Adding pending breakpoints...
In this case since the method is not JITted yet sos will wait until the method is JITted before it actually sets the breakpoint.
5. Hit G to get the debugger to go and call the CreateAThread method
0:005> g
(1cc8.14c4): CLR notification exception - code e0444143 (first chance)
JITTED MyApplication!MyApplication.MyThreadClass.CreateAThread()
Setting breakpoint: bp 023F04E8 [MyApplication.MyThreadClass.CreateAThread()]
Breakpoint 4 hit
eax=01d66b48 ebx=02721f60 ecx=02722fc4 edx=002216e8 esi=02722fc4 edi=02722fc4
eip=023f04e8 esp=0012efc8 ebp=0012f024 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
023f04e8 57 push edi
Once this method is hit, sos will set a breakpoint with bp, and our list of breakpoints now looks like this
0:000> bl
0 e 793b01cc 0001 (0001) 0:**** mscorlib_ni+0x2f01cc
1 e 794064c4 0001 (0001) 0:**** mscorlib_ni+0x3464c4
2 e 794064f4 0001 (0001) 0:**** mscorlib_ni+0x3464f4
3 e 79406510 0001 (0001) 0:**** mscorlib_ni+0x346510
4 e 023f04e8 0001 (0001) 0:****
If we continue hitting g we will stop everytime either CreateAThread is called or everytime a new thread is created. If this is something that happens frequently this soon gets very tiring so we can automate it instead to have it print something everytime it hits CreateAThread and for example run !clrstack to show the stack everytime it creates a new thread
6. Customize the breakpoints using bp, in the command string that follows bp <address> you can put any old commands you like to execute (separated by semicolon), and you can even make the breakpoint conditional so that you do certain things if a condition is true and others if it is not, or only activate it for a few passes. The helpfiles for windbg has some good examples around this. Notice in this case that apart from echoing some text and running !clrstack I am adding an extra g to have the debugger continue after it hit the breakpoint.
0:000> bp 793b01cc ".echo -->Created a new Thread;!clrstack;g"
breakpoint 0 redefined
0:000> bp 023f04e8 ".echo -->Hit breakpoint CreateAThread;g"
breakpoint 4 redefined
7. Run the application and watch the results scroll by... you might want to run .logopen /d to create a logfile before you start running so that all the output gets logged
0:000> g
--> Hit breakpoint CreateAThread
--> Created a new Thread
OS Thread Id: 0x14c4 (0)
ESP EIP
0012efb4 793b01cc System.Threading.Thread..ctor(System.Threading.ThreadStart)
0012efb8 023f0530 MyApplication.MyThreadClass.CreateAThread()
0012efcc 023f049d MyApplication.Form1.btnThreads_Click(System.Object, System.EventArgs)
0012efe4 7b062c9a System.Windows.Forms.Control.OnClick(System.EventArgs)
0012eff8 7b11cb29 System.Windows.Forms.Button.OnClick(System.EventArgs)
0012f004 7b11cc38 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)
0012f02c 7b0e5ae3 System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
0012f09c 7b070246 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
0012f0a0 7b07e46f [InlinedCallFrame: 0012f0a0]
0012f140 7b07e393 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
0012f148 7b06fd0d System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
0012f14c 7b06fce6 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
0012f15c 7b06fb8a System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
0012f318 004720d4 [NDirectMethodFrameStandalone: 0012f318] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0012f328 7b0835e5 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
0012f3c8 7b0831a5 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0012f440 7b082fe3 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0012f470 7b0692c2 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
0012f480 023f00a8 MyApplication.Program.Main()
0012f69c 79e7c74b [GCFrame: 0012f69c]
And thats all folks,
Tess
Comments
Anonymous
April 28, 2008
Thanks, excellent resources for debugging!Anonymous
April 30, 2008
Is there ever a case where code is not JITTED and therefore impossible to set a breakpoint?Anonymous
April 30, 2008
It will be jitted when you first call the method, so when you need the breakpoint it will be there if you use !bpmdAnonymous
April 30, 2008
So managed code is never interpreted, it's always JITTED and executed by the cpu?Anonymous
May 01, 2008
yes