3 minute read

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: 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 yet called, 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 every time either CreateAThread is called or every time 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 every time it hits CreateAThread and for example run !clrstack to show the stack every time 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 semi colon), 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 help files 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 log file 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