Using WinDbg to find .NET object references

Jun 22, 2024  

I was testing some code which relied on WeakReference to keep track of objects. My test called GC.Collect() in order to force the garbage collector to run and my objects to be recycled. However, for some reason which was not clear to me, a few objects were not collected.

I remembered vaguely Tess Ferrandez explaining how we could leverage an sos plug-in for WinDbg to explore the GC roots. It might have been the 2022 talk given at NDC in London (Advanced .NET Debugging session) or some blog post she wrote…

It has been years since I used WinDbg, so I had to rediscover the tool, which has been signficantly improved over the past 20 years.

Getting WinDbg

I headed over to Microsoft’s documentation on how to Install the Windows debugger and used the winget command line:

winget install Microsoft.WinDbg

However, WinDbg alone is not enough. To inspect .NET code, you have to install a plugin called SOS.dll (which now officially stands for SOS debugging extension but the old hats remember it as Son of Strike).

Installing SOS.dll is best done automatically by WinDbg itself. See below…

Attach to a process and load SOS.dll

As I wanted to debug some unit test running under Visual Studio test runner, I started my test, which I had modified to loop forever in the test which I wanted to debug. I then launched WinDbg and attached it to the test process testhost.exe.

To install and load SOS.dll I used following command:

!analyze –v

which as a side-effect loads the most compatible SOS.dll from some Microsoft servers. The trick is explained in details here.

Dumping the heap, looking for the culprit

In order to find all living instances of a type Foo, use the DumpHeap command:

!DumpHeap -type Foo

WinDbg dumps a table with the Address and MT (method table) of every object.

DumpHeap command output

Pick an address (e.g. 02170bc05310) and execute the GCRoot command:

!GCRoot 02170bc05310

This displays all places where a reference to the specified object is being held. From there, follow the references…

Be patient

The first time I attached to the testhost.exe process, I thought that WinDbg had gone unresponsive. Whatever command I tried, nothing happened. This is normal! Just be patient, as WinDbg has to first download the symbols for all the modules it discovered in the running process.

Only then does it make sense to try to load a source file and set some breakpoints. Trying to set the breakpoints while the debugger is still working won’t do anything visible. So, watch out for the status indicator which tells you that WinDbg is still working… and have a look at the logs (ribbon View > Logs).

View Logs

Additional resources