Skip to content

Instantly share code, notes, and snippets.

@TheXenocide
Created March 6, 2014 23:46
Show Gist options
  • Save TheXenocide/9402163 to your computer and use it in GitHub Desktop.
Save TheXenocide/9402163 to your computer and use it in GitHub Desktop.
In-progress abstraction around accessing heap data using ClrMD
// 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