Finding an instance of thread by id in WinDbg

Kevin Gosse
2 min readSep 16, 2021

--

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 😉

--

--

Kevin Gosse
Kevin Gosse

Written by Kevin Gosse

Software developer at Datadog. Passionate about .NET, performance, and debugging.