[ServiceImport]
public RootCacheService RootCache { get; set; }
+ [ServiceImport]
+ public StaticVariableService StaticVariables { get; set; }
+
[ServiceImport]
public ManagedFileLineService FileLineService { get; set; }
}
Console.WriteLine($" {objAddress:x}");
- PrintPath(Console, RootCache, Runtime.Heap, path);
+ PrintPath(Console, RootCache, StaticVariables, Runtime.Heap, path);
Console.WriteLine();
count++;
private void PrintPath(ClrRoot root, GCRoot.ChainLink link)
{
PrintRoot(root);
- PrintPath(Console, RootCache, Runtime.Heap, link);
+ PrintPath(Console, RootCache, StaticVariables, Runtime.Heap, link);
Console.WriteLine();
}
- public static void PrintPath(IConsoleService console, RootCacheService rootCache, ClrHeap heap, GCRoot.ChainLink link)
+ public static void PrintPath(IConsoleService console, RootCacheService rootCache, StaticVariableService statics, ClrHeap heap, GCRoot.ChainLink link)
{
Table objectOutput = new(console, Text.WithWidth(2), DumpObj, TypeName, Text)
{
objectOutput.SetAlignment(Align.Left);
+ bool first = true;
+ bool isPossibleStatic = true;
+
+ ClrObject firstObj = default;
+
ulong prevObj = 0;
while (link != null)
{
- bool isDependentHandleLink = rootCache.IsDependentHandleLink(prevObj, link.Object);
ClrObject obj = heap.GetObject(link.Object);
- objectOutput.WriteRow("->", obj, obj.Type, (isDependentHandleLink ? " (dependent handle)" : ""));
+ // Check whether this link is a dependent handle
+ string extraText = "";
+ bool isDependentHandleLink = rootCache.IsDependentHandleLink(prevObj, link.Object);
+ if (isDependentHandleLink)
+ {
+ extraText = "(dependent handle)";
+ }
+
+ // Print static variable info. In all versions of the runtime, static variables are stored in
+ // a pinned object array. We check if the first link in the chain is an object[], and if so we
+ // check if the second object's address is the location of a static variable. We could further
+ // narrow this by checking the root type, but that needlessly complicates this code...we can't
+ // get false positives or negatives here (as nothing points to static variable object[] other
+ // than the root).
+ if (first)
+ {
+ firstObj = obj;
+ isPossibleStatic = firstObj.IsValid && firstObj.IsArray && firstObj.Type.Name == "System.Object[]";
+ first = false;
+ }
+ else if (isPossibleStatic)
+ {
+ if (statics is not null && !isDependentHandleLink)
+ {
+ foreach (ClrReference reference in firstObj.EnumerateReferencesWithFields(carefully: false, considerDependantHandles: false))
+ {
+ if (reference.Object == obj)
+ {
+ ulong address = firstObj + (uint)reference.Offset;
+
+ if (statics.TryGetStaticByAddress(address, out ClrStaticField field))
+ {
+ extraText = $"(static variable: {field.Type?.Name ?? "Unknown"}.{field.Name})";
+ break;
+ }
+ }
+ }
+ }
+
+ // only the first object[] in the chain is possible to be the static array
+ isPossibleStatic = false;
+ }
+
+ objectOutput.WriteRow("->", obj, obj.Type, extraText);
prevObj = link.Object;
link = link.Next;
ClrHandleKind.SizedRef => "sized ref handle",
ClrHandleKind.WeakWinRT => "weak WinRT handle",
_ => handleKind.ToString()
- }; ;
+ };
}
private string GetFrameOutput(ClrStackFrame currFrame)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [ServiceExport(Scope = ServiceScope.Runtime)]
+ public class StaticVariableService
+ {
+ private Dictionary<ulong, ClrStaticField> _fields;
+ private IEnumerator<(ulong Address, ClrStaticField Static)> _enumerator;
+
+ [ServiceImport]
+ public ClrRuntime Runtime { get; set; }
+
+ /// <summary>
+ /// Returns the static field at the given address.
+ /// </summary>
+ /// <param name="address">The address of the static field. Note that this is not a pointer to
+ /// an object, but rather a pointer to where the CLR runtime tracks the static variable's
+ /// location. In all versions of the runtime, address will live in the middle of a pinned
+ /// object[].</param>
+ /// <param name="field">The field corresponding to the given address. Non-null if return
+ /// is true.</param>
+ /// <returns>True if the address corresponded to a static variable, false otherwise.</returns>
+ public bool TryGetStaticByAddress(ulong address, out ClrStaticField field)
+ {
+ if (_fields is null)
+ {
+ _fields = new();
+ _enumerator = EnumerateStatics().GetEnumerator();
+ }
+
+ if (_fields.TryGetValue(address, out field))
+ {
+ return true;
+ }
+
+ // pay for play lookup
+ if (_enumerator is not null)
+ {
+ do
+ {
+ _fields[_enumerator.Current.Address] = _enumerator.Current.Static;
+ if (_enumerator.Current.Address == address)
+ {
+ field = _enumerator.Current.Static;
+ return true;
+ }
+ } while (_enumerator.MoveNext());
+
+ _enumerator = null;
+ }
+
+ return false;
+ }
+
+ public IEnumerable<(ulong Address, ClrStaticField Static)> EnumerateStatics()
+ {
+ ClrAppDomain shared = Runtime.SharedDomain;
+
+ foreach (ClrModule module in Runtime.EnumerateModules())
+ {
+ foreach ((ulong mt, _) in module.EnumerateTypeDefToMethodTableMap())
+ {
+ ClrType type = Runtime.GetTypeByMethodTable(mt);
+ if (type is null)
+ {
+ continue;
+ }
+
+ foreach (ClrStaticField stat in type.StaticFields)
+ {
+ foreach (ClrAppDomain domain in Runtime.AppDomains)
+ {
+ ulong address = stat.GetAddress(domain);
+ if (address != 0)
+ {
+ yield return (address, stat);
+ }
+ }
+
+ if (shared is not null)
+ {
+ ulong address = stat.GetAddress(shared);
+ if (address != 0)
+ {
+ yield return (address, stat);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}