// 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); /// /// 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. /// public class Value: DebuggerObject { AppDomain appDomain; ICorDebugValue corValue; PauseSession corValue_pauseSession; DebugType type; // Permanently stored as convinience so that it survives Continue bool isNull; /// The appdomain that owns the value 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; } } /// Returns the of the value public DebugType Type { get { return type; } } /// Returns true if the Value can not be used anymore. /// Value is valid only until the debuggee is resummed. public bool IsInvalid { get { return corValue_pauseSession != this.Process.PauseSession && !(corValue is ICorDebugHandleValue); } } /// Gets value indication whether the value is a reference /// Value types also return true if they are boxed public bool IsReference { get { return this.CorValue is ICorDebugReferenceValue; } } /// Returns true if the value is null public bool IsNull { get { return isNull; } } /// /// Gets the address in memory where this value is stored /// [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(); } } /// Gets a string representation of the value /// /// The maximum length of the result string. /// 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(); } } /// Dereferences a pointer type /// Returns null for a null pointer 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()); } } /// Copy the acutal value from some other Value object 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 /// /// Gets or sets the value of a primitive type. /// /// If setting of a value fails, NotSupportedException is thrown. /// 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 /// /// Gets the number of elements in the array. /// eg new object[4,5] returns 20 /// /// 0 for non-arrays public int ArrayLength { get { if (!this.Type.IsArray) return 0; return (int)CorArrayValue.GetCount(); } } /// /// Gets the number of dimensions of the array. /// eg new object[4,5] returns 2 /// /// 0 for non-arrays public int ArrayRank { get { if (!this.Type.IsArray) return 0; return (int)CorArrayValue.GetRank(); } } /// Gets the dimensions of the array /// null for non-arrays 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 dimensions = new List(); 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); } } /// Returns an element of a single-dimensional array public Value GetArrayElement(int index) { return GetArrayElement(new int[] {index}); } /// Returns an element of an array 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); } /// Returns all elements in the array public Value[] GetArrayElements() { if (!this.Type.IsArray) return null; List values = new List(); 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 /// Get a field or property of an object with a given name. /// Null if not found public Value GetMemberValue(string name) { MemberInfo memberInfo = this.Type.GetMember(name, DebugType.BindingFlagsAllInScope, DebugType.IsFieldOrNonIndexedProperty); if (memberInfo == null) return null; return GetMemberValue(memberInfo); } /// Get the value of given member. public Value GetMemberValue(MemberInfo memberInfo, params Value[] arguments) { return GetMemberValue(this, memberInfo, arguments); } #endregion /// Get the value of given member. /// null if member is static 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 /// Get the value of given field. 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); } /// Get the value of given field. /// null if field is static 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 /// Get the value of the property using the get accessor public Value GetPropertyValue(PropertyInfo propertyInfo, params Value[] arguments) { return GetPropertyValue(this, propertyInfo, arguments); } #endregion /// Get the value of the property using the get accessor 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 /// Set the value of the property using the set accessor public Value SetPropertyValue(PropertyInfo propertyInfo, Value newValue) { return SetPropertyValue(this, propertyInfo, null, newValue); } /// Set the value of the property using the set accessor public Value SetPropertyValue(PropertyInfo propertyInfo, Value[] arguments, Value newValue) { return SetPropertyValue(this, propertyInfo, arguments, newValue); } /// Set the value of the property using the set accessor public static Value SetPropertyValue(Value objectInstance, PropertyInfo propertyInfo, Value newValue) { return SetPropertyValue(objectInstance, propertyInfo, null, newValue); } #endregion /// Set the value of the property using the set accessor 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 /// Synchronously invoke the method public Value InvokeMethod(MethodInfo methodInfo, params Value[] arguments) { return InvokeMethod(this, methodInfo, arguments); } #endregion /// Synchronously invoke the method 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] ); } /// Invoke the ToString() method 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 /// Asynchronously invoke the method public Eval AsyncInvokeMethod(MethodInfo methodInfo, params Value[] arguments) { return AsyncInvokeMethod(this, methodInfo, arguments); } #endregion /// Asynchronously invoke the method 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(); } } }