Skip to content

Instantly share code, notes, and snippets.

@leculver
Last active February 3, 2017 10:08
Show Gist options
  • Save leculver/2252976329e24a433da4 to your computer and use it in GitHub Desktop.
Save leculver/2252976329e24a433da4 to your computer and use it in GitHub Desktop.
Proposal for ClrObject implementation
struct ClrObject
{
const ulong AddressMask = 0x3FFFFFFFFFFFFFFF;
const ulong InteriorBit = 0x8000000000000000;
const ulong ExactTypeBit = 0x4000000000000000;
// bit0: interior
// bit1: exact type
// remaining bits: the address
private ulong _address;
private ClrType _type;
public ClrObject(ulong address, ClrType type, bool isTypeExact, bool interior = false)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
_address = address;
_type = type;
if (isTypeExact)
_address |= ExactTypeBit;
if (interior)
_address |= InteriorBit;
}
public ulong Address { get { return _address & AddressMask; } }
public ClrType Type
{
get
{
if (!IsTypeExact)
{
// The variable 'type' may be null if the runtime is in an inconsistent state.
ClrType type = _type.Heap.GetObjectType(Address);
if (type != null)
_type = type;
_address |= ExactTypeBit;
}
return _type;
}
}
public ClrHeap Heap { get { return _type.Heap; } }
public ulong Size { get { return _type.GetSize(Address); } }
public bool IsValid { get { return _type is InvalidType; } }
public bool IsNull { get { return Address == 0; } }
public bool IsString { get { return _type.IsString; } }
public bool IsFree { get { return _type.IsFree; } }
public bool IsArray { get { return _type.IsArray; } }
public bool IsInterior
{
get
{
return (_address & InteriorBit) == InteriorBit;
}
}
public dynamic Dynamic { get { return null; } } //todo
#region Field Readers
public ClrObject GetObjectField(string name)
{
// Check the type we currently have first. If the field isn't there and we do not
// have an exact type, we will resolve the exact type and check again. This should
// work correctly the first time in most cases, and resolving/rechecking the exact
// type should be the exceptional case.
ClrInstanceField field = _type.GetFieldByName(name);
if (field == null && !IsTypeExact)
field = Type.GetFieldByName(name);
if (field == null)
throw new InvalidOperationException(string.Format("Field '{0}' not found in type '{1}'.", name, _type.Name));
ulong address = 0;
if (!IsNull)
{
Debug.Assert(field.Type.IsObjectReference);
address = field.GetAddress(Address, IsInterior);
Type.Heap.ReadPointer(address, out address); // Assigns 0 on failure.
}
// The actual object type might be a subclass of field.Type, so isExact=false.
return new ClrObject(address, field.Type, isTypeExact: false);
}
// If this object is null, then GetStructField will successfully return a 0
// address value. That is: obj.GetStructField("foo").IsNull == true.
public ClrObject GetStructField(string name)
{
ClrInstanceField field = _type.GetFieldByName(name);
if (field == null)
throw new InvalidOperationException(string.Format("Field '{0}' not found in type '{1}'.", name, _type.Name));
Debug.Assert(field.ElementType == ClrElementType.Struct);
ulong address = Address;
if (address != 0)
address = field.GetAddress(address, IsInterior);
return new ClrObject(address, field.Type, isTypeExact: true, interior: true);
}
// Primitive values will be implemented similar to this.
public int GetIntField(string name)
{
ClrInstanceField field = _type.GetFieldByName(name);
if (field == null)
throw new InvalidOperationException(string.Format("Field '{0}' not found in type '{1}'.", name, _type.Name));
Debug.Assert(field.ElementType == ClrElementType.Int32);
ulong address = field.GetAddress(Address, IsInterior);
// This is not final. I will need to move the "Read" helpers to a better place to avoid this ugly cast.
int result;
((RuntimeBase)Type.Heap.Runtime).ReadDword(address, out result);
return result;
}
#endregion
#region Conversion
// Note that only primitive and array converters will go in the class itself.
// All "complex" converters (such as AsDateTime, AsGuid, etc) will be in its
// own namespace: Microsoft.Diagnostics.Runtime.ObjectConverters
// Primitive values will be implemented similar to this.
public int AsInt32()
{
Debug.Assert(_type.ElementType == ClrElementType.Int32);
// TODO: What should the correct behavior be here?
if (IsNull)
throw new InvalidOperationException("Cannot convert null value to Int32.");
ulong address = IsInterior ? Address : Address + (uint)IntPtr.Size;
// This is not final. I will need to move the "Read" helpers to a better place to avoid this ugly cast.
int result;
((RuntimeBase)Type.Heap.Runtime).ReadDword(address, out result);
return result;
}
public ClrObject AsBaseType()
{
ClrType baseType = Type.BaseType;
if (baseType == null)
throw new InvalidOperationException();
ClrObject result = new ClrObject();
result._address = _address;
result._type = baseType;
return result;
}
public ClrObjectArray AsObjectArray()
{
if (!_type.IsArray || !_type.ComponentType.IsObjectReference)
throw new InvalidOperationException("Object is not an object array.");
return new ClrObjectArray(Address, _type);
}
public ClrArray<T> AsPrimitiveArray<T>() where T : struct
{
if (!_type.IsArray)
throw new InvalidOperationException("Object is not an object array.");
return new ClrArray<T>(Address, _type);
}
public ClrStructArray AsStructArray()
{
if (!_type.IsArray || _type.ComponentType.ElementType != ClrElementType.Struct)
throw new InvalidOperationException("Object is not an array of structs.");
return new ClrStructArray(Address, _type);
}
#endregion
private bool IsTypeExact
{
get
{
return ((_address & ExactTypeBit) == ExactTypeBit) || !_type.IsObjectReference;
}
}
}
// All "complex" ClrObject coverters (such as AsDateTime, AsGuid, etc) will go in its own namespace,
// not in the class itself.
namespace ObjectConverters
{
// All non-primitive types will
static class ClrObjectConverters
{
public static DateTime AsDateTime(this ClrObject self)
{
if (self.Type.Name != "System.DateTime")
throw new InvalidOperationException();
return new DateTime(); // TODO: Conversion
}
}
}
// These three array types will inherit from a common base, so ignore the copy/paste.
class ClrObjectArray
{
private ulong _address;
private ClrType _type;
private int? _length;
public ClrObjectArray(ulong address, ClrType type)
{
_address = address;
_type = type;
}
public ulong Address { get { return _address; } }
public ClrType Type { get { return _type; } }
public ClrHeap Heap { get { return _type.Heap; } }
public int Length
{
get
{
if (!_length.HasValue)
_length = _type.GetArrayLength(_address);
return _length.Value;
}
}
public ClrObject this[int i]
{
get
{
Debug.Assert(i < Length);
if (_address == 0)
return new ClrObject(0, _type.ComponentType, isTypeExact: true);
ulong address = _type.GetArrayElementAddress(_address, i);
_type.Heap.ReadPointer(address, out address);
return new ClrObject(address, _type.ComponentType, isTypeExact: false);
}
}
// Also method for enumerating all values.
}
class ClrStructArray
{
private ulong _address;
private ClrType _type;
private int? _length;
public ClrStructArray(ulong address, ClrType type)
{
_address = address;
_type = type;
}
public ulong Address { get { return _address; } }
public ClrType Type { get { return _type; } }
public ClrHeap Heap { get { return _type.Heap; } }
public bool IsNull { get { return _address == 0; } }
public int Length
{
get
{
if (!_length.HasValue)
_length = _type.GetArrayLength(_address);
return _length.Value;
}
}
public ClrObject this[int i]
{
get
{
Debug.Assert(i < Length);
if (_address == 0)
return new ClrObject(0, _type.ComponentType, isTypeExact: true);
ulong address = _type.GetArrayElementAddress(_address, i);
return new ClrObject(address, _type.ComponentType, isTypeExact: true, interior: true);
}
}
// Also method for enumerating all values.
}
class ClrArray<T> where T : struct
{
private ulong _address;
private ClrType _type;
private int? _length;
public ClrArray(ulong address, ClrType type)
{
// TODO: Assert that type.ComponentType.ElementType is compatible with T.
_address = address;
_type = type;
}
public ulong Address { get { return _address; } }
public ClrType Type { get { return _type; } }
public ClrHeap Heap { get { return _type.Heap; } }
public bool IsNull { get { return _address == 0; } }
public int Length
{
get
{
if (!_length.HasValue)
_length = _type.GetArrayLength(_address);
return _length.Value;
}
}
public T this[int i]
{
get
{
Debug.Assert(i < Length);
// TODO: Should this throw?
if (_address == 0)
return default(T);
return (T)_type.GetArrayElementValue(_address, i);
}
}
// Also method for enumerating all values.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment