.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

653 lines
22 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Reflection;
using Debugger.Interop.CorDebug;
using Debugger.MetaData;
using System.Runtime.InteropServices;
namespace Debugger
{
public delegate Value ValueGetter(StackFrame context);
/// <summary>
/// Value class provides functions to examine value in the debuggee.
/// It has very life-time. In general, value dies whenever debugger is
/// resumed (this includes method invocation and property evaluation).
/// You can use Expressions to reobtain the value.
/// </summary>
public class Value: DebuggerObject
{
AppDomain appDomain;
ICorDebugValue corValue;
PauseSession corValue_pauseSession;
DebugType type;
// Permanently stored as convinience so that it survives Continue
bool isNull;
/// <summary> The appdomain that owns the value </summary>
public AppDomain AppDomain {
get { return appDomain; }
}
public Process Process {
get { return appDomain.Process; }
}
[Debugger.Tests.Ignore]
public ICorDebugValue CorValue {
get {
if (this.IsInvalid)
throw new GetValueException("Value is no longer valid");
return corValue;
}
}
[Debugger.Tests.Ignore]
public ICorDebugReferenceValue CorReferenceValue {
get {
if (IsNull) throw new GetValueException("Value is null");
if (!(this.CorValue is ICorDebugReferenceValue))
throw new DebuggerException("Reference value expected");
return (ICorDebugReferenceValue)this.CorValue;
}
}
[Debugger.Tests.Ignore]
public ICorDebugGenericValue CorGenericValue {
get {
if (IsNull) throw new GetValueException("Value is null");
ICorDebugValue corValue = this.CorValue;
// Dereference and unbox if necessary
if (corValue is ICorDebugReferenceValue)
corValue = ((ICorDebugReferenceValue)corValue).Dereference();
if (corValue is ICorDebugBoxValue)
corValue = ((ICorDebugBoxValue)corValue).GetObject();
if (!(corValue is ICorDebugGenericValue))
throw new DebuggerException("Value is not an generic value");
return (ICorDebugGenericValue)corValue;
}
}
[Debugger.Tests.Ignore]
public ICorDebugArrayValue CorArrayValue {
get {
if (IsNull) throw new GetValueException("Value is null");
if (!this.Type.IsArray) throw new DebuggerException("Value is not an array");
return (ICorDebugArrayValue)this.CorReferenceValue.Dereference();
}
}
[Debugger.Tests.Ignore]
public ICorDebugObjectValue CorObjectValue {
get {
if (IsNull) throw new GetValueException("Value is null");
ICorDebugValue corValue = this.CorValue;
// Dereference and unbox if necessary
if (corValue is ICorDebugReferenceValue)
corValue = ((ICorDebugReferenceValue)corValue).Dereference();
if (corValue is ICorDebugBoxValue)
return ((ICorDebugBoxValue)corValue).GetObject();
if (!(corValue is ICorDebugObjectValue))
throw new DebuggerException("Value is not an object");
return (ICorDebugObjectValue)corValue;
}
}
/// <summary> Returns the <see cref="Debugger.DebugType"/> of the value </summary>
public DebugType Type {
get { return type; }
}
/// <summary> Returns true if the Value can not be used anymore.
/// Value is valid only until the debuggee is resummed. </summary>
public bool IsInvalid {
get {
return corValue_pauseSession != this.Process.PauseSession &&
!(corValue is ICorDebugHandleValue);
}
}
/// <summary> Gets value indication whether the value is a reference </summary>
/// <remarks> Value types also return true if they are boxed </remarks>
public bool IsReference {
get {
return this.CorValue is ICorDebugReferenceValue;
}
}
/// <summary> Returns true if the value is null </summary>
public bool IsNull {
get { return isNull; }
}
/// <summary>
/// Gets the address in memory where this value is stored
/// </summary>
[Debugger.Tests.Ignore]
public ulong Address {
get { return corValue.GetAddress(); }
}
[Debugger.Tests.Ignore]
public ulong PointerAddress {
get {
if (!(this.CorValue is ICorDebugReferenceValue))
throw new DebuggerException("Not a pointer");
return ((ICorDebugReferenceValue)this.CorValue).GetValue();
}
}
/// <summary> Gets a string representation of the value </summary>
/// <param name="maxLength">
/// The maximum length of the result string.
/// </param>
public string AsString(int maxLength = int.MaxValue)
{
if (this.IsNull) return "null";
if (this.Type.IsPrimitive || this.Type.FullName == typeof(string).FullName) {
string text = PrimitiveValue.ToString();
if (text != null && text.Length > maxLength)
text = text.Substring(0, Math.Max(0, maxLength - 3)) + "...";
return text;
} else {
string name = this.Type.FullName;
if (name != null && name.Length > maxLength)
return "{" + name.Substring(0, Math.Max(0, maxLength - 5)) + "...}";
else
return "{" + name + "}";
}
}
internal Value(AppDomain appDomain, ICorDebugValue corValue)
{
if (corValue == null)
throw new ArgumentNullException("corValue");
this.appDomain = appDomain;
this.corValue = corValue;
this.corValue_pauseSession = this.Process.PauseSession;
this.isNull = corValue is ICorDebugReferenceValue && ((ICorDebugReferenceValue)corValue).IsNull() != 0;
if (corValue is ICorDebugReferenceValue &&
((ICorDebugReferenceValue)corValue).GetValue() == 0 &&
((ICorDebugValue2)corValue).GetExactType() == null)
{
// We were passed null reference and no metadata description
// (happens during CreateThread callback for the thread object)
this.type = appDomain.ObjectType;
} else {
ICorDebugType exactType = ((ICorDebugValue2)this.CorValue).GetExactType();
this.type = DebugType.CreateFromCorType(appDomain, exactType);
}
}
// Box value type
public Value Box()
{
byte[] rawValue = this.CorGenericValue.GetRawValue();
// The type must not be a primive type (always true in current design)
ICorDebugReferenceValue corRefValue = Eval.NewObjectNoConstructor(this.Type).CorReferenceValue;
// Make the reference to box permanent
corRefValue = ((ICorDebugHeapValue2)corRefValue.Dereference()).CreateHandle(CorDebugHandleType.HANDLE_STRONG);
// Create new value
Value newValue = new Value(appDomain, corRefValue);
// Copy the data inside the box
newValue.CorGenericValue.SetRawValue(rawValue);
return newValue;
}
[Debugger.Tests.Ignore]
public Value GetPermanentReference()
{
if (this.CorValue is ICorDebugHandleValue) {
return this;
} else if (this.CorValue is ICorDebugReferenceValue) {
if (this.IsNull)
return this; // ("null" expression) It isn't permanent
ICorDebugValue deRef = this.CorReferenceValue.Dereference();
if (deRef is ICorDebugHeapValue2) {
return new Value(appDomain, ((ICorDebugHeapValue2)deRef).CreateHandle(CorDebugHandleType.HANDLE_STRONG));
} else {
// For exampe int* is a refernce not pointing to heap
// TODO: It isn't permanent
return this;
}
} else {
return this.Box();
}
}
/// <summary> Dereferences a pointer type </summary>
/// <returns> Returns null for a null pointer </returns>
public Value Dereference()
{
if (!this.Type.IsPointer) throw new DebuggerException("Not a pointer");
ICorDebugReferenceValue corRef = (ICorDebugReferenceValue)this.CorValue;
if (corRef.GetValue() == 0 || corRef.Dereference() == null) {
return null;
} else {
return new Value(this.AppDomain, corRef.Dereference());
}
}
/// <summary> Copy the acutal value from some other Value object </summary>
public void SetValue(Value newValue)
{
ICorDebugValue newCorValue = newValue.CorValue;
if (this.CorValue is ICorDebugReferenceValue) {
if (!(newCorValue is ICorDebugReferenceValue))
newCorValue = newValue.Box().CorValue;
((ICorDebugReferenceValue)this.CorValue).SetValue(((ICorDebugReferenceValue)newCorValue).GetValue());
} else {
this.CorGenericValue.SetRawValue(newValue.CorGenericValue.GetRawValue());
}
}
#region Primitive
/// <summary>
/// Gets or sets the value of a primitive type.
///
/// If setting of a value fails, NotSupportedException is thrown.
/// </summary>
public object PrimitiveValue {
get {
if (this.Type.FullName == typeof(string).FullName) {
if (this.IsNull) return null;
return ((ICorDebugStringValue)this.CorReferenceValue.Dereference()).GetString();
} else {
if (this.Type.PrimitiveType == null)
throw new DebuggerException("Value is not a primitive type");
return CorGenericValue.GetValue(this.Type.PrimitiveType);
}
}
set {
if (this.Type.FullName == typeof(string).FullName) {
this.SetValue(Eval.NewString(this.AppDomain, value.ToString()));
} else {
if (this.Type.PrimitiveType == null)
throw new DebuggerException("Value is not a primitive type");
if (value == null)
throw new DebuggerException("Can not set primitive value to null");
object newValue;
try {
newValue = Convert.ChangeType(value, this.Type.PrimitiveType);
} catch {
throw new NotSupportedException("Can not convert " + value.GetType().ToString() + " to " + this.Type.PrimitiveType.ToString());
}
CorGenericValue.SetValue(newValue);
}
}
}
#endregion
#region Array
/// <summary>
/// Gets the number of elements in the array.
/// eg new object[4,5] returns 20
/// </summary>
/// <returns> 0 for non-arrays </returns>
public int ArrayLength {
get {
if (!this.Type.IsArray) return 0;
return (int)CorArrayValue.GetCount();
}
}
/// <summary>
/// Gets the number of dimensions of the array.
/// eg new object[4,5] returns 2
/// </summary>
/// <returns> 0 for non-arrays </returns>
public int ArrayRank {
get {
if (!this.Type.IsArray) return 0;
return (int)CorArrayValue.GetRank();
}
}
/// <summary> Gets the dimensions of the array </summary>
/// <returns> null for non-arrays </returns>
public ArrayDimensions ArrayDimensions {
get {
if (!this.Type.IsArray) return null;
int rank = this.ArrayRank;
uint[] baseIndicies;
if (CorArrayValue.HasBaseIndicies() == 1) {
baseIndicies = CorArrayValue.GetBaseIndicies();
} else {
baseIndicies = new uint[this.ArrayRank];
}
uint[] dimensionCounts = CorArrayValue.GetDimensions();
List<ArrayDimension> dimensions = new List<ArrayDimension>();
for(int i = 0; i < rank; i++) {
dimensions.Add(new ArrayDimension((int)baseIndicies[i], (int)baseIndicies[i] + (int)dimensionCounts[i] - 1));
}
return new ArrayDimensions(dimensions);
}
}
/// <summary> Returns an element of a single-dimensional array </summary>
public Value GetArrayElement(int index)
{
return GetArrayElement(new int[] {index});
}
/// <summary> Returns an element of an array </summary>
public Value GetArrayElement(int[] elementIndices)
{
int[] indices = (int[])elementIndices.Clone();
return new Value(this.AppDomain, GetCorValueOfArrayElement(indices));
}
// May be called later
ICorDebugValue GetCorValueOfArrayElement(int[] indices)
{
if (indices.Length != ArrayRank) {
throw new GetValueException("Given indicies do not have the same dimension as array.");
}
if (!this.ArrayDimensions.IsIndexValid(indices)) {
throw new GetValueException("Given indices are out of range of the array");
}
return CorArrayValue.GetElement(indices);
}
public void SetArrayElement(int[] elementIndices, Value newVal)
{
Value elem = GetArrayElement(elementIndices);
elem.SetValue(newVal);
}
/// <summary> Returns all elements in the array </summary>
public Value[] GetArrayElements()
{
if (!this.Type.IsArray) return null;
List<Value> values = new List<Value>();
foreach(int[] indices in this.ArrayDimensions.Indices) {
values.Add(GetArrayElement(indices));
}
return values.ToArray();
}
#endregion
#region Object
static void CheckObject(Value objectInstance, MemberInfo memberInfo)
{
if (memberInfo == null)
throw new DebuggerException("memberInfo");
IDebugMemberInfo debugMemberInfo = memberInfo as IDebugMemberInfo;
if (debugMemberInfo == null)
throw new DebuggerException("DebugMemberInfo must be used");
if (!debugMemberInfo.IsStatic) {
if (objectInstance == null)
throw new DebuggerException("No target object specified");
if (objectInstance.IsNull)
throw new GetValueException("Null reference");
//if (!objectInstance.IsObject) // eg Array.Length can be called
if (!debugMemberInfo.DeclaringType.IsInstanceOfType(objectInstance))
throw new GetValueException("Object is not of type " + debugMemberInfo.DeclaringType.FullName);
}
}
#region Convenience overload methods
/// <summary> Get a field or property of an object with a given name. </summary>
/// <returns> Null if not found </returns>
public Value GetMemberValue(string name)
{
MemberInfo memberInfo = this.Type.GetMember<MemberInfo>(name, DebugType.BindingFlagsAllInScope, DebugType.IsFieldOrNonIndexedProperty);
if (memberInfo == null)
return null;
return GetMemberValue(memberInfo);
}
/// <summary> Get the value of given member. </summary>
public Value GetMemberValue(MemberInfo memberInfo, params Value[] arguments)
{
return GetMemberValue(this, memberInfo, arguments);
}
#endregion
/// <summary> Get the value of given member. </summary>
/// <param name="objectInstance">null if member is static</param>
public static Value GetMemberValue(Value objectInstance, MemberInfo memberInfo, params Value[] arguments)
{
if (memberInfo is FieldInfo) {
if (arguments.Length > 0)
throw new GetValueException("Arguments can not be used for a field");
return GetFieldValue(objectInstance, (FieldInfo)memberInfo);
} else if (memberInfo is PropertyInfo) {
return GetPropertyValue(objectInstance, (PropertyInfo)memberInfo, arguments);
} else if (memberInfo is MethodInfo) {
return InvokeMethod(objectInstance, (MethodInfo)memberInfo, arguments);
}
throw new DebuggerException("Unknown member type: " + memberInfo.GetType());
}
#region Convenience overload methods
/// <summary> Get the value of given field. </summary>
public Value GetFieldValue(FieldInfo fieldInfo)
{
return Value.GetFieldValue(this, fieldInfo);
}
#endregion
public static void SetFieldValue(Value objectInstance, FieldInfo fieldInfo, Value newValue)
{
Value val = GetFieldValue(objectInstance, fieldInfo);
if (!fieldInfo.FieldType.IsAssignableFrom(newValue.Type))
throw new GetValueException("Can not assign {0} to {1}", newValue.Type.FullName, fieldInfo.FieldType.FullName);
val.SetValue(newValue);
}
/// <summary> Get the value of given field. </summary>
/// <param name="objectInstance">null if field is static</param>
public static Value GetFieldValue(Value objectInstance, FieldInfo fieldInfo)
{
CheckObject(objectInstance, fieldInfo);
if (fieldInfo.IsStatic && fieldInfo.IsLiteral) {
return GetLiteralValue((DebugFieldInfo)fieldInfo);
} else {
return new Value(
((DebugFieldInfo)fieldInfo).AppDomain,
GetFieldCorValue(objectInstance, fieldInfo)
);
}
}
static ICorDebugValue GetFieldCorValue(Value objectInstance, FieldInfo fieldInfo)
{
Process process = ((DebugFieldInfo)fieldInfo).Process;
// Current frame is used to resolve context specific static values (eg. ThreadStatic)
ICorDebugFrame curFrame = null;
if (process.IsPaused &&
process.SelectedThread != null &&
process.SelectedThread.MostRecentStackFrame != null &&
process.SelectedThread.MostRecentStackFrame.CorILFrame != null)
{
curFrame = process.SelectedThread.MostRecentStackFrame.CorILFrame;
}
try {
if (fieldInfo.IsStatic) {
return ((DebugType)fieldInfo.DeclaringType).CorType.GetStaticFieldValue((uint)fieldInfo.MetadataToken, curFrame);
} else {
return objectInstance.CorObjectValue.GetFieldValue(((DebugType)fieldInfo.DeclaringType).CorType.GetClass(), (uint)fieldInfo.MetadataToken);
}
} catch (COMException e) {
throw new GetValueException("Can not get value of field", e);
}
}
static Value GetLiteralValue(DebugFieldInfo fieldInfo)
{
CorElementType corElemType = (CorElementType)fieldInfo.FieldProps.ConstantType;
if (corElemType == CorElementType.CLASS) {
// Only null literals are allowed
return Eval.CreateValue(fieldInfo.AppDomain, null);
} else if (corElemType == CorElementType.STRING) {
string str = Marshal.PtrToStringUni(fieldInfo.FieldProps.ConstantPtr, (int)fieldInfo.FieldProps.ConstantStringLength);
return Eval.CreateValue(fieldInfo.AppDomain, str);
} else {
DebugType type = DebugType.CreateFromType(fieldInfo.AppDomain.Mscorlib, DebugType.CorElementTypeToManagedType(corElemType));
if (fieldInfo.FieldType.IsEnum && fieldInfo.FieldType.GetEnumUnderlyingType() == type) {
Value val = Eval.NewObjectNoConstructor((DebugType)fieldInfo.FieldType);
Value backingField = val.GetMemberValue("value__");
backingField.CorGenericValue.SetValue(fieldInfo.FieldProps.ConstantPtr);
return val;
} else {
Value val = Eval.NewObjectNoConstructor(type);
val.CorGenericValue.SetValue(fieldInfo.FieldProps.ConstantPtr);
return val;
}
}
}
#region Convenience overload methods
/// <summary> Get the value of the property using the get accessor </summary>
public Value GetPropertyValue(PropertyInfo propertyInfo, params Value[] arguments)
{
return GetPropertyValue(this, propertyInfo, arguments);
}
#endregion
/// <summary> Get the value of the property using the get accessor </summary>
public static Value GetPropertyValue(Value objectInstance, PropertyInfo propertyInfo, params Value[] arguments)
{
CheckObject(objectInstance, propertyInfo);
if (propertyInfo.GetGetMethod() == null) throw new GetValueException("Property does not have a get method");
Value val = Value.InvokeMethod(objectInstance, (DebugMethodInfo)propertyInfo.GetGetMethod(), arguments);
return val;
}
#region Convenience overload methods
/// <summary> Set the value of the property using the set accessor </summary>
public Value SetPropertyValue(PropertyInfo propertyInfo, Value newValue)
{
return SetPropertyValue(this, propertyInfo, null, newValue);
}
/// <summary> Set the value of the property using the set accessor </summary>
public Value SetPropertyValue(PropertyInfo propertyInfo, Value[] arguments, Value newValue)
{
return SetPropertyValue(this, propertyInfo, arguments, newValue);
}
/// <summary> Set the value of the property using the set accessor </summary>
public static Value SetPropertyValue(Value objectInstance, PropertyInfo propertyInfo, Value newValue)
{
return SetPropertyValue(objectInstance, propertyInfo, null, newValue);
}
#endregion
/// <summary> Set the value of the property using the set accessor </summary>
public static Value SetPropertyValue(Value objectInstance, PropertyInfo propertyInfo, Value[] arguments, Value newValue)
{
CheckObject(objectInstance, propertyInfo);
if (propertyInfo.GetSetMethod() == null) throw new GetValueException("Property does not have a set method");
arguments = arguments ?? new Value[0];
Value[] allParams = new Value[1 + arguments.Length];
allParams[0] = newValue;
arguments.CopyTo(allParams, 1);
return Value.InvokeMethod(objectInstance, (DebugMethodInfo)propertyInfo.GetSetMethod(), allParams);
}
#region Convenience overload methods
/// <summary> Synchronously invoke the method </summary>
public Value InvokeMethod(MethodInfo methodInfo, params Value[] arguments)
{
return InvokeMethod(this, methodInfo, arguments);
}
#endregion
/// <summary> Synchronously invoke the method </summary>
public static Value InvokeMethod(Value objectInstance, MethodInfo methodInfo, params Value[] arguments)
{
CheckObject(objectInstance, methodInfo);
return Eval.InvokeMethod(
(DebugMethodInfo)methodInfo,
methodInfo.IsStatic ? null : objectInstance,
arguments ?? new Value[0]
);
}
/// <summary> Invoke the ToString() method </summary>
public string InvokeToString(int maxLength = int.MaxValue)
{
if (this.Type.IsPrimitive) return AsString(maxLength);
if (this.Type.FullName == typeof(string).FullName) return AsString(maxLength);
if (this.Type.IsPointer) return "0x" + this.PointerAddress.ToString("X");
// if (!IsObject) // Can invoke on primitives
DebugMethodInfo methodInfo = (DebugMethodInfo)this.AppDomain.ObjectType.GetMethod("ToString", new DebugType[] {});
return Eval.InvokeMethod(methodInfo, this, new Value[] {}).AsString(maxLength);
}
#region Convenience overload methods
/// <summary> Asynchronously invoke the method </summary>
public Eval AsyncInvokeMethod(MethodInfo methodInfo, params Value[] arguments)
{
return AsyncInvokeMethod(this, methodInfo, arguments);
}
#endregion
/// <summary> Asynchronously invoke the method </summary>
public static Eval AsyncInvokeMethod(Value objectInstance, MethodInfo methodInfo, params Value[] arguments)
{
CheckObject(objectInstance, methodInfo);
return Eval.AsyncInvokeMethod(
(DebugMethodInfo)methodInfo,
methodInfo.IsStatic ? null : objectInstance,
arguments ?? new Value[0]
);
}
#endregion
public override string ToString()
{
return this.AsString();
}
}
}