This sixth post of the ClrMD series details how to make object fields navigation simple with C# like syntax thanks to the dynamic infrastructure. The associated code is part of the DynaMD library and is available on GitHub and nuget.
Part 1: Bootstrap ClrMD to load a dump.
Part 2: Find duplicated strings with ClrMD heap traversing.
Part 3: List timers by following static fields links.
Part 4: Identify timers callback and other properties.
Part 5: Use ClrMD to extend SOS in WinDBG.
As we’ve seen in the previous articles of the series, exploring a complex data structure using ClrMD can quickly become tedious.
Let’s take a concrete example. Imagine we have those types declared:
Given the address of the Sample object in the memory dump, even with the GetFieldValue helper method to make it simpler, the code to navigate these recursive data types is still… verbose:
And now, how to get the value of the Name property?
Same question for the inner Child fields or deeper Size field of its Description?
Wouldn’t that be great if we could navigate just like through real strongly typed instances? In short, to be able to write:
The first issue is: what is the GetProxy method going to return? Since we don’t know at compilation time the properties of the object the code is going to manipulate, we need a way to support some kind of late-binding. Fortunately, this scenario is supported in C# through the usage of the dynamic keyword. As you will see in the rest of this post, this is not only a keyword but also an extensible mechanism that perfectly fits our need to define fields at runtime instead of compile time.
We start by creating a class inheriting from System.Dynamic.DynamicObject.
This base class provides all the facilities needed for late-binding:
As you will see, only a few of these virtual methods need to be overridden to support our scenario.
To construct our proxy, we need two parameters: the ClrMD ClrHeap object, that allows us to browse the objects in the memory, and the address of the object we want to impersonate.
We also provide an extension method for convenience:
The next step is to override the virtual TryGetMember method, inherited from DynamicObject. It is automatically invoked whenever somebody tries to access a any member of the dynamic object, including its fields.
The Name property of the binder parameter provides the name of the accessed member and we are supposed to return the corresponding proxy object as the out result parameter.
We’re going to need the type of the object. For convenience, we store it in a property:
Using the binder.Name property containing the name of the field we’re trying to access, we retrieve the ClrMD field description:
From there, we get the value marshalled by ClrMD and assign it to the result out parameter:
Finally, we signal that we managed to bind the invoked member:
This is just a handful of lines of code, but it’s enough for the simple cases where field values are primitive types.. This covers the “Value” field for our Sample type. For the auto-property “Name”, that’s trickier, because the name of the underlying field has characters that are forbidden in C#: “<Name>k__BackingField”. If we write this, it won’t compile:
We can handle this case by guessing the name of the compiler-generated field, then accessing it:
Thanks to this trick, we can write:
Great! The next challenge is to transparently manipulate the “Child” field as a reference to another “Sample” object. To achieve this goal, the field could simply return another DynamicProxy object that we can manipulate the same way as its parent.
First, we need a helper to find out whether a value is a reference or not:
We treat string as a special case, because ClrMD gives us the marshaled string rather than a reference like for all other types. That’s how we were able to retrieve the value of the Name field previously.
Now we call the helper and return a new proxy whenever we’re dealing with a reference:
We can now write:
That’s it for accessing a referenced object allocated on the heap. However, this won’t work for accessing an embedded struct such as proxy.Description.Id. We’ll see in the next part how to handle this specific case.
Post written by:
Staff Software Engineer, R&D.
Staff Software Engineer, R&D.
Our lovely Community Manager / Event Manager is updating you about what's happening at Criteo Labs.See DevOps Engineer roles