What’s New in CLR 4.5 Debugging API?

April 3, 2012

The most used command in the SOS extension DLL is probably !dumpheap. This command is able to show every object on the managed heap. This capability has no match within Visual Studio. The reason is that Visual Studio uses the CLR debugging API, that doesn’t seem to have this capability.

At first glance, the ICorDebugProcess interface has the perfect method: EnumerateObjects. What could be better than that? Unfortunately, the documentation states that this method is not implemented… bummer.

A while back I created a project CLR Explorer. This was supposed to be a tool to look at managed processes. Looking at metadata was easy enough, but runtime information was scarce, as the debug interfaces did not have a way to get managed heap information.

With .NET 4.5. beta released, I looked through the debug interfaces. I wanted to see if anything changed since CLR 4. I discovered there is a new interface supposedly supported on a CorDebugProcess object, ICorDebugProcess5. This new interface (the previous one was ICorDebugProcess3 – clearly the number 4 was to be avoided to not give the false impression that this is available with CLR 4).

This new interface has a method named EnumerateHeap – great! finally… and the method seems to be documented as having an actual implementation.

So, I decided to give it a try…

Attaching to a Process

Warning! C++ (and COM) ahead!

First, let’s get the CLR meta host object:

  1. CComPtr<ICLRMetaHost> spMetaHost;
  2. hr = ::CLRCreateInstance(CLSID_CLRMetaHost, __uuidof(ICLRMetaHost), (void**)&spMetaHost);

This object is the root for starting to work with the CLR (debugging or not). It was introduced in CLR 4.

We need to get the ICorDebug interface. This is how it’s done (hProcess is a handle to the process we want to attach to – obtained using the usual OpenProcess API):

  1. CComPtr<IEnumUnknown> spEnumUnk;
  2. spMetaHost->EnumerateLoadedRuntimes(hProcess, &spEnumUnk);
  3. CComPtr<IUnknown> unk;
  4. spEnumUnk->Next(1, &unk, NULL);
  5. CComQIPtr<ICLRRuntimeInfo> spInfo(unk);
  6. ATLASSERT(spInfo);
  7. CComPtr<ICorDebug> spDebug;
  8. spInfo->GetInterface(CLSID_CLRDebuggingLegacy, __uuidof(ICorDebug), (void**)&spDebug);

(Error handling is mostly non existing to keep the code short)

Now we need to initialize the ICorDebug object and attach to the process to debug:

  1. hr = spDebug->Initialize();
  2. CComObject<CDebugManagedCallback>* pHandler;
  3. pHandler->CreateInstance(&pHandler);
  4. spDebug->SetManagedHandler(pHandler);
  5.  
  6. CComPtr<ICorDebugProcess> spProcess;
  7. hr = spDebug->DebugActiveProcess(pid, FALSE, &spProcess);

The CDebugManagedCallback class is a local C++ COM class that is the handler for debugging events. I will not show this one here (you can check out the CLR Explorer project to see one such class in action). For the purpose of this post, it really is just there so ICorDebug would be happy.

Now for the fun part. Can we get an ICorDebugProcess5? If we can, let’s use it:

  1. CComQIPtr<ICorDebugProcess5> spProcess5(spProcess);
  2. if(spProcess5) {
  3.     COR_HEAPINFO heapInfo;
  4.     hr = spProcess->Stop(0);
  5.     hr = spProcess5->GetGCHeapInformation(&heapInfo);
  6.     cout << "GC Type: " << (heapInfo.gcType == CorDebugWorkstationGC ? "Workstation" : "Server") << endl;
  7.     cout << "Heaps: " << heapInfo.numHeaps << endl;
  8.     if(heapInfo.areGCStructuresValid) {
  9.         CComPtr<ICorDebugHeapEnum> spEnumHeap;
  10.         hr = spProcess5->EnumerateHeap(&spEnumHeap);
  11.         ULONG count;
  12.         hr = spEnumHeap->GetCount(&count);
  13.         if(SUCCEEDED(hr)) {
  14.             // unfortunately… hr = E_NOTIMPL
  15.             cout << "# Objects: " << count << endl;
  16.         }
  17.         COR_HEAPOBJECT obj;
  18.         hr = spEnumHeap->Next(1, &obj, nullptr);    // works!
  19.         CComPtr<ICorDebugType> spType;
  20.         spProcess5->GetTypeForTypeID(obj.type, &spType);
  21.         if(spType) {
  22.             CorElementType type;
  23.             spType->GetType(&type);
  24.             cout << type << endl;
  25.         }
  26.     }
  27. }

The ICorDebugProcess5::GetGCHeapInformation gets some basic info for the heap. Then we can enumerate the objects on the managed heap. The GetCount method on the enumeration interface is not implemented, which makes perfect sense, since this information is unknown to the CLR, would be expensive to keep track of, and adds no real value.

The Next method however, works! Now we can enumerate the heap!

A new version of CLR Explorer is certainly due!

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Richard FencelApril 17, 2017 ב 03:44

    Your CLR Explorer Zip file appears to contain only the executeable. Do you have the source available? I am trying to create a CDebugManagedCallback class.

    Reply