Using WinDBG/CDB and SOS to find a thread given a ManagedThreadId
CDB and SOS have more power than most people realize, I’ll use this post to show some some handy cdb/sos tricks while at the same answering the simple question of which thread a ManagedThreadId maps to.
At the end of this post you’ll be able to:
- Map a ManagedThreadId to the correct thread ordinal
- Use CDB to convert from decimal to hex
- Pipe CDB commands through pipeline commands like findstr
Full source for the simple app below can be found here. In the below app we want to figure which thread we’re running on so we can inspect the stack.
internal class Program { private static void SleepForever() { Thread.Sleep(Int32.MaxValue); } static void Main(string[] args) { const int countThreadsToStart = 20; foreach (var unused in Enumerable.Range(0, countThreadsToStart)) { // Instantiate thread that prints and stores its ManagedThreadId. new Thread(() => { // store ThreadId as an object as this forces boxing and lets us find the object on the stack via !dso in SOS. object boxedThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Find my threadId of {0}", boxedThreadId); SleepForever(); }).Start(); } SleepForever(); } }
Step 1, run this app in CDB:
PS C:\hgs\ig2600_blog\FindTheThread\bin> C:\bin_drop\public_dbg_64\cdb .\Debug\FindTheThread.exe Microsoft (R) Windows Debugger Version 6.11.0001.404 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\Debug\FindTheThread.exeFind my threadId of 3 Find my threadId of 4 Find my threadId of 5 Find my threadId of 6 Find my threadId of 7 Find my threadId of 8 Find my threadId of 9 Find my threadId of 10 Find my threadId of 11 Find my threadId of 12 Find my threadId of 13 Find my threadId of 14 Find my threadId of 15 Find my threadId of 16 Find my threadId of 17 Find my threadId of 18 Find my threadId of 19 Find my threadId of 20 Find my threadId of 21 Find my threadId of 22 (e28.c94): Break instruction exception - code 80000003 (first chance) ntdll!DbgBreakPoint: 00000000`7744ef90 cc int 3 0:025> .loadby sos clr
Lets say our goal is to debug what’s running on thread 21, how do we know which thread ordinal we should debug on? Luckily we have a !Threads command:
0:025> !threads ThreadCount: 22 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive Lock ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Excepti on 0 1 224c 000000000054dd80 200a020 Enabled 0000000002ac90b0:0000000002ac9fd0 00000000004ee680 0 MTA 2 2 c8c 0000000000553bd0 b220 Enabled 0000000000000000:0000000000000000 00000000004ee680 0 MTA (Finalizer) 4 3 1670 0000000000591620 200b020 Enabled 0000000002ac3be0:0000000002ac3fd0 00000000004ee680 0 MTA 5 4 1c84 0000000000578370 200b020 Enabled 0000000002ac4160:0000000002ac5fd0 00000000004ee680 0 MTA 6 5 1a54 000000000057a190 200b020 Enabled 0000000002ac6160:0000000002ac7fd0 00000000004ee680 0 MTA 7 6 2364 0000000000591f90 200b020 Enabled 0000000002aca160:0000000002acbfd0 00000000004ee680 0 MTA 8 7 b38 0000000000593570 200b020 Enabled 0000000002acc160:0000000002acdfd0 00000000004ee680 0 MTA 9 8 1c88 0000000000594df0 200b020 Enabled 0000000002ace160:0000000002acffd0 00000000004ee680 0 MTA 10 9 ef8 00000000005966b0 200b020 Enabled 0000000002ad0160:0000000002ad1fd0 00000000004ee680 0 MTA 11 a 21d0 0000000000597f70 200b020 Enabled 0000000002ad2160:0000000002ad3fd0 00000000004ee680 0 MTA 12 b 22a8 0000000000599830 200b020 Enabled 0000000002ad4160:0000000002ad5fd0 00000000004ee680 0 MTA 13 c 600 000000000059b0f0 200b020 Enabled 0000000002ad6160:0000000002ad7fd0 00000000004ee680 0 MTA 14 d 2388 000000000059c9b0 200b020 Enabled 0000000002ad8160:0000000002ad9fd0 00000000004ee680 0 MTA 15 e 19b8 000000000059e270 200b020 Enabled 0000000002ada160:0000000002adbfd0 00000000004ee680 0 MTA 16 f 1e9c 000000000059fb30 200b020 Enabled 0000000002adc160:0000000002addfd0 00000000004ee680 0 MTA 17 10 1c9c 00000000005a13f0 200b020 Enabled 0000000002ade160:0000000002adffd0 00000000004ee680 0 MTA 18 11 20c4 00000000005a2d40 200b020 Enabled 0000000002ae0160:0000000002ae1fd0 00000000004ee680 0 MTA 19 12 14bc 00000000005a4600 200b020 Enabled 0000000002ae2160:0000000002ae3fd0 00000000004ee680 0 MTA 20 13 1994 00000000005a5ec0 200b020 Enabled 0000000002ae4160:0000000002ae5fd0 00000000004ee680 0 MTA 21 14 19bc 00000000005a7780 200b020 Enabled 0000000002ae6160:0000000002ae7fd0 00000000004ee680 0 MTA 22 15 1ac 00000000005a9040 200b020 Enabled 0000000002ae8160:0000000002ae9fd0 00000000004ee680 0 MTA 23 16 2084 00000000005aa900 200b020 Enabled 0000000002aea160:0000000002aebfd0 00000000004ee680 0 MTA 0:025>
In this output, the first column is the thread ordinal (which is what the ~ operator works on). The second column, is the OSID which is the ManagedThreadID, but is inconveniently the value is displayed in hexdecimal. To continue we need to find the thread ordinal, given an OSID of 21 in base 16. To convert 21 to hex, use the evaluation operator (?), and pass in an explicitly base 10 number by pre-pending 0n:
0:025> ? 0n21 Evaluate expression: 21 = 00000000`00000015 0:025>
Now, re-run !threads, but pipe the output through findstr 15 . Findstr can be run via the .shell command:
0:025> .shell -ci "!Threads" findstr 15" 15 e 19b8 000000000059e270 200b020 Enabled 0000000002ada160:0000000002adbfd0 00000000004ee680 0 MTA 22 15 1ac 00000000005a9040 200b020 Enabled 0000000002ae8160:0000000002ae9fd0 00000000004ee680 0 MTA
OSID 15, is thread 22, lets switch to it with the ~s operator:
0:025> ~22s ntdll!NtDelayExecution+0xa: 00000000`7745009a c3 ret
To verify we have the correct thread, we can dump the stack and inspect the boxedThreadId Object. We do this with !dso to (dump stack objects) and then run !do (dump object) on the int32.
0:022> !dso OS Thread Id: 0x1ac (22) RSP/REG Object Name 0000000021F7E4C0 0000000002ac2110 Microsoft.Win32.SafeHandles.SafeFileHandle 0000000021F7E590 0000000002ac3048 System.IO.StreamWriter 0000000021F7E5C0 0000000002ac3318 System.Byte[] 0000000021F7E5E0 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E638 0000000002ae7fe8 System.Int32 0000000021F7E640 0000000002ac2070 System.String Find my threadId of {0} 0000000021F7E680 0000000002ac1fe8 System.Threading.ContextCallback 0000000021F7E698 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E6A0 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E6B0 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E6C0 0000000002ac3490 System.IO.TextWriter+SyncTextWriter 0000000021F7E6D0 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E6D8 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E6E0 0000000002ae7fe8 System.Int32 0000000021F7E6F0 0000000002ac8ea0 System.Threading.Thread 0000000021F7E700 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E748 0000000002ac1fe8 System.Threading.ContextCallback 0000000021F7E750 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E760 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E770 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E7A0 0000000002ac8f60 System.Threading.ExecutionContext 0000000021F7E7B0 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E7C0 0000000002ac8ef8 System.Threading.ThreadHelper 0000000021F7E810 0000000002ac8f20 System.Threading.ThreadStart 0000000021F7E978 0000000002ac8f20 System.Threading.ThreadStart 0000000021F7EBB0 0000000002ac8f20 System.Threading.ThreadStart 0000000021F7EBC8 0000000002ac8f20 System.Threading.ThreadStart 0:022>!do 0000000002ae7fe8 Name: System.Int32 MethodTable: 000007fee50bc848 EEClass: 000007fee4c40890 Size: 24(0x18) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields: MT Field Offset Type VT Attr Value Name 000007fee50bc848 400047b 8 System.Int32 1 instance 21 m_value 0:022>
Note the value is 21, exactly what we were looking for! Leave a comment if you have things you’d like to see me debug.
Comments