在.net堆中查找类型的实例数据

时间:2022-07-14 17:01:33

Let's say I have two class Foo and Bar as follows

假设我有两个类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; }
}

I have an application that attaches to a process that is using these classes. I would like to see all instances of Foo type in .NET heap and inspect the TheBar.Name property or _whatever field of all Foo instances present in .NET heap. I can find the type but I am not sure how to get the instance and see its properties. Any ideas how?

我有一个应用程序附加到使用这些类的进程。我想在.NET堆中看到所有Foo类型的实例,并检查.NET堆中存在的所有Foo实例的TheBar.Name属性或_whatever字段。我可以找到类型,但我不知道如何获取实例并查看其属性。有什么想法?

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 个解决方案

#1


I don't think you can get property values directly because that would require you to run code and the target might not even be a process but a dump file.

我不认为您可以直接获取属性值,因为这将要求您运行代码,目标甚至可能不是进程而是转储文件。

You can definitely get an object's fields and their values. ClrType has a Fields property which you can use to loop through fields. Then you can call GetFieldValue for fields where HasSimpleValue is true.

你绝对可以获得一个对象的字段及其值。 ClrType有一个Fields属性,可用于循环遍历字段。然后,您可以为HasSimpleValue为true的字段调用GetFieldValue。

A simple example:

一个简单的例子:

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);
                }
            }
        }
    }
}

So you could look for a field that has "Name", "_name", or something similar in it. If it is an auto-implemented property, the name will be something like <Name>k__BackingField.

因此,您可以查找具有“名称”,“_名称”或类似内容的字段。如果它是自动实现的属性,则名称将类似于 k__BackingField。

Your scenario is a little more complicated in that you want to go into another object. To do that we can recursively inspect the fields. However note that in the general case you would want to keep track of which objects you've visited so you don't recurse indefinitely.

您的场景有点复杂,因为您想要进入另一个对象。为此,我们可以递归检查字段。但请注意,在一般情况下,您需要跟踪您访问过的对象,这样您就无法无限期地递归。

Here is an example that is more appropriate for this:

这是一个更适合这个的例子:

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);
        }
    }
}

#2


Here is how you could do it in LINQPad with ClrMD.Extensions:

以下是使用ClrMD.Extensions在LINQPad中执行此操作的方法:

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

#3


I don't know if it's possible to query the heap in such a way . but an easy soluotion is to do something like this :

我不知道是否可以以这种方式查询堆。但一个简单的解决方案是做这样的事情:

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

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

Make sure to wrap then in a WeakReference so your collection won't keep them in the heap until the process ends .

确保在WeakReference中包装,以便在进程结束之前,您的集合不会将它们保留在堆中。

#1


I don't think you can get property values directly because that would require you to run code and the target might not even be a process but a dump file.

我不认为您可以直接获取属性值,因为这将要求您运行代码,目标甚至可能不是进程而是转储文件。

You can definitely get an object's fields and their values. ClrType has a Fields property which you can use to loop through fields. Then you can call GetFieldValue for fields where HasSimpleValue is true.

你绝对可以获得一个对象的字段及其值。 ClrType有一个Fields属性,可用于循环遍历字段。然后,您可以为HasSimpleValue为true的字段调用GetFieldValue。

A simple example:

一个简单的例子:

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);
                }
            }
        }
    }
}

So you could look for a field that has "Name", "_name", or something similar in it. If it is an auto-implemented property, the name will be something like <Name>k__BackingField.

因此,您可以查找具有“名称”,“_名称”或类似内容的字段。如果它是自动实现的属性,则名称将类似于 k__BackingField。

Your scenario is a little more complicated in that you want to go into another object. To do that we can recursively inspect the fields. However note that in the general case you would want to keep track of which objects you've visited so you don't recurse indefinitely.

您的场景有点复杂,因为您想要进入另一个对象。为此,我们可以递归检查字段。但请注意,在一般情况下,您需要跟踪您访问过的对象,这样您就无法无限期地递归。

Here is an example that is more appropriate for this:

这是一个更适合这个的例子:

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);
        }
    }
}

#2


Here is how you could do it in LINQPad with ClrMD.Extensions:

以下是使用ClrMD.Extensions在LINQPad中执行此操作的方法:

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

#3


I don't know if it's possible to query the heap in such a way . but an easy soluotion is to do something like this :

我不知道是否可以以这种方式查询堆。但一个简单的解决方案是做这样的事情:

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

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

Make sure to wrap then in a WeakReference so your collection won't keep them in the heap until the process ends .

确保在WeakReference中包装,以便在进程结束之前,您的集合不会将它们保留在堆中。