MANAGED DEBUGGING with WINDBG. Threads. Part 1
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)