Analyzing Live Process Memory with ClrMD

Update 29th December 2015: As noted in the comments, the API has changed since I wrote this article, and some methods have been deprecated. See the new section at the end for updated full source code. The rest of the article has not yet been updated.

There are many ways to debug and inspect a live process or a crash dump. Unfortunately, many of these are very low-level and can be a bit of a nightmare to use. However, a relatively new library called ClrMD, released in beta 2 years ago, provides a convenient abstraction and makes it much easier to do this kind of work.

You can install the ClrMD NuGet package from the NuGet package manager. However, since this is still prerelease software, you’ll need to enable the option to include prerelease packages:

clrmd-nuget

Once you have that installed, you can use ClrMD via the following namespace:

using Microsoft.Diagnostics.Runtime;

In this example, we’ll attach to a running process, but ClrMD also allows you to inspect a crash dump. In order to attach, we need the process id (PID). You can get this either from Task Manager…

clrmd-pid

…or by looking for the process in code (needs System.Diagnostics):

            var process = Process.GetProcessesByName("Dandago.Mail.ImapTalk").FirstOrDefault();

            if (process != null)
            {
                int pid = process.Id;

                // TODO rest of code goes here
            }
            else
                Console.WriteLine("Process not found!");

Once you have the PID, you can attach to the process as follows.

                const int timeout = 5000;

                using (var dataTarget = DataTarget.AttachToProcess(pid, timeout))
                {
                    // TODO rest of code goes here
                }

Before you can inspect objects in memory, you need to get your hands on the heap in the process you attached to. To do this, we need to go through the following steps:

  1. Get the CLR runtime version(s) loaded in the process.
  2. From that, get this library called DAC (Data Access Component, implemented in mscordacwks.dll), which provides debugging functionality.
  3. Based on this, we can create an instance of ClrRuntime.
  4. From ClrRuntime, we get a ClrHeap.

These steps are performed in the code below:

                    var clrVersion = dataTarget.ClrVersions.FirstOrDefault();
                    string dacLocation = clrVersion.TryGetDacLocation();
                    var runtime = dataTarget.CreateRuntime(dacLocation);
                    var heap = runtime.GetHeap();

ClrHeap gives us a handy EnumerateObjects() method that allows us to see all the objects in memory. If we want to just get a list of all of them, it’s a simple matter to iterate through them and write out their types:

                    foreach (var obj in heap.EnumerateObjects())
                    {
                        var type = heap.GetObjectType(obj).Name;
                        Console.WriteLine(type);
                    }

This gives us a long list of object types in memory:

clrmd-typelist

There are lots of things we can do with these objects. As a slightly more elaborate example, we can see a list of types ordered by the most common:

                    var types = heap.EnumerateObjects()
                        .GroupBy(obj => heap.GetObjectType(obj).Name)
                        .Select(group => new { Key = group.Key, Count = group.Count() })
                        .OrderBy(type => type.Count);

                    foreach(var type in types)
                        Console.WriteLine("{0} {1}", type.Key, type.Count);

The output is thus:

clrmd-mostcommontypes

Further Reading

Full Source Code

        static void Main(string[] args)
        {
            Console.Title = "ClrMD Live Process Analysis";

            var process = Process.GetProcessesByName("Dandago.Mail.ImapTalk").FirstOrDefault();

            if (process != null)
            {
                int pid = process.Id;
                const int timeout = 5000;

                using (var dataTarget = DataTarget.AttachToProcess(pid, timeout))
                {
                    var clrVersion = dataTarget.ClrVersions.FirstOrDefault();
                    string dacLocation = clrVersion.TryGetDacLocation();
                    var runtime = dataTarget.CreateRuntime(dacLocation);
                    var heap = runtime.GetHeap();

                    var types = heap.EnumerateObjects()
                        .GroupBy(obj => heap.GetObjectType(obj).Name)
                        .Select(group => new { Key = group.Key, Count = group.Count() })
                        .OrderBy(type => type.Count);

                    foreach (var type in types)
                        Console.WriteLine("{0} {1}", type.Key, type.Count);
                }
            }
            else
                Console.WriteLine("Process not found!");

            Console.ReadLine();
        }

Full Source Code after API Change

Update 29th December 2015: the API changed since this article was written, and some methods have been deprecated. The full code below is an updated version to work with version 0.8.31-beta of ClrMD.

        static void Main(string[] args)
        {
            Console.Title = "ClrMD Live Process Analysis";

            var process = Process.GetProcessesByName("Dandago.Mail.ImapTalk").FirstOrDefault();

            if (process != null)
            {
                int pid = process.Id;
                const int timeout = 5000;

                using (var dataTarget = DataTarget.AttachToProcess(pid, timeout))
                {
                    var clrVersion = dataTarget.ClrVersions.FirstOrDefault();
                    string dacLocation = dataTarget.SymbolLocator.FindBinary(clrVersion.DacInfo);
                    var runtime = clrVersion.CreateRuntime(dacLocation);
                    var heap = runtime.GetHeap();

                    var types = heap.EnumerateObjectAddresses()
                        .GroupBy(obj => heap.GetObjectType(obj).Name)
                        .Select(group => new { Key = group.Key, Count = group.Count() })
                        .OrderBy(type => type.Count);

                    foreach (var type in types)
                        Console.WriteLine("{0} {1}", type.Key, type.Count);
                }
            }
            else
                Console.WriteLine("Process not found!");

            Console.ReadLine();
        }

5 thoughts on “Analyzing Live Process Memory with ClrMD”

  1. Hi,
    Thank you very much for article. I have downloaded the latest nuget package i.e, 0.8.31 and few of the methods are obsolete.

    like:
    clrVersion.TryGetDacLocation();
    dataTarget.CreateRuntime(dacLocation);

    Please let me know which methods to use in place of obsolete methods.
    Thank you.

      1. Thank you very much for your help and for speedy response. I got that method. Looks like many more methods are obsolete. GetHeap is Obsolete and heap.EnumerateObjects() is Deprecated. I searched link you have given but I couldn’t find the replacement for these methods. Please let me know which method to use.

        Thanks in advance. You have a good day.

        1. You can find the code you need in the .NET samples at the same location. I’ve added a new section at the end of this article with updated (working) code.

Leave a Reply

Your email address will not be published. Required fields are marked *