Getting Started with ClrMD
07/12/2025
5 minutes
What is ClrMD?
ClrMD or Microsoft.Diagnostics.Runtime
NuGet package is a helpful tool to analyze crash dumps or debug applications at runtime.
This post discusses scenarios where this tool excels compared to other diagnostic tools. Its capabilities are comparable to WinDBG with the SOS extension or PerfView. As a NuGet package, it also provides common CLR abstractions enabling developers to build their own diagnostic tools.
In most investigations, the usual tools such as WinDBG + SOS, PerfView, Visual Studio's performance profiler, etc. provide clear insights into the application. However, in rare cases, a developer might want to write custom scripts or code for deeper analysis not available through standard commands or views. In these cases, ClrMD comes to the rescue.
This blog post demonstrates a few of these use cases by analyzing a memory dump. Note that a memory dump captures a snapshot of an application's memory address space at a single point in time. A single snapshot does not provide a comprehensive view of the application's lifetime or behavior under different workloads (e.g., bursty vs. steady loads).
Setup
Create a new console app
dotnet new console
.Add Microsoft.Diagnostics.Runtime NuGet package
dotnet add package --prerelease Microsoft.Diagnostics.Runtime
.Install dotnet-dump:
dotnet tool install dotnet-dump --global
.List running .NET processes:
dotnet-dump ps
.Create a heap dump:
dotnet-dump collect -p <pid> --type heap
.
ClrMD's concepts are described in its Getting Started. At the time of writing most samples and snippets refer to version 2 of the library, while the latest preview version is 4.0.0-beta.24360.3
.
Viewing Generation 0 Garbage
Task: List all unrooted objects in Generation 0. WinDBG + SOS, dotnet-dump is good at listing all objects and finding the roots (dumpheap, gcroot) for a given object. PerfView can analyze traces and allocated objects (GC Heap Alloc Ignore Free - optionally with .NET Alloc/.NET SampAlloc).
It can be interesting to see which types are allocated and immediately become garbage. Sampling allocations shows what types are allocated, and usually, the number of allocations is a good indicator of short-lived objects - but these objects may be also promoted to Gen2. With ClrMD, this can be confirmed by investigating object roots.
With ClrMD a developer can load a dump file and enumerate all objects within Generation 0:
// 👇 Open the memory dump using DataTarget dataTarget = DataTarget.LoadDump(@"D:\perf\dump_heap2.dmp"); ClrInfo runtimeInfo = dataTarget.ClrVersions[0]; // using the first CLR ClrRuntime runtime = runtimeInfo.CreateRuntime(); var heap = runtime.Heap; Dictionary<ulong, ClrObject> capturedObjects = new(); // Iterate all objects that are in Gen0. foreach (var subHeap in heap.SubHeaps) { foreach (ClrSegment segment in subHeap.Segments) { if (segment.Kind != GCSegmentKind.Generation0) continue; foreach (ClrObject obj in segment.EnumerateObjects()) { // Always check for the validity of an object. if (!obj.IsValid) continue; capturedObjects.Add(obj.Address, obj); } } }
Next, iterate all objects reachable from the roots:
var live = new HashSet<ulong>(); var roots = new Stack<ClrObject>(heap.EnumerateRoots().Where(x => x.Object.IsValid).Select(x => x.Object).ToList()); while (roots.Count > 0) { var current = roots.Pop(); if (!current.IsValid) continue; // Add object to a collection of live objects. live.Add(current.Address); // Iterate all references of the current object; carefully: only considers references pointing onto the managed heap foreach (var referencedObject in current.EnumerateReferences(carefully: true, considerDependantHandles: false)) { if (live.Contains(referencedObject.Address) || roots.Contains(referencedObject)) continue; // Skip already visited objects roots.Push(referencedObject); } }
Finally, a developer may filter the capturedObjects
with live
objects. This filtering may contain additional conditions to only consider types from certain namespaces or to ignore null, free and runtime objects.
Note that if the snapshot is captured after a GC, the Gen0 might be empty (as all live objects are promoted to Gen1).
Viewing Generation 1 Live Objects
Task: List all Gen1 objects that are reachable from roots. This analysis helps identify objects likely to reach Gen2. Since Gen2 is typically large, analyzing all its objects can be challenging. A single dump during a bursty workload can indicate which objects are promoted to Gen2 but might be collected soon after.
With traces, PerfView provides a view of Gen 2 Object Deaths, that can also indicate objects that reach Gen2 only to become dead soon after. However, this view and analysis is only available for trace files, not memory dumps. With ClrMD the same (with filtering GCSegmentKind.Generation1
) approach as described in View Gen0 Garbage can be applied.
Finding a Known Object
Task: Find an object with a known content. Developers often need to search for objects with specific content, such as a known string
value. While WinDBG + SOS or dotnet-dump are good at listing all objects, finding one based on its content is challenging. Scripting is possible with WinDBG, it is far from convenient.
With ClrMD one can easily get the type and content of an object:
// Example of finding a specific string object if (obj.Type.Name == "System.String") { string value = obj.AsString(); if (value == "searchText") { // Found matching string } }
Conclusion
ClrMD provides a powerful programmatic interface for analyzing .NET application memory dumps. While tools like WinDBG+SOS, PerfView, and Visual Studio's profiler are excellent for common debugging scenarios, ClrMD shines when you need to:
Write custom analysis scripts for specific debugging scenarios
Analyze Generation 0/1 objects and their promotion patterns
Search for objects with specific content
Automate memory analysis as part of a larger toolchain
The library's key strengths include:
Easy integration via NuGet
Familiar C# API for .NET developers
Flexible object traversal and filtering capabilities
Ability to analyze both live processes and memory dumps
Remember that memory dumps are snapshots in time - for a complete understanding of your application's memory behavior, you may need to analyze multiple dumps or combine ClrMD analysis with other profiling approaches.