Hi all,

This post is a continuation of MANAGED DEBUGGING with WINDBG. Call Stacks. Part 3.

 

THREADS. Part 1

 

·         We can see all threads in our process:

If we reach a breakpoint or break on an exception, WinDbg command prompt shows the ID of the thread which reached the breakpoint or raised the exception. We can directly see the call stack, or inspect the exception we got, etc., for that thread.

If we break manually (i.e. Ctrl+Break), the command prompt shows the ID of the debugger thread  (injected by the debugger in our process to interact with it). We can’t see or do anything useful there:

0:004> kL

ChildEBP RetAddr 

04f1fcfc 77c0f0a9 ntdll!DbgBreakPoint

04f1fd2c 77a43833 ntdll!DbgUiRemoteBreakin+0x3c

04f1fd38 77bba9bd KERNEL32!BaseThreadInitThunk+0xe

04f1fd78 00000000 ntdll!_RtlUserThreadStart+0x23

 

So we need to change to another more useful thread. We can take a look to all threads as in any unmanaged application:

0:004> ~

   0  Id: 1910.1f3c Suspend: 1 Teb: 7ffdf000 Unfrozen

   1  Id: 1910.1b08 Suspend: 1 Teb: 7ffde000 Unfrozen

   2  Id: 1910.904 Suspend: 1 Teb: 7ffdd000 Unfrozen

   3  Id: 1910.1bbc Suspend: 1 Teb: 7ffdc000 Unfrozen

.  4  Id: 1910.1b60 Suspend: 1 Teb: 7ffdb000 Unfrozen

 

We can see for how long they’ve been running:

0:004> !runaway

 User Mode Time

  Thread       Time

   0:1f3c      0 days 0:00:00.078

   4:1b60      0 days 0:00:00.000

   3:1bbc      0 days 0:00:00.000

   2:904       0 days 0:00:00.000

   1:1b08      0 days 0:00:00.000

 

We can move to any other thread and see i.e. its call stack. Let’s take a look to thread 0 because it’s the one which apparently has been doing more stuff, and then go back to the thread where we broke:

0:004> ~0s

eax=00000000 ebx=00000000 ecx=003b008c edx=77be0f34 esi=00dd8c30 edi=00000000

eip=77be0f34 esp=0027e3a8 ebp=0027e3dc iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

ntdll!KiFastSystemCallRet:

77be0f34 c3              ret

0:000> kL

ChildEBP RetAddr 

0027e3a4 77afb5bc ntdll!KiFastSystemCallRet

0027e3a8 77af1598 USER32!NtUserWaitMessage+0xc

0027e3dc 77af1460 USER32!DialogBox2+0x202

0:000> ~

.  0  Id: 1910.1f3c Suspend: 1 Teb: 7ffdf000 Unfrozen

   1  Id: 1910.1b08 Suspend: 1 Teb: 7ffde000 Unfrozen

   2  Id: 1910.904 Suspend: 1 Teb: 7ffdd000 Unfrozen

   3  Id: 1910.1bbc Suspend: 1 Teb: 7ffdc000 Unfrozen

#  4  Id: 1910.1b60 Suspend: 1 Teb: 7ffdb000 Unfrozen

0:000> ~#

#  4  Id: 1910.1b60 Suspend: 1 Teb: 7ffdb000 Unfrozen

      Start: ntdll!RtlUserThreadStart (77be0f18)

      Priority: 0  Priority class: 32  Affinity: 3

0:000> ~#s

0:004>

 

Thread 0 is the main GUI thread of a WinForms application, for instance.

Note that ‘~’ command will use a ‘.’ to indicate the active thread, and a ‘#’ to indicate the thread where the debugger broke in the first place.

Additionally, we may want to see all call stacks (kL100) for all threads (~*) to find the thread where we want to move:

0:000> ~*kL100

 

.  0  Id: 1910.1f3c Suspend: 1 Teb: 7ffdf000 Unfrozen

ChildEBP RetAddr 

0027e3a4 77afb5bc ntdll!KiFastSystemCallRet

0027e3a8 77af1598 USER32!NtUserWaitMessage+0xc

0027e3dc 77af1460 USER32!DialogBox2+0x202

...

0027f894 00000000 ntdll!_RtlUserThreadStart+0x23

   1  Id: 1910.1b08 Suspend: 1 Teb: 7ffde000 Unfrozen

ChildEBP RetAddr 

00bef7b8 77be0690 ntdll!KiFastSystemCallRet

...

00bef980 00000000 ntdll!_RtlUserThreadStart+0x23

...

#  4  Id: 1910.1b60 Suspend: 1 Teb: 7ffdb000 Unfrozen

ChildEBP RetAddr 

04f1fcfc 77c0f0a9 ntdll!DbgBreakPoint

...

04f1fd78 00000000 ntdll!_RtlUserThreadStart+0x23

 

Note that we will always see more threads than the ones we created. In a typical WinForms application we will see our main GUI thread, a Finalizer thread (Garbage Collector related), a GDI+ thread, a couple of debugger related threads, and any other thread we’ve created.

·         We can see all managed threads in our process:

Some threads in our process will contain some managed calls (managed threads), but not all (unmanaged threads). We can only focus on managed threads:

0:004> !Threads

ThreadCount: 2

UnstartedThread: 0

BackgroundThread: 1

PendingThread: 0

DeadThread: 0

Hosted Runtime: no

                                      PreEmptive   GC Alloc           Lock

       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception

   0    1 1f3c 003ec600      6020 Enabled  0198c744:0198dfe8 003b4bd8     0 STA

   2    2  904 003f5990      b220 Enabled  00000000:00000000 003b4bd8     0 MTA (Finalizer)

0:004> ~0kL

ChildEBP RetAddr 

0027e3a4 77afb5bc ntdll!KiFastSystemCallRet

0027e3a8 77af1598 USER32!NtUserWaitMessage+0xc

0027e3dc 77af1460 USER32!DialogBox2+0x202

00000001 00371a20 System_Windows_Forms_ni!System.Windows.Forms.MessageBox.Show(System.String)+0x26

00000001 7b062c9a WindowsApplication1!WindowsApplication1.Form1.Button6_Click(System.Object, System.EventArgs)+0xa8

...

0:004> ~2kL

ChildEBP RetAddr 

03b4f5b0 77be0690 ntdll!KiFastSystemCallRet

03b4f5b4 77a47e09 ntdll!ZwWaitForMultipleObjects+0xc

03b4f650 77a48150 KERNEL32!WaitForMultipleObjectsEx+0x11d

03b4f66c 79ef224b KERNEL32!WaitForMultipleObjects+0x18

03b4f68c 79fb997b mscorwks!WKS::WaitForFinalizerEvent+0x77

03b4f6a0 79ef3207 mscorwks!WKS::GCHeap::FinalizerThreadWorker+0x79

03b4f6b4 79ef31a3 mscorwks!ManagedThreadBase_DispatchInner+0x4f

03b4f748 79ef30c3 mscorwks!ManagedThreadBase_DispatchMiddle+0xb1

03b4f784 79fb9643 mscorwks!ManagedThreadBase_DispatchOuter+0x6d

03b4f7ac 79fb960d mscorwks!ManagedThreadBase_NoADTransition+0x32

03b4f7bc 79fba09b mscorwks!ManagedThreadBase::FinalizerBase+0xd

03b4f7f4 79f95a2e mscorwks!WKS::GCHeap::FinalizerThreadStart+0xbb

03b4f88c 77a43833 mscorwks!Thread::intermediateThreadProc+0x49

03b4f898 77bba9bd KERNEL32!BaseThreadInitThunk+0xe

03b4f8d8 00000000 ntdll!_RtlUserThreadStart+0x23

 

We may want to see only managed calls (!CLRStack) in all threads (~*):

0:004> ~*e !CLRStack

OS Thread Id: 0x1f3c (0)

ESP       EIP    

0027e6c8 77be0f34 [NDirectMethodFrameStandalone: 0027e6c8] System.Windows.Forms.SafeNativeMethods.MessageBox(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)

...

0027e780 7b28595e System.Windows.Forms.MessageBox.Show(System.String)

0027e788 00371a20 WindowsApplication1.Form1.Button6_Click(System.Object, System.EventArgs)

...

0027ecdc 00370117 WindowsApplication1.My.MyApplication.Main(System.String[])

0027ef24 79e7c74b [GCFrame: 0027ef24]

OS Thread Id: 0x1b08 (1)

Unable to walk the managed stack. The current thread is likely not a

managed thread. You can run !threads to get a list of managed threads in

the process

OS Thread Id: 0x904 (2)

Failed to start stack walk: 80004005

OS Thread Id: 0x1bbc (3)

Unable to walk the managed stack. The current thread is likely not a

managed thread. You can run !threads to get a list of managed threads in

the process

...

 

Note that we have to use ‘e’ with ‘~*’ to be able to execute a command from a debugger extension in all threads.

Note that thread 2 (Finalizer) is supposed to be a managed thread, but it doesn’t currently contain any managed calls in the call stack.

 

Next post: MANAGED DEBUGGING with WINDBG. Threads. Part 2.

Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.

 

Regards,

 

Alex (Alejandro Campos Magencio)