-
-
Save gene9/ab23a3b58202e0f438ab2295dc84b4ac to your computer and use it in GitHub Desktop.
In-progress abstraction around accessing heap data using ClrMD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: encapsulate runtime session, store heap, common general purpose types, stats, etc. | |
static ClrRuntime _runtime; | |
void Main() | |
{ | |
DataTarget _target = DataTarget.LoadCrashDump("C:\\Temp\\MEM.DMP"); | |
_target.SetSymbolPath(Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH")); | |
var clrVer = _target.ClrVersions[0]; | |
string dac = clrVer.TryGetDacLocation(); | |
if (dac == null) | |
{ | |
Trace.WriteLine("Local DAC not found for CLR Version " + clrVer.ToString() + "; attempting to download"); | |
dac = clrVer.TryDownloadDac(); | |
} | |
_runtime = _target.CreateRuntime(dac); | |
// sample; can get from ClrHeap.EnumerateObjects, WinDbg+SOS, etc. | |
ulong address = Convert.ToUInt64("0000000012345abc", 16); | |
Instance inst = InstanceFactory.CreateInstance(address).Dump(1); | |
// TODO: Test Boxed Values, Struct[], String[], | |
// Class->Struct->Struct, Struct->Struct[] | |
// rectangular/jagged multi-dimensional arrays, maybe more? | |
// Curious also about Struct Test { public ValueType InnerAnonStruct; } | |
} | |
// TODO: replace all explicit UInt64 addresses with this; use to abstract away from 64bit to platform agnostic | |
public class AddressReference | |
{ | |
private static readonly ClrType _ptrType = _runtime.GetHeap().GetTypeByName("System.UInt64"); | |
public static ulong Dereference(ulong pointerAddress) | |
{ | |
if (pointerAddress == 0ul) return 0; | |
return (ulong)_ptrType.GetValue(pointerAddress); | |
} | |
internal static readonly ClrType _strType = _runtime.GetHeap().GetTypeByName("System.String"); | |
private static readonly ClrInstanceField _strLengthField = _strType.GetFieldByName("m_stringLength"); | |
// modified from https://github.com/BravoAlpha/WPFMemDumpAnalyzer/blob/master/WPFMemDumpAnalyzer.Core/ValueHelper.cs | |
public static string GetStringValue(ulong stringAddress) | |
{ | |
// pointer to 0 is null | |
if (stringAddress == 0ul) return null; | |
var stringLength = (int)_strLengthField.GetFieldValue(stringAddress); | |
// NOTE: was <= 0? Changed to cause exceptions with unexpected negative values | |
if (stringLength == 0) | |
return String.Empty; | |
var content = new byte[stringLength * 2]; | |
int bytesRead; | |
// NOTE: not sure why + 12 when first char offset is reported as 4? | |
// Maybe there's a unicode preamble or something? Seems to work fine. | |
_strType.Heap.GetRuntime().ReadMemory(stringAddress + 12, content, content.Length, out bytesRead); | |
return System.Text.Encoding.Unicode.GetString(content); | |
} | |
} | |
public abstract class Instance | |
{ | |
// TODO: uncomment; commented because it dramatically slows down larger LINQPad Dump()s | |
//public ClrType Type { get; private set; } | |
protected ClrType Type { get; set; } | |
public string TypeName { get { return this.Type.Name; } } | |
internal ulong Address { get; private set; } | |
public string AddressHex { get { return Address.ToString("x12"); } } | |
public Instance(ulong address, ClrType type) { | |
this.Address = address; | |
this.Type = type; | |
} | |
public static ClrType GetTypeFromAddress(ulong address) { | |
var ret = _runtime.GetHeap().GetObjectType(address); | |
if (ret == null) throw new Exception("Unable to find Heap type from address " + address.ToString("x12")); | |
return ret; | |
} | |
} | |
public class PrimitiveInstance<T> : Instance where T : struct | |
{ | |
private Lazy<T> value; | |
public T Value { get { return value.Value; } } | |
public PrimitiveInstance(ulong address) : base(address, _runtime.GetHeap().GetTypeByName(typeof(T).FullName)) | |
{ | |
this.value = new Lazy<T>(() => (T)this.Type.GetValue(address)); | |
} | |
} | |
public class StringInstance : Instance | |
{ | |
private Lazy<string> value; | |
public string Value { get { return value.Value; } } | |
public StringInstance(ulong address) : base(address, AddressReference._strType) | |
{ | |
this.value = new Lazy<string>(() => AddressReference.GetStringValue(this.Address) | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
} | |
public class ArrayInstance : Instance | |
{ | |
private InstanceFactory factory; | |
private Lazy<int> arrayLength; | |
public int Length { get { return arrayLength.Value; } } | |
public ArrayInstance(ulong address) : base(address, Instance.GetTypeFromAddress(address)) { | |
this.arrayLength = new Lazy<int>(() => address != 0 ? this.Type.GetArrayLength(address) : 0 | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
var elType = this.Type.ArrayComponentType; | |
this.factory = InstanceFactory.GetFactoryForType(elType); | |
} | |
public IEnumerable<Instance> Elements { get { | |
for (int x = 0; x < this.Length; x++) { | |
var addr = this.Type.GetArrayElementAddress(this.Address, x); | |
yield return this.factory.Create(addr); | |
} | |
} } | |
} | |
public abstract class ComplexInstance : Instance | |
{ | |
public ComplexInstance(ulong address, ClrType type) : base(address, type) { } | |
protected abstract InstanceField CreatePrimitiveInstanceField(ClrInstanceField field); | |
public IEnumerable<InstanceField> Fields { get { | |
foreach (var field in this.Type.Fields) { | |
switch (field.Type.ElementType) { | |
case ClrElementType.Object: | |
yield return new ObjectInstanceField(this, field); | |
break; | |
case ClrElementType.Struct: | |
yield return new StructInstanceField(this, field); | |
break; | |
case ClrElementType.String: | |
yield return new StringInstanceField(this, field); | |
break; | |
case ClrElementType.Array: | |
case ClrElementType.SZArray: | |
yield return new ArrayInstanceField(this, field); | |
break; | |
default: | |
// try creating a primitive field | |
yield return (field.IsPrimitive() ? CreatePrimitiveInstanceField(field) : null) | |
?? new InstanceField(this, field); // fallback to basic field info | |
break; | |
} | |
} | |
} } | |
} | |
public class ObjectInstance : ComplexInstance | |
{ | |
public ObjectInstance(ulong address) : base(address, Instance.GetTypeFromAddress(address)) { } | |
//public ObjectInstance(ulong address, ClrType type) : base(address, type) { } | |
protected override InstanceField CreatePrimitiveInstanceField(ClrInstanceField field) | |
{ | |
switch (field.ElementType) | |
{ | |
case ClrElementType.Boolean: | |
return new PrimitiveInstanceField<bool>(this, field); | |
case ClrElementType.Char: | |
return new PrimitiveInstanceField<char>(this, field); | |
case ClrElementType.Double: | |
return new PrimitiveInstanceField<double>(this, field); | |
case ClrElementType.Float: | |
return new PrimitiveInstanceField<float>(this, field); | |
case ClrElementType.Int16: | |
return new PrimitiveInstanceField<short>(this, field); | |
case ClrElementType.Int32: | |
return new PrimitiveInstanceField<int>(this, field); | |
case ClrElementType.Int64: | |
return new PrimitiveInstanceField<long>(this, field); | |
case ClrElementType.Int8: | |
return new PrimitiveInstanceField<sbyte>(this, field); | |
case ClrElementType.NativeInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstanceField<long>(this, field); | |
case ClrElementType.NativeUInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstanceField<ulong>(this, field); | |
case ClrElementType.UInt16: | |
return new PrimitiveInstanceField<ushort>(this, field); | |
case ClrElementType.UInt32: | |
return new PrimitiveInstanceField<uint>(this, field); | |
case ClrElementType.UInt64: | |
return new PrimitiveInstanceField<ulong>(this, field); | |
case ClrElementType.UInt8: | |
return new PrimitiveInstanceField<byte>(this, field); | |
default: | |
field.Dump("Unable to find primitive type " + field.ElementType.ToString(), 1); | |
return null; | |
} | |
} | |
} | |
public class StructInstance : ComplexInstance | |
{ | |
public StructInstance(ulong address, ClrType type) : base(address, type) { } | |
protected override InstanceField CreatePrimitiveInstanceField(ClrInstanceField field) | |
{ | |
switch (field.ElementType) | |
{ | |
case ClrElementType.Boolean: | |
return new PrimitiveInstanceField<bool>(this, field); | |
case ClrElementType.Char: | |
return new PrimitiveInstanceField<char>(this, field); | |
case ClrElementType.Double: | |
return new PrimitiveInstanceField<double>(this, field); | |
case ClrElementType.Float: | |
return new PrimitiveInstanceField<float>(this, field); | |
case ClrElementType.Int16: | |
return new PrimitiveInstanceField<short>(this, field); | |
case ClrElementType.Int32: | |
return new PrimitiveInstanceField<int>(this, field); | |
case ClrElementType.Int64: | |
return new PrimitiveInstanceField<long>(this, field); | |
case ClrElementType.Int8: | |
return new PrimitiveInstanceField<sbyte>(this, field); | |
case ClrElementType.NativeInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstanceField<long>(this, field); | |
case ClrElementType.NativeUInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstanceField<ulong>(this, field); | |
case ClrElementType.UInt16: | |
return new PrimitiveInstanceField<ushort>(this, field); | |
case ClrElementType.UInt32: | |
return new PrimitiveInstanceField<uint>(this, field); | |
case ClrElementType.UInt64: | |
return new PrimitiveInstanceField<ulong>(this, field); | |
case ClrElementType.UInt8: | |
return new PrimitiveInstanceField<byte>(this, field); | |
default: | |
field.Dump("Unable to find primitive type " + field.ElementType.ToString(), 1); | |
return null; | |
} | |
} | |
} | |
public class InstanceField | |
{ | |
protected ClrInstanceField field; | |
// TODO: uncomment; commented because it dramatically slows down larger LINQPad Dump()s | |
//public Instance Parent { get; protected set; } | |
protected Instance Parent { get; set; } | |
public string Name { get { return field.Name; } } | |
public string ElementType { get { return field.ElementType.ToString(); } } | |
public string ClrTypeName { get { return field.Type.Name; } } | |
public int Offset { get { return field.Offset; } } | |
public InstanceField(Instance parent, ClrInstanceField field) { | |
this.Parent = parent; | |
this.field = field; | |
} | |
} | |
public class StringInstanceField : InstanceField | |
{ | |
private Lazy<string> value; | |
public string Value { get { return value.Value; } } | |
public StringInstanceField(Instance parent, ClrInstanceField field) : base(parent, field) | |
{ | |
this.value = new Lazy<string>( | |
() => AddressReference.GetStringValue( | |
// get the pointer to the string; this is a workaround because GetFieldValue | |
// doesn't return the reference pointer, but rather tries to reconstruct the | |
// string ("tries" but fails to do so correctly) | |
AddressReference.Dereference(this.Parent.Address + (ulong)this.field.Offset) | |
) | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
} | |
public class StructInstanceField : InstanceField | |
{ | |
private Lazy<Instance> value; | |
public Instance Value { get { return value.Value; } } | |
public StructInstanceField(Instance parent, ClrInstanceField field) : base(parent, field) | |
{ | |
this.value = new Lazy<Instance>(() => { | |
ulong addr; | |
// GetFieldAddress works for Object fields, but doesn't seem to work well un struct fields. | |
if (parent is StructInstance) { | |
addr = parent.Address + (ulong)field.Offset; | |
} else { | |
addr = field.GetFieldAddress(parent.Address); | |
} | |
if (0ul.Equals(addr)) { | |
addr.Dump("Unexpected Struct Field Address; Address was 0 (null)"); | |
return null; | |
} | |
return new StructInstance(addr, field.Type); | |
}, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
} | |
public class ObjectInstanceField : InstanceField | |
{ | |
private Lazy<Instance> value; | |
public Instance Value { get { return value.Value; } } | |
public ObjectInstanceField(Instance parent, ClrInstanceField field) : base(parent, field) | |
{ | |
this.value = new Lazy<Instance>(() => { | |
var val = field.GetFieldValue(parent.Address); | |
if (val == null) return null; | |
if (0ul.Equals(val)) return null; | |
if (!(val is ulong)) { | |
val.Dump("Unexpected Object Field value; expected ulong address"); | |
return null; | |
} | |
return new ObjectInstance((ulong)val); | |
} | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
} | |
public class PrimitiveInstanceField<T> : InstanceField where T : struct | |
{ | |
private Lazy<T> value; | |
public T Value { get { return value.Value; } } | |
public PrimitiveInstanceField(ObjectInstance parent, ClrInstanceField field) : base(parent, field) | |
{ | |
this.value = new Lazy<T>(() => (T)field.GetFieldValue(parent.Address) | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
public PrimitiveInstanceField(StructInstance parent, ClrInstanceField field) : base(parent, field) | |
{ | |
this.value = new Lazy<T>(() => (T)field.GetFieldValue(parent.Address, true) | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
} | |
} | |
public class ArrayInstanceField : InstanceField | |
{ | |
private Lazy<ulong> arrayAddress; | |
private Lazy<int> arrayLength; | |
private InstanceFactory factory; | |
private ClrType elementType; | |
public int Length { get { return arrayLength.Value; } } | |
public ArrayInstanceField(Instance parent, ClrInstanceField field) : base (parent, field) | |
{ | |
this.arrayAddress = new Lazy<ulong>(() => { | |
return AddressReference.Dereference(this.Parent.Address + (ulong)this.field.Offset); | |
}, LazyThreadSafetyMode.ExecutionAndPublication); | |
this.arrayLength = new Lazy<int>(() => this.arrayAddress.Value != 0 ? field.Type.GetArrayLength(this.arrayAddress.Value) : 0 | |
, LazyThreadSafetyMode.ExecutionAndPublication); | |
this.elementType = field.Type.ArrayComponentType; | |
if (this.elementType == null) { | |
field.Dump("Unable to find ArrayComponentType", 1); | |
if (field.Type.Name.EndsWith("[]")) { | |
// not sure why this happened besides maybe being an array of struct with no items? | |
this.elementType = _runtime.GetHeap().GetTypeByName(field.Type.Name.Substring(0, field.Type.Name.Length - 2)); | |
} | |
} | |
this.factory = InstanceFactory.GetFactoryForType(this.elementType); | |
} | |
public IEnumerable<Instance> Elements { get { | |
ulong arrAddr = arrayAddress.Value; | |
for (int x = 0; x < this.Length; x++) { | |
var address = this.field.Type.GetArrayElementAddress(arrAddr, x); | |
if (elementType.IsObjectReference) { | |
address = AddressReference.Dereference(address); | |
} | |
yield return this.factory.Create(address); | |
} | |
} } | |
} | |
public abstract class InstanceFactory | |
{ | |
public abstract Instance Create(ulong address); | |
public static Instance CreateInstance(ulong address) { | |
if (address == 0ul) return null; | |
ClrType type = _runtime.GetHeap().GetObjectType(address); | |
if (type == null) | |
{ | |
throw new ArgumentOutOfRangeException( | |
"Unable to find ClrType for address " + address.ToString("x12")); | |
} | |
return GetFactoryForType(type).Create(address); | |
} | |
public static InstanceFactory GetFactoryForType(ClrType type) | |
{ | |
switch (type.ElementType) { | |
case ClrElementType.Object: | |
return new ObjectInstanceFactory(type); | |
case ClrElementType.String: | |
return new StringInstanceFactory(); | |
case ClrElementType.Struct: | |
return new StructInstanceFactory(type); | |
case ClrElementType.Array: | |
case ClrElementType.SZArray: | |
return new ArrayInstanceFactory(); | |
default: | |
if (type.IsPrimitive) return new PrimitiveInstanceFactory(type); | |
throw new ArgumentException( | |
"No InstanceFactory found for ElementType " | |
+ type.ElementType.ToString() | |
+ ", ClrType " | |
+ type.Name | |
, "type"); | |
} | |
} | |
private class ObjectInstanceFactory : InstanceFactory | |
{ | |
private ClrType type; | |
public ObjectInstanceFactory(ClrType type) { | |
this.type = type; | |
} | |
public override Instance Create(ulong address) { | |
return new ObjectInstance(address); | |
} | |
} | |
private class PrimitiveInstanceFactory : InstanceFactory | |
{ | |
private ClrType type; | |
public PrimitiveInstanceFactory(ClrType type) { | |
this.type = type; | |
} | |
public override Instance Create(ulong address) { | |
switch (type.ElementType) | |
{ | |
case ClrElementType.Boolean: | |
return new PrimitiveInstance<bool>(address); | |
case ClrElementType.Char: | |
return new PrimitiveInstance<char>(address); | |
case ClrElementType.Double: | |
return new PrimitiveInstance<double>(address); | |
case ClrElementType.Float: | |
return new PrimitiveInstance<float>(address); | |
case ClrElementType.Int16: | |
return new PrimitiveInstance<short>(address); | |
case ClrElementType.Int32: | |
return new PrimitiveInstance<int>(address); | |
case ClrElementType.Int64: | |
return new PrimitiveInstance<long>(address); | |
case ClrElementType.Int8: | |
return new PrimitiveInstance<sbyte>(address); | |
case ClrElementType.NativeInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstance<long>(address); | |
case ClrElementType.NativeUInt: //not sure if this is always 32 or if it is architecture dependent | |
return new PrimitiveInstance<ulong>(address); | |
case ClrElementType.UInt16: | |
return new PrimitiveInstance<ushort>(address); | |
case ClrElementType.UInt32: | |
return new PrimitiveInstance<uint>(address); | |
case ClrElementType.UInt64: | |
return new PrimitiveInstance<ulong>(address); | |
case ClrElementType.UInt8: | |
return new PrimitiveInstance<byte>(address); | |
default: | |
this.type.Dump("Unable to find primitive type", 1); | |
return null; | |
} | |
} | |
} | |
private class StringInstanceFactory : InstanceFactory | |
{ | |
public override Instance Create(ulong address) { | |
return new StringInstance(address); | |
} | |
} | |
private class StructInstanceFactory : InstanceFactory | |
{ | |
private ClrType type; | |
public StructInstanceFactory(ClrType type) { | |
this.type = type; | |
} | |
public override Instance Create(ulong address) { | |
return new StructInstance(address, type); | |
} | |
} | |
private class ArrayInstanceFactory : InstanceFactory | |
{ | |
public override Instance Create(ulong address) { | |
return new ArrayInstance(address); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment