Поиск данных экземпляра типа в куче.net

Допустим, у меня есть два класса Foo и Bar следующим образом

public class Foo
{
    private Bar _bar;
    private string _whatever = "whatever";
    public Foo()
    {
        _bar = new Bar();
    }

    public Bar TheBar
        {
            get
            {
                return _bar;
            }

        }
}

public class Bar
{
    public string Name { get; set; }
}

У меня есть приложение, которое подключается к процессу, который использует эти классы. Я хотел бы видеть все экземпляры типа Foo в куче.NET и проверить свойство TheBar.Name или поле _whwh всех экземпляров Foo, присутствующих в куче.NET. Я могу найти тип, но я не уверен, как получить экземпляр и увидеть его свойства. Есть идеи как?

using (DataTarget target = DataTarget.AttachToProcess(processId, 30000))
{
    string dacLocation = target.ClrVersions[0].TryGetDacLocation();
    ClrRuntime runtime = target.CreateRuntime(dacLocation);

    if (runtime != null)
    {
        ClrHeap heap = runtime.GetHeap();
        foreach (ulong obj in heap.EnumerateObjects())
        {
            ClrType type = heap.GetObjectType(obj);
            if (type.Name.Compare("Foo") == 0 )
            {
                // I would like to see value of TheBar.Name property or _whatever field of all instances of Foo type in the heap. How can I do it?
            }
        }
    }
}

3 ответа

Решение

Я не думаю, что вы можете получить значения свойств напрямую, потому что это потребует от вас выполнения кода, и целью может быть даже не процесс, а файл дампа.

Вы можете определенно получить поля объекта и их значения. ClrType имеет свойство Fields, которое вы можете использовать для циклического перемещения по полям. Затем вы можете вызвать GetFieldValue для полей, в которых значение HasSimpleValue равно true.

Простой пример:

private static void PrintFieldsForType(ClrRuntime runtime, string targetType)
{
    ClrHeap heap = runtime.GetHeap();
    foreach (var ptr in heap.EnumerateObjects())
    {
        ClrType type = heap.GetObjectType(ptr);
        if (type.Name == targetType)
        {
            foreach(var field in type.Fields)
            {
                if (field.HasSimpleValue)
                {
                    object value = field.GetFieldValue(ptr);
                    Console.WriteLine("{0} ({1}) = {2}", field.Name, field.Type.Name, value);
                }
                else
                {
                    Console.WriteLine("{0} ({1})", field.Name, field.Type.Name);
                }
            }
        }
    }
}

Таким образом, вы можете найти поле, в котором есть "Имя", "_имя" или что-то подобное. Если это автоматически реализованное свойство, имя будет примерно таким <Name>k__BackingField,

Ваш сценарий немного сложнее, потому что вы хотите перейти к другому объекту. Для этого мы можем рекурсивно проверить поля. Тем не менее, обратите внимание, что в общем случае вы бы хотели отслеживать, какие объекты вы посетили, чтобы не повторять бесконечно.

Вот пример, который больше подходит для этого:

private static void PrintFieldsForType(ClrRuntime runtime, TextWriter writer, string targetType)
{
    ClrHeap heap = runtime.GetHeap();
    foreach (var ptr in heap.EnumerateObjects())
    {
        ClrType type = heap.GetObjectType(ptr);
        if (type.Name == targetType)
        {
            writer.WriteLine("{0}:", targetType);
            PrintFields(type, writer, ptr, 0);
        }
    }
}

private static void PrintFields(ClrType type, TextWriter writer, ulong ptr, int indentLevel)
{
    string indent = new string(' ', indentLevel * 4);
    foreach (var field in type.Fields)
    {
        writer.Write(indent);
        if (field.IsObjectReference() && field.Type.Name != "System.String")
        {
            writer.WriteLine("{0} ({1})", field.Name, field.Type.Name);
            ulong nextPtr = (ulong)field.GetFieldValue(ptr);
            PrintFields(field.Type, writer, nextPtr, indentLevel + 1);
        }
        else if (field.HasSimpleValue)
        {
            object value = field.GetFieldValue(ptr);
            writer.WriteLine("{0} ({1}) = {2}", field.Name, field.Type.Name, value);
        }
        else
        {
            writer.WriteLine("{0} ({1})", field.Name, field.Type.Name);
        }
    }
}

Вот как вы можете сделать это в LINQPad с ClrMD.Extensions:

var session = ClrMDSession.AttachToProcess(processId);
session.EnumerateClrObjects("*Foo").Dump(depth:3);

Я не знаю, можно ли запросить кучу таким способом. но простое решение - сделать что-то вроде этого:

public class Foo
{
    public static List<WeakReference<Foo>> allInstances = new List<WeakReference<Foo>>();

    public Foo()
    {
        allInstances.Add(new WeakReference<Foo>(this));
    }
}

Обязательно оберните затем в WeakReference, чтобы ваша коллекция не оставляла их в куче, пока процесс не завершится.

Другие вопросы по тегам