Skip to content

Instantly share code, notes, and snippets.

@piers7
Created November 22, 2017 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piers7/8a79454dc778713799a9537336530eed to your computer and use it in GitHub Desktop.
Save piers7/8a79454dc778713799a9537336530eed to your computer and use it in GitHub Desktop.
Memory dump explorer in Linqpad using the Microsoft.Diagnostics.Runtime engine
<Query Kind="Program">
<Reference>&lt;RuntimeDirectory&gt;\System.Windows.Forms.dll</Reference>
<NuGetReference>Rx-Main</NuGetReference>
<NuGetReference Prerelease="true">Microsoft.Diagnostics.Runtime</NuGetReference>
<Namespace>Microsoft.Diagnostics.Runtime</Namespace>
<Namespace>System.Reactive</Namespace>
<Namespace>System.Reactive.Linq</Namespace>
</Query>
//string sourceDump = @"C:\MyLocalData\piers.williams\Downloads\temp\xunit.dmp";
string sourceDump = null;
[STAThread]
void Main()
{
Environment.Version.Dump();
Environment.Is64BitProcess.Dump();
if(sourceDump == null){
using(var d = new System.Windows.Forms.OpenFileDialog()){
var result = d.ShowDialog();
if(result==System.Windows.Forms.DialogResult.OK){
sourceDump = d.FileName;
}else{
return;
}
}
}
using(var target = DataTarget.LoadCrashDump(sourceDump)){
target.SymbolLocator.SymbolCache = @"C:\Symbols";
var clrVersion = target.ClrVersions.First();
//var runtime = target.CreateRuntime(@"C:\Symbols\SOS_4.0.30319.1026\mscordacwks.dll");
var runtime = clrVersion.CreateRuntime();
var heap = runtime.GetHeap();
runtime.Threads
.Select (t => new {
t.OSThreadId,
t.ManagedThreadId,
t.GcMode,
t.IsFinalizer,
t.IsAlive,
t.CurrentException,
t.StackTrace,
// Objects = t.EnumerateStackObjects(),
})
.Dump("Threads",3);
GetHeapStats(heap).Dump("Heap");
// var traceEventType = heap.GetTypeByName("ORDW.Diagnostics.AnalysisServices.Trace.TraceEvent");
// var instances = heap.EnumerateObjects()
// .Select (o => new ClrObject(o, heap.GetObjectType(o)))
// .Where (o => o.Type == traceEventType)
// //.Dump(3)
// ;
//traceEventType.Dump();
//WalkReferences(instances.First().Address).Dump();
// heap.EnumerateObjects()
// .Take(10)
// .Dump()
// ;
// instances
// .Take(100)
//
// .Select (o => o.ToObject())
// .Dump(1);
// var find = instances.First().Address;
// DisplayRefChainsToObject(find, heap);
}
}
// Define other methods and classes here
public static IEnumerable<object> GetHeapStats(ClrHeap heap){
var heapByType = (
from address in heap.EnumerateObjectAddresses()
let type = heap.GetObjectType(address)
let size = type.GetSize(address)
group address by type into g
select new {
Type = g.Key,
TotalSize = g.Sum (o => (long)g.Key.GetSize(o)),
Count = g.Count (),
Items = g.GetEnumerator(),
}
)
;
return heapByType
.OrderByDescending (g => g.TotalSize)
.Take(20)
.Select (t => new {
t.TotalSize,
t.Count,
t.Type.Name,
Items = Util.OnDemand("Items", () => t.Items)
})
;
}
public static ILookup<ulong,ClrRoot> BuildRootsLookup(ClrHeap heap){
var roots = heap.EnumerateRoots(); // aledgedly cached within the API anyway
return BuildRootsLookup(roots);
}
public static ILookup<ulong,ClrRoot> BuildRootsLookup(IEnumerable<ClrRoot> roots){
// See https://github.com/Microsoft/dotnetsamples/blob/master/Microsoft.Diagnostics.Runtime/CLRMD/GCRoot/Program.cs
// That implementation populated a static dictionary and returned a list in one pass
// Bit of a dirty-side-effecting method IMHO
var lookup = roots
.ToLookup (r => r.Object)
;
return lookup;
}
private static void DisplayRefChainsToObject(ulong targetAddress, ClrHeap heap){
var roots = heap.EnumerateRoots().ToList();
var rootDictionary = BuildRootsLookup(roots);
foreach(var root in roots){
var visited = new HashSet<ulong>();
var chain = new Stack<ulong>();
chain.Push(root.Object);
//Console.WriteLine("Scanning roots for {0}", root.Type.Name);
DisplayRefChainsToObject(targetAddress, heap, chain, visited);
}
}
private static void DisplayRefChainsToObject(ulong targetAddress, ClrHeap heap, Stack<ulong> refs, HashSet<ulong> visited)
{
// See http://blogs.microsoft.co.il/sasha/2013/05/20/traversing-the-gc-heap-with-clrmd/
// Also https://github.com/Microsoft/dotnetsamples/blob/master/Microsoft.Diagnostics.Runtime/CLRMD/docs/WalkingTheHeap.md
// Basic idea is that we have to start with all the GC roots, and recursively walk all their references
// Any time we hit the reference we are looking for we return the path
var currentObj = refs.Peek();
if (visited.Contains(currentObj)) return;
visited.Add(currentObj);
if (currentObj == targetAddress)
{
// Display the chain
Console.WriteLine("Found path to {0}", targetAddress);
refs
.Select (addr => new ClrObject(addr, heap.GetObjectType(addr)))
.Dump()
;
}
var type = heap.GetObjectType(currentObj);
if(type==null)
return;
type.EnumerateRefsOfObject(currentObj, (innerObj, fieldOffset) =>
{
refs.Push(innerObj);
DisplayRefChainsToObject(targetAddress, heap, refs, visited);
refs.Pop();
});
}
public class ClrObject{
public ulong Address {get;private set;}
public ClrType Type {get;private set;}
public ClrObject(ulong address, ClrType type)
{
Address = address;
Type = type;
}
public dynamic ToObject(){
var expando = (IDictionary<string,object>)new System.Dynamic.ExpandoObject();
expando["__this"] = this;
foreach(var field in Type.Fields){
var name = field.Name;
if(name.StartsWith("<") && name.EndsWith(">k__BackingField"))
name = name.Substring(1,name.IndexOf("__")-3);
expando[name] = field.GetValue(Address);
}
return expando;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment