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.exe
Find 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.