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)