microsoft / clrmd

Microsoft.Diagnostics.Runtime is a set of APIs for introspecting processes and dumps.
MIT License
1.05k stars 255 forks source link

Determining static var roots #735

Closed jwdj closed 4 years ago

jwdj commented 4 years ago

@leculver, @NextTurn First of all, congratulations on releasing ClrMD 2! A lot of effort went into it.

With ClrMD 1.1 there used to be much more roots. You could see the static variables. How do you find the static variabeles in ClrMD 2.0?

I think it is more informative to know which static variable is holding on to an object than to know it is a pinned handle.

    if (heap.CanWalkHeap)
    {
        foreach (var root in heap.EnumerateRoots())
        {
            Console.WriteLine(root.ToString());
        }
    }
nxtn commented 4 years ago

How do you find the static variabeles in ClrMD 2.0?

v1

ClrHeap.EnumerateRoots(enumerateStatics: true)

v2

Static variables aren't actually roots. In order to find all static variables, just enumerate all non-zero values of all reference type static fields of all types, in all app domains.

ClrRuntime runtime = ...;
foreach (ClrObject obj in
    from module in runtime.EnumerateModules()
    from map in module.EnumerateTypeDefToMethodTableMap()
    let type = module.ResolveToken(map.Token)
    where type != null // dotnet/roslyn#44913
    from staticField in type.StaticFields // Thread-local static fields are no longer available.
    where staticField.IsObjectReference
    from domain in runtime.AppDomains
    let obj = staticField.ReadObject(domain)
    where obj.IsValid
    select obj
)
{
    Console.WriteLine(obj);
}
jwdj commented 4 years ago

@NextTurn Thank you for your swift response and helpful example. I don't mind v2 is more elaborate while v1 did more under the hood. It helps in understanding the way managed memory (and static vars in particular) is structured. Closing this issue.

leculver commented 4 years ago

@jwdj Please feel free to open more issues to ask questions if you run into trouble in the future.

FYI, I've moved a lot of things that were super slow or in some way hacky out of the library. I haven't decided whether I will add them to the "Utilities" library or not, but I just haven't had time to make the decision and then implement it.

Enumerating all types in all modules is one of those things (use EnumerateTypeDefToMethodTableMap instead). Static variable "roots" are another. Static variables are always rooted by a strong GC handle, but there's not a quick way to enumerate all static variables in the process to map onto "roots". Since it takes SO long to walk every MT in the process I removed it from the roots enumeration.

I need to put these things in the FAQ but I came right down to the wire fixing issues and I didn't want to push back 2.0 for another week.

holc-tkomp commented 4 years ago

+1 to need of finding static "roots". For the moment I'll go with @NextTurn's solution I suppose.

holc-tkomp commented 4 years ago

FYI, I've moved a lot of things that were super slow or in some way hacky out of the library. I haven't decided whether I will add them to the "Utilities" library or not, but I just haven't had time to make the decision and then implement it.

Maybe extensions library? Adding ClrHeap.EnumerateStaticObjects extension method? Could be a separate nuget