The Debugger Extension, Part 5: Manipulating Managed Types
November 26th, 2005

The Debugger Extension

In the last post in this series, we succeeded in writing a working extension that searched memory for instances of a particular type. Trouble is, we haven’t done anything useful yet. We’ve merely duplicated a very small subset of the functionality offered by SOS’s !DumpHeap command, and poorly at that.

In the problem setup, we said we wanted to show statistics about a particular property of these instances—for the purposes of this example, we’re calling that their “Color.” A sensible step in this direction would be to write some utility C++ code to accompany the Colors enumeration that we wrote earlier in C#.


    // Some definitions that correspond to the managed
    // SampleApp.Colors enum.
    typedef ULONG Color;
    const Color COLOR_RED = 0;
    const Color COLOR_GREEN = 1;
    const Color COLOR_BLUE = 2;
    const Color COLOR_PURPLE = 3;
    const Color MAX_COLOR = COLOR_PURPLE;

    PCSTR g_szColorNames[] = { "Red", "Green",
        "Blue", "Purple" };

    bool IsColor(Color c)
    {
        if( c <= MAX_COLOR )
        {
            return true;
        }
        return false;
    }

    PCSTR ColorName(Color c)
    {
        if( !IsColor(c) )
        {
            return NULL;
        }
        return g_szColorNames[static_cast<int>(c)];
    }

  

Part of the point of this series has been to develop a framework that deals with instances of .NET objects. To that end, we should try to write a generic base class that loads a managed instance in the debuggee into the debugger’s process. This is my implementation of such a class.


    // ------------------------------------------------------
    // mtypes.h
    //        Some base classes for dealing with instances
    //        of managed objects.
    //
    #pragma once

    template<class object_fields>
    class ManagedInstance
    {
    protected:
        object_fields m_Fields;
        ULONG64 m_offset;
        bool m_valid;

        // Can be used by derived classes can to refer
        // to this class.
        typedef ManagedInstance<object_fields> base_t;

        // Constructor - pass the offset of the managed
        // object. Check the result of IsValid() before
        // using an instance derived from this class.
        ManagedInstance(ULONG64 offset) : m_Fields()
        {
            // Load the data for the fields from the
            // debugee process / memory dump
            IDebugDataSpaces* pData = g_Ext->m_Data;
            m_offset = offset;
            ULONG read = 0L;
            HRESULT hr = pData->ReadVirtual(
                m_offset,
                reinterpret_cast<void*>(&m_Fields),
                sizeof(object_fields),
                &read);
            m_valid = SUCCEEDED(hr) &&
                (read == sizeof(object_fields));
        }

        virtual ~ManagedInstance() {}

    public:
        // Returns true if the object was successfully
        // read from the debugee. Doesn't validate that
        // it actually is a managed object of the desired
        // type, but overridden implementations should
        // do this.
        virtual bool IsValid() { return m_valid; }
    };

    // This can be used as a base class for classes used
    // as the object_fields template parameter.
    class ObjectFields
    {
    public:
        ULONG pMethodTable;
    };

  

The template parameter for the ManagedInstance class takes a POD (“plain old data”) type that should just list the fields in the instance. The constructor for ManagedInstance loads the data at the specified offset as those fields. I declared a virtual function that indicates whether or not the managed instance is valid. In this base class, all we can really say about that is whether or not we could read the data at the provided address.

I’ve also defined a base class for the fields of a managed object, and put the MethodTable address in it. Given these classes, it’s not a lot of work to write the implementations for ArbitraryType.


    // Represents the fields of a
    // SampleApp.ArbitraryType instance.
    class ArbitraryTypeFields
         : public ObjectFields
    {
    public:
        Color col;
        ULONG id;
    };

    // Represents a single ArbitraryType instance.
    class ArbitraryType :
        public ManagedInstance<ArbitraryTypeFields>
    {
    public:
        ArbitraryType(ULONG64 offset)
            : base_t(offset) {}
        virtual bool IsValid();
        Color GetColor() { return m_Fields.col; }
    };

    // Returns true if the loaded data is a valid
    // ArbitraryType instance.
    bool ArbitraryType::IsValid()
    {
        if( base_t::IsValid() &&
            IsColor(m_Fields.col) )
        {
            return true;
        }
        return false;
    }

  

In the last post, the only criteria we used for finding ArbitraryType instances was that we had found a INT_PTR containing the address of its MethodTable. That’s obviously going to result in false positives, because the CLR’s execution engine will certainly have this pointer in several places in its own internal data structures. We can do a little better now by making sure that the Color field is within the range of expected values. While we’re changing our HandleMatch function to implement this, we’ll add an STL map to the mix to keep track of the colors we find.


    class AtStatCmd : public SearchCommand
    {
    protected:
        typedef map<Color, int>  CountMap_t;
        CountMap_t m_counts;

    public:
        virtual void ShowResults(int totalHits);
        virtual bool HandleMatch(ULONG64 offset);
        AtStatCmd();
    };

    bool AtStatCmd::HandleMatch(ULONG64 offset)
    {
        ArbitraryType at(offset);
        if( at.IsValid() )
        {
            Color c = at.GetColor();
            g_Ext->Out("%08I64x : %s\n",
               offset, ColorName(c));
            m_counts[c]++;
            return true;
        }
        return false;
    }

  

The output of the extension in WinDbg now looks like this:

0:000> !atstat!atstat 009131b0
Searching for ArbitraryTypes...
--------------------------------------------
Searching 00000000 to 7fffffff.
009100cc : Red
009130e0 : Red
01271ce4 : Red
01271d00 : Blue
01271d1c : Red
01271d38 : Blue
01271d54 : Red
--------------------------------------------
Found 7 total instances.
Totals:
Red: 5
Blue: 2

There’s one obvious problem with this that I can think of, and that is the fact these objects are not necessarily alive (rooted) on the GC heap. They could very well be collected and sitting in freed memory. In my case, this is not a concern since I am interested mostly in debugging leaked memory. This may be an issue for other users, however.

Since the extension we set out to build is basically complete, I’ll post the code now even though I have one more feature in mind for the next post. You can download the code here.