Finding an instance of thread by id in WinDbg
As you may already know, it’s possible to list all the managed threads in a .NET memory dump using the !threads
command:
The “ID” column gives you the managed thread id, which is the same value that you could retrieve from the code by calling thread.ManagedThreadId
. Let’s say we are interested by the thread with the managed id “16”.
You can switch the active thread by retrieving the value of the “DBG” column (so for the thread with managed id 16, that would be “20”) and giving it to the ~
command:
When switching thread, WinDbg shows the top frame of the native callstack (here, ntdll!NtDelayExecution+0x14
) and changes the prompt to 0:020>
to show that thread 20 is now the active thread.
But what if you’re interesting in getting the instance of System.Threading.Thread
associated to this thread?
The fourth column of the output of !threads
is named “ThreadOBJ” and contains a value that looks like a pointer. From there, you might be tempted to think this is the address of the thread object, but unfortunately…
… this is the address of the native thread object, not the managed one.
So how can we retrieve the address of the managed thread? We could enumerate all the threads with !dumpheap -mt
and inspect all of them individually until finding the right one, but that’s going to be really tedious if you have tens, or hundreds of threads. Wouldn't that be great if we could just call Thread.CurrentThread
from WinDbg?
Well it turns out we kind of can. Following an optimization in .NET Core 3.0, the value of Thread.CurrentThread
is now stored in a thread-static field.
To retrieve the value, we first need to retrieve the EEClass address:
We can then give that value to the !dumpclass
command (or click on the DML link next to EEClass:
):
The last line (the one after the t_currentThread
field) gives the value of the field for each thread. The id used is the “OS ID” given by the !threads
command:
So in our case we just need to find the value prefixed by 1dc8
:
>> Thread:Value 359c:000001c53436bf88 1940:000001c53436be40 7214:000001c53436c050 7034:000001c53436c128 6e38:000001c53436c200 7220:000001c53436c2d8 5684:000001c53436c3b0 4364:000001c53436c488 63ec:000001c53436c560 1970:000001c53436c638 6bcc:000001c53436c710 6890:000001c53436c7e8 69d0:000001c53436c8c0 1dc8:000001c53436c998 1db4:000001c53436ca70 6aa8:000001c53436cb48 6b94:000001c53436cc20 628:000001c53436ccf8 <<
Then we can use the !dumpobj
command to confirm that this is indeed the address of the instance of System.Threading.Thread
associated to the thread with managed id 16:
Of course this won’t work if you’re using a version of .NET older than 3.0. In that case, well, I hope you like scripting in WinDbg 😉