Skip to content

Commit

Permalink
Merge pull request #137 from Washi1337/feature/better-debug-views
Browse files Browse the repository at this point in the history
Add debugger display proxies for various primitives in emulator
  • Loading branch information
Washi1337 committed Mar 20, 2024
2 parents 47aab36 + b7e1459 commit 90da97a
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 41 deletions.
15 changes: 10 additions & 5 deletions src/Core/Echo/Memory/BitVector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Echo.Memory
/// Represents an array of bits for which the concrete may be known or unknown, and can be reinterpreted as
/// different value types, and operated on using the different semantics of these types.
/// </summary>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
[DebuggerTypeProxy(typeof(BitVectorSpan))]
public class BitVector : ICloneable
{
/// <summary>
Expand Down Expand Up @@ -53,6 +53,15 @@ public BitVector(byte[] bits, byte[] knownMask)
KnownMask = knownMask;
}

/// <summary>
/// Copies a span into a new bit vector.
/// </summary>
/// <param name="span">The span to copy.</param>
public BitVector(BitVectorSpan span)
: this(span.Bits.ToArray(), span.KnownMask.ToArray())
{
}

/// <summary>
/// Creates a new fully known 8-wide bit vector.
/// </summary>
Expand Down Expand Up @@ -256,10 +265,6 @@ public byte[] KnownMask
{
get;
}

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[EditorBrowsable(EditorBrowsableState.Never)]
internal string DebuggerDisplay => AsSpan().DebuggerDisplay;

/// <summary>
/// Gets the number of bits stored in the bit vector.
Expand Down
18 changes: 16 additions & 2 deletions src/Core/Echo/Memory/BitVectorSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ namespace Echo.Memory
private static StringBuilder? _builder;

[ThreadStatic]
private static List<BitVector?>? _temporaryVectors;
private static List<BitVector?>? _temporaryVectors;

/// <summary>
/// Creates a new span around an existing bitvector.
/// </summary>
/// <param name="vector">The vector to span.</param>
public BitVectorSpan(BitVector vector)
: this(vector.Bits, vector.KnownMask)
{
}

/// <summary>
/// Creates a new span around a pair of bits and a known bit mask.
Expand Down Expand Up @@ -349,6 +358,11 @@ public string ToHexString()

return _builder.ToString();
}

/// <summary>
/// Copies the span into a new bit vector.
/// </summary>
public BitVector ToVector() => new(this);

private void AssertSameBitSize(BitVectorSpan other)
{
Expand Down Expand Up @@ -398,7 +412,7 @@ public bool Equals(BitVectorSpan other)
}

/// <inheritdoc />
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
// Since this is a ref struct, it will
// never equal any reference type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;

namespace Echo.Platforms.AsmResolver.Emulation;

[DebuggerTypeProxy(typeof(DebuggerProxy))]
public readonly partial struct ObjectHandle
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private object? Tag
{
get
{
if (IsNull)
return null;

try
{
return GetObjectType();
}
catch
{
return "<invalid>";
}
}
}

private sealed class DebuggerProxy
{
private readonly ObjectHandle _handle;

public DebuggerProxy(ObjectHandle handle)
{
_handle = handle;
}

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<object, object>[] Items
{
get
{
if (_handle.IsNull)
return Array.Empty<KeyValuePair<object, object>>();

var type = _handle.GetObjectType().ToTypeSignature();

return type switch
{
CorLibTypeSignature {ElementType: ElementType.String} => GetStringValue(),
SzArrayTypeSignature arrayType => GetArrayValues(arrayType),
_ => GetObjectFieldValues(type)
};
}
}

private KeyValuePair<object, object>[] GetStringValue()
{
// We cannot infer the string data if we don't know its length.
if (!_handle.ReadStringLength().IsFullyKnown)
return Array.Empty<KeyValuePair<object, object>>();

// Read and stringify the data.
var data = _handle.ReadStringData();
char[] result = new char[data.ByteCount / 2];
for (int i = 0; i < result.Length; i++)
{
var c = data.AsSpan(i * 8 * sizeof(char), 8 * sizeof(char));
result[i] = c.IsFullyKnown
? (char) c.U16
: '?';
}

return new[] {new KeyValuePair<object, object>(0, new string(result))};
}

private KeyValuePair<object, object>[] GetArrayValues(SzArrayTypeSignature type)
{
// We cannot infer the array data if we don't know its length.
var length = _handle.ReadArrayLength();
if (!length.IsFullyKnown)
return Array.Empty<KeyValuePair<object, object>>();

var elementType = type.BaseType;

// Collect all elements.
var result = new KeyValuePair<object, object>[length.AsSpan().I32];
for (int i = 0; i < result.Length; i++)
{
var element = _handle.ReadArrayElement(elementType, i);
result[i] = new KeyValuePair<object, object>(i, element);
}

return result;
}

private KeyValuePair<object, object>[] GetObjectFieldValues(TypeSignature type)
{
// We need to resolve the type to know which fields to include.
var definition = type.Resolve();
if (definition is null)
return Array.Empty<KeyValuePair<object, object>>();

// Collect all fields and their values.
var result = new List<KeyValuePair<object, object>>();
for (int i = 0; i < definition.Fields.Count; i++)
{
var field = definition.Fields[i];
if (field.IsStatic)
continue;

result.Add(new KeyValuePair<object, object>(field, _handle.ReadField(field)));
}

return result.ToArray();
}
}
}
25 changes: 4 additions & 21 deletions src/Platforms/Echo.Platforms.AsmResolver/Emulation/ObjectHandle.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Memory;
using AsmResolver.DotNet.Signatures.Types;
Expand All @@ -10,7 +9,7 @@ namespace Echo.Platforms.AsmResolver.Emulation
/// <summary>
/// Represents an address to an object (including its object header) within a CIL virtual machine.
/// </summary>
public readonly struct ObjectHandle : IEquatable<ObjectHandle>
public readonly partial struct ObjectHandle : IEquatable<ObjectHandle>
{
/// <summary>
/// Creates a new object handle from the provided address.
Expand Down Expand Up @@ -49,25 +48,6 @@ public long Address
/// </summary>
public StructHandle Contents => new(Machine, Address + Machine.ValueFactory.ObjectHeaderSize);

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal object? Tag
{
get
{
if (IsNull)
return null;

try
{
return GetObjectType();
}
catch
{
return "<invalid>";
}
}
}

/// <summary>
/// Gets the object's type (or method table).
/// </summary>
Expand All @@ -82,6 +62,9 @@ public ITypeDescriptor GetObjectType()
var methodTableSpan = methodTableVector.AsSpan();
Machine.Memory.Read(Address, methodTableSpan);

if (!methodTableSpan.IsFullyKnown)
throw new ArgumentException("Object contains an unknown method table.");

// Read the method table pointer.
long methodTablePointer = methodTableSpan.ReadNativeInteger(Machine.Is32Bit);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Collections;

namespace Echo.Platforms.AsmResolver.Emulation.Stack;

[DebuggerTypeProxy(typeof(DebuggerProxy))]
public partial class CallFrame
{
private sealed class DebuggerProxy
{
private readonly CallFrame _frame;

public DebuggerProxy(CallFrame frame)
{
_frame = frame;
}

public IMethodDescriptor Method => _frame.Method;

public int ProgramCounter => _frame.ProgramCounter;

public EvaluationStack EvaluationStack => _frame.EvaluationStack;

public ExceptionHandlerStack ExceptionHandlerStack => _frame.ExceptionHandlerStack;

public KeyValuePair<Parameter, object>[] Arguments
{
get
{
// Verify we have access to everything.
if (_frame is not {Body.Owner.Parameters: { } parameters, Method.Signature: { } signature})
return Array.Empty<KeyValuePair<Parameter, object>>();

// Verify count.
int count = signature.GetTotalParameterCount();
if (count == 0)
return Array.Empty<KeyValuePair<Parameter, object>>();

// Read all their values.
var result = new KeyValuePair<Parameter, object>[count];
for (int i = 0; i < result.Length; i++)
{
result[i] = new KeyValuePair<Parameter, object>(
parameters.GetBySignatureIndex(i),
_frame.ReadArgument(i)
);
}

return result;
}
}

public KeyValuePair<CilLocalVariable, object>[] Locals
{
get
{
// Verify locals exist.
if (_frame is not {Body.LocalVariables: {} locals} || locals.Count == 0)
return Array.Empty<KeyValuePair<CilLocalVariable, object>>();

// Read all their values.
var result = new KeyValuePair<CilLocalVariable, object>[locals.Count];
for (int i = 0; i < result.Length; i++)
result[i] = new KeyValuePair<CilLocalVariable, object>(locals[i], _frame.ReadLocal(i));

return result;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Echo.Platforms.AsmResolver.Emulation.Stack
/// <summary>
/// Represents a single frame in a virtual stack.
/// </summary>
public class CallFrame : IMemorySpace
public partial class CallFrame : IMemorySpace
{
// Stack layout sketch:
//
Expand Down Expand Up @@ -226,6 +226,25 @@ public long Allocate(int size)
? _baseAddress + _offsets[index]
: throw new ArgumentOutOfRangeException(nameof(index));

/// <summary>
/// Reads the value of a local variable into a buffer.
/// </summary>
/// <param name="index">The index of the variable.</param>
/// <returns>A bit vector with the current data of the local variable.</returns>
public BitVector ReadLocal(int index)
{
if (Body is null)
throw new ArgumentException("Method does not have a managed method body.");

var context = GenericContext.FromMethod(Method);
var type = Body.LocalVariables[index].VariableType.InstantiateGenericTypes(context);

var result = EvaluationStack.Factory.CreateValue(type, false);
ReadLocal(index, result);

return result;
}

/// <summary>
/// Reads the value of a local variable into a buffer.
/// </summary>
Expand All @@ -250,6 +269,25 @@ public long GetArgumentAddress(int index) => index < Method.Signature!.GetTotalP
? _baseAddress + _offsets[LocalsCount + 1 + index]
: throw new ArgumentOutOfRangeException(nameof(index));

/// <summary>
/// Reads the value of a local variable into a buffer.
/// </summary>
/// <param name="index">The index of the variable.</param>
/// <returns>A bit vector with the current data of the argument.</returns>
public BitVector ReadArgument(int index)
{
if (Body is null)
throw new ArgumentException("Method does not have a managed method body.");

var context = GenericContext.FromMethod(Method);
var type = Body.Owner.Parameters.GetBySignatureIndex(index).ParameterType.InstantiateGenericTypes(context);

var result = EvaluationStack.Factory.CreateValue(type, false);
ReadArgument(index, result);

return result;
}

/// <summary>
/// Reads the value of an argument into a buffer.
/// </summary>
Expand Down
Loading

0 comments on commit 90da97a

Please sign in to comment.