Saturday, July 02, 2011

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);

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)
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
   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

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

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
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
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee50bc848  400047b        8         System.Int32  1 instance               21 m_value

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.

1 comment:

Russell Troywest said...

Thank you Igor. I've just spent the last hour trying to work out why my callstack wasn't what I was expecting for a specific thread. I'd completely forgotten the OSID was the ManagedThreadId :)