Používáme-li WinDbg a jeho rozšíření SOS.dll (popř. SOSEX nebo PSSCOR4) k analýze memory-dumpu .NET procesů, pak jednou z komplikovanějších úloh je například nalezení statických rootů, tedy statických fieldů, které drží reference na prozkoumávanou instanci.
Že se jedná o zarootování instance ze statického fieldu je zpravidla zřejmé z výstupu !GCRoot, který pak vypadá například nějak takto:
0:000> !GCRoot 0000000acaf3e8f8 HandleTable: 0000000f159f19f8 (pinned handle) -> 0000000ec87f1be8 System.Object[] -> 0000000d11ac08c8 System.Action -> 00000010f5df1020 System.Object[] -> 0000000acaf3e8f8 System.Action Found 1 unique roots (run '!GCRoot -all' to see all roots).
.NET si drží hodnoty statických fieldů v systémových object[] polích, nicméně dohledat, který konkrétní statický field je naším rootem, už není tak jednoduché (obvykle skončíme ve WinDbg u s-d nebo s-q prohledávání paměti a hledání další reference, atp.).
ClrMD: Microsoft.Diagnostics.Runtime
Naštěstí existuje a z NuGet se dá snadno nainstalovat .NET knihovna Microsoft.Diagnostics.Runtime (zkráceně ClrMD), která umožňuje inspekci a analýzu memory-dumpu z .NET kódu obdobně jako byste pracovali s SOS ve WinDbg.
…a jednou z věcí, které lze pomocí ClrMD snadno dostat je právě identifikace statického fieldu, který je rootovou referencí na prozkoumávaný objekt:
DataTarget dataTarget = DataTarget.LoadCrashDump(@"D:\path\to\memory-dump.DMP"); ClrRuntime runtime = dataTarget.CreateRuntime(@"D:\path\to\mscordacwks.dll"); ClrHeap heap = runtime.GetHeap(); foreach (ClrRoot root in heap.EnumerateRoots()) { if (root.Object == 0x0000000d11ac08c8) // adresa rootu, který chceme prozkoumat { // jen pro ukázku třeba: Console.WriteLine(root.Kind); if (root.Kind == GCRootKind.StaticVar) Console.WriteLine(root.Name); } }
Pracujeme ideálně paralelně s WinDbg+SOS:
- do CreateRuntime() dáváme cestu k příslušnému mscordacwks.dll, z WinDbg získáme přes .cordll -ve -u -l
- adresu prozkoumávaného rootu známe z WinDbg (první po System.Object[], viz výše uvedený příklad z !GCRoot)
- výše uvedený snippet stačí prsknout do ConsoleApp, do které nareferencujeme ClrMD z NuGet
- je potřeba nastavit Build / Platform Target na x86 nebo x64, podle toho, z jaké platformy pochází memory-dump