// 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.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using Debugger.Interop.CorDebug; using Debugger.Interop.CorSym; using Debugger.Interop.MetaData; using Mono.Cecil.Signatures; namespace Debugger.MetaData { public class DebugMethodInfo: System.Reflection.MethodInfo, IDebugMemberInfo, IOverloadable { DebugType declaringType; MethodProps methodProps; internal DebugMethodInfo(DebugType declaringType, MethodProps methodProps) { this.declaringType = declaringType; this.methodProps = methodProps; } /// public override Type DeclaringType { get { return declaringType; } } /// The AppDomain in which this member is declared public AppDomain AppDomain { get { return declaringType.AppDomain; } } /// The Process in which this member is declared public Process Process { get { return declaringType.Process; } } /// The Module in which this member is declared public Debugger.Module DebugModule { get { return declaringType.DebugModule; } } /// public override int MetadataToken { get { return (int)methodProps.Token; } } /// public override System.Reflection.Module Module { get { throw new NotSupportedException(); } } /// Name including the declaring type, return type and parameters public string FullName { get { StringBuilder sb = new StringBuilder(); if (this.IsStatic) { sb.Append("static "); } if (this.ReturnType != null) { sb.Append(this.ReturnType.Name); sb.Append(" "); } else { sb.Append("void "); } sb.Append(this.DeclaringType.FullName); sb.Append("."); sb.Append(this.Name); sb.Append("("); bool first = true; foreach(DebugParameterInfo p in GetParameters()) { if (!first) sb.Append(", "); first = false; sb.Append(p.ParameterType.Name); sb.Append(" "); sb.Append(p.Name); } sb.Append(")"); return sb.ToString(); } } /// public override string Name { get { return methodProps.Name; } } /// public override Type ReflectedType { get { throw new NotSupportedException(); } } /// public override object[] GetCustomAttributes(bool inherit) { throw new NotSupportedException(); } /// public override object[] GetCustomAttributes(Type attributeType, bool inherit) { throw new NotSupportedException(); } /// public override bool IsDefined(Type attributeType, bool inherit) { return DebugType.IsDefined(this, inherit, attributeType); } // public virtual Type[] GetGenericArguments(); // public virtual MethodBody GetMethodBody(); /// public override MethodImplAttributes GetMethodImplementationFlags() { return (MethodImplAttributes)methodProps.ImplFlags; } /// public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) { List args = new List(); foreach(object arg in parameters) { args.Add((Value)arg); } if (this.IsSpecialName && this.Name == ".ctor") { if (obj != null) throw new GetValueException("'obj' must be null for constructor call"); return Eval.NewObject(this, args.ToArray()); } else { return Eval.InvokeMethod(this, (Value)obj, args.ToArray()); } } /// public override MethodAttributes Attributes { get { return (MethodAttributes)methodProps.Flags; } } // public virtual CallingConventions CallingConvention { get; } /// public override bool ContainsGenericParameters { get { throw new NotSupportedException(); } } /// public override bool IsGenericMethod { get { throw new NotSupportedException(); } } /// public override bool IsGenericMethodDefinition { get { throw new NotSupportedException(); } } /// public override RuntimeMethodHandle MethodHandle { get { throw new NotSupportedException(); } } /// public override MethodInfo GetBaseDefinition() { throw new NotSupportedException(); } // public override Type[] GetGenericArguments(); // public virtual MethodInfo GetGenericMethodDefinition(); // public virtual MethodInfo MakeGenericMethod(params Type[] typeArguments); // public override bool ContainsGenericParameters { get; } /// public override Type ReturnType { get { if (this.MethodDefSig.RetType.Void) return null; return DebugType.CreateFromSignature(this.DebugModule, this.MethodDefSig.RetType.Type, declaringType); } } /// public override ParameterInfo ReturnParameter { get { if (this.MethodDefSig.RetType.Void) return null; return new DebugParameterInfo(this, string.Empty, this.ReturnType, -1, delegate { throw new NotSupportedException(); }); } } /// public override ICustomAttributeProvider ReturnTypeCustomAttributes { get { throw new NotSupportedException(); } } MethodDefSig methodDefSig; MethodDefSig MethodDefSig { get { if (methodDefSig == null) { SignatureReader sigReader = new SignatureReader(methodProps.SigBlob.GetData()); methodDefSig = sigReader.GetMethodDefSig(0); } return methodDefSig; } } /// Gets the number of paramters of this method public int ParameterCount { get { return this.MethodDefSig.ParamCount; } } ParameterInfo[] parameters; public DebugParameterInfo GetParameter(string name) { foreach(DebugParameterInfo par in GetParameters()) { if (par.Name == name) return par; } return null; } /// public override ParameterInfo[] GetParameters() { if (parameters == null) { parameters = new ParameterInfo[this.MethodDefSig.ParamCount]; for(int i = 0; i < parameters.Length; i++) { string name; try { // index = 0 is return parameter name = this.DebugModule.MetaData.GetParamPropsForMethodIndex((uint)this.MetadataToken, (uint)i + 1).Name; } catch { name = String.Empty; } int iCopy = i; parameters[i] = new DebugParameterInfo( this, name, DebugType.CreateFromSignature(this.DebugModule, this.MethodDefSig.Parameters[i].Type, declaringType), i, delegate (StackFrame context) { return context.GetArgumentValue(iCopy); } ); } } return parameters; } internal ICorDebugFunction CorFunction { get { return this.DebugModule.CorModule.GetFunctionFromToken((uint)this.MetadataToken); } } /// Gets value indicating whether this method should be stepped over /// accoring to current options public bool StepOver { get { Options opt = this.Process.Options; if (opt.StepOverNoSymbols) { if (this.SymMethod == null) return true; } if (opt.StepOverDebuggerAttributes) { if (this.IsNonUserCode) return true; } if (opt.StepOverAllProperties) { if (this.IsPropertyAccessor) return true; } if (opt.StepOverSingleLineProperties) { if (this.IsPropertyAccessor && this.IsSingleLine) return true; } if (opt.StepOverFieldAccessProperties) { if (this.IsPropertyAccessor && this.BackingFieldToken != 0) return true; } return false; } } internal bool IsPropertyAccessor { get; set; } uint backingFieldToken; /// /// Backing field that can be used to obtain the same value as by calling this method. /// [Tests.Ignore] public uint BackingFieldToken { get { LoadBackingFieldToken(); return backingFieldToken; } } /// /// Backing field that can be used to obtain the same value as by calling this method. /// It works only for fields defined in the class /// public DebugFieldInfo BackingField { get { uint token = this.BackingFieldToken; if (token == 0) return null; // The token can be a field in different class (static or instance in base class) - so it might not be found in the next call MemberInfo member; if (!declaringType.TryGetMember(token, out member)) return null; return (DebugFieldInfo)member; } } bool loadBackingFieldTokenCalled; /// Is this method in form 'return this.field;'? void LoadBackingFieldToken() { if (loadBackingFieldTokenCalled) return; loadBackingFieldTokenCalled = true; backingFieldToken = 0; if (this.ParameterCount != 0) return; ICorDebugCode corCode; try { corCode = this.CorFunction.GetILCode(); } catch (COMException) { return; } if (corCode == null || corCode.IsIL() == 0 || corCode.GetSize() > 12) return; List code = new List(corCode.GetCode()); uint token = 0; bool success = (Read(code, 0x00) || true) && // nop || nothing (Read(code, 0x02, 0x7B) || Read(code, 0x7E)) && // ldarg.0; ldfld || ldsfld ReadToken(code, ref token) && // (Read(code, 0x0A, 0x2B, 0x00, 0x06) || true) && // stloc.0; br.s; offset+00; ldloc.0 || nothing Read(code, 0x2A); // ret if (!success) return; if (this.Process.Options.Verbose) { this.Process.TraceMessage(string.Format("Found backing field for {0}", this.FullName)); } backingFieldToken = token; } // Read expected sequence of bytes static bool Read(List code, params byte[] expected) { if (code.Count < expected.Length) return false; for(int i = 0; i < expected.Length; i++) { if (code[i] != expected[i]) return false; } code.RemoveRange(0, expected.Length); return true; } // Read field token static bool ReadToken(List code, ref uint token) { if (code.Count < 4) return false; if (code[3] != 0x04) // field token return false; token = ((uint)code[0]) + ((uint)code[1] << 8) + ((uint)code[2] << 16) + ((uint)code[3] << 24); code.RemoveRange(0, 4); return true; } bool? isSingleLine; bool IsSingleLine { get { // Note symbols might get loaded manually later by the user ISymUnmanagedMethod symMethod = this.SymMethod; if (symMethod == null) return false; // No symbols - can not determine if (isSingleLine.HasValue) return isSingleLine.Value; List seqPoints = new List(symMethod.GetSequencePoints()); seqPoints.Sort(); // Remove initial "{" if (seqPoints.Count > 0 && seqPoints[0].Line == seqPoints[0].EndLine && seqPoints[0].EndColumn - seqPoints[0].Column <= 1) { seqPoints.RemoveAt(0); } // Remove last "}" int listIndex = seqPoints.Count - 1; if (seqPoints.Count > 0 && seqPoints[listIndex].Line == seqPoints[listIndex].EndLine && seqPoints[listIndex].EndColumn - seqPoints[listIndex].Column <= 1) { seqPoints.RemoveAt(listIndex); } // Is single line isSingleLine = seqPoints.Count == 0 || seqPoints[0].Line == seqPoints[seqPoints.Count - 1].EndLine; return isSingleLine.Value; } } bool? isNonUserCode; public bool IsNonUserCode { get { if (isNonUserCode.HasValue) return isNonUserCode.Value; isNonUserCode = // Look on the method DebugType.IsDefined( this, false, typeof(System.Diagnostics.DebuggerStepThroughAttribute), typeof(System.Diagnostics.DebuggerNonUserCodeAttribute), typeof(System.Diagnostics.DebuggerHiddenAttribute)) || // Look on the type DebugType.IsDefined( declaringType, false, typeof(System.Diagnostics.DebuggerStepThroughAttribute), typeof(System.Diagnostics.DebuggerNonUserCodeAttribute), typeof(System.Diagnostics.DebuggerHiddenAttribute)); return isNonUserCode.Value; } } internal void MarkAsNonUserCode() { ((ICorDebugFunction2)this.CorFunction).SetJMCStatus(0 /* false */); if (this.Process.Options.Verbose) { this.Process.TraceMessage("Funciton {0} marked as non-user code", this.FullName); } } internal ISymUnmanagedMethod SymMethod { get { if (this.DebugModule.SymReader == null) return null; try { return this.DebugModule.SymReader.GetMethod((uint)this.MetadataToken); } catch { return null; } } } public DebugLocalVariableInfo GetLocalVariable(int offset, string name) { foreach(DebugLocalVariableInfo loc in GetLocalVariables(offset)) { if (loc.Name == name) return loc; } return null; } [Debugger.Tests.Ignore] public DebugLocalVariableInfo GetLocalVariableThis() { foreach(DebugLocalVariableInfo loc in GetLocalVariables()) { if (loc.IsThis) return loc; } return null; } /// Get local variables valid at the given IL offset public IEnumerable GetLocalVariables(int offset) { foreach (DebugLocalVariableInfo varInfo in GetLocalVariables()) { if (varInfo.StartOffset <= offset && offset < varInfo.EndOffset) { yield return varInfo; } } } List localVariables; public List GetLocalVariables() { if (localVariables != null) return localVariables; // Generated constructor may not have any symbols if (this.SymMethod == null) return new List(); localVariables = GetLocalVariablesInScope(this.SymMethod.GetRootScope()); if (declaringType.IsDisplayClass || declaringType.IsYieldEnumerator) { // Get display class from self AddCapturedLocalVariables( localVariables, 0, int.MaxValue, delegate(StackFrame context) { return context.GetThisValue(); }, declaringType ); // Get dispaly classes from fields foreach(DebugFieldInfo fieldInfo in this.DeclaringType.GetFields()) { DebugFieldInfo fieldInfoCopy = fieldInfo; if (fieldInfo.Name.StartsWith("CS$")) { AddCapturedLocalVariables( localVariables, 0, int.MaxValue, delegate(StackFrame context) { return context.GetThisValue().GetFieldValue(fieldInfoCopy); }, (DebugType)fieldInfo.FieldType ); } } } else { // Add this if (!this.IsStatic) { DebugLocalVariableInfo thisVar = new DebugLocalVariableInfo( "this", -1, 0, int.MaxValue, declaringType, delegate(StackFrame context) { return context.GetThisValue(); } ); thisVar.IsThis = true; localVariables.Add(thisVar); } } return localVariables; } static void AddCapturedLocalVariables(List vars, int scopeStartOffset, int scopeEndOffset, ValueGetter getCaptureClass, DebugType captureClassType) { if (captureClassType.IsDisplayClass || captureClassType.IsYieldEnumerator) { foreach(DebugFieldInfo fieldInfo in captureClassType.GetFields()) { DebugFieldInfo fieldInfoCopy = fieldInfo; if (fieldInfo.Name.StartsWith("CS$")) continue; // Ignore DebugLocalVariableInfo locVar = new DebugLocalVariableInfo( fieldInfo.Name, -1, scopeStartOffset, scopeEndOffset, (DebugType)fieldInfo.FieldType, delegate(StackFrame context) { return getCaptureClass(context).GetFieldValue(fieldInfoCopy); } ); locVar.IsCaptured = true; if (locVar.Name.StartsWith("<>")) { bool hasThis = false; foreach(DebugLocalVariableInfo l in vars) { if (l.IsThis) { hasThis = true; break; } } if (!hasThis && locVar.Name.EndsWith("__this")) { locVar.Name = "this"; locVar.IsThis = true; } else { continue; // Ignore } } if (locVar.Name.StartsWith("<")) { int endIndex = locVar.Name.IndexOf('>'); if (endIndex == -1) continue; // Ignore locVar.Name = fieldInfo.Name.Substring(1, endIndex - 1); } vars.Add(locVar); } } } List GetLocalVariablesInScope(ISymUnmanagedScope symScope) { List vars = new List(); foreach (ISymUnmanagedVariable symVar in symScope.GetLocals()) { ISymUnmanagedVariable symVarCopy = symVar; int start; SignatureReader sigReader = new SignatureReader(symVar.GetSignature()); LocalVarSig.LocalVariable locVarSig = sigReader.ReadLocalVariable(sigReader.Blob, 0, out start); DebugType locVarType = DebugType.CreateFromSignature(this.DebugModule, locVarSig.Type, declaringType); // Compiler generated? // NB: Display class does not have the compiler-generated flag if ((symVar.GetAttributes() & 1) == 1 || symVar.GetName().StartsWith("CS$")) { // Get display class from local variable if (locVarType.IsDisplayClass) { AddCapturedLocalVariables( vars, (int)symScope.GetStartOffset(), (int)symScope.GetEndOffset(), delegate(StackFrame context) { return GetLocalVariableValue(context, symVarCopy); }, locVarType ); } } else { DebugLocalVariableInfo locVar = new DebugLocalVariableInfo( symVar.GetName(), (int)symVar.GetAddressField1(), // symVar also has Get*Offset methods, but the are not implemented (int)symScope.GetStartOffset(), (int)symScope.GetEndOffset(), locVarType, delegate(StackFrame context) { return GetLocalVariableValue(context, symVarCopy); } ); vars.Add(locVar); } } foreach(ISymUnmanagedScope childScope in symScope.GetChildren()) { vars.AddRange(GetLocalVariablesInScope(childScope)); } return vars; } static Value GetLocalVariableValue(StackFrame context, ISymUnmanagedVariable symVar) { ICorDebugValue corVal; try { corVal = context.CorILFrame.GetLocalVariable((uint)symVar.GetAddressField1()); } catch (COMException e) { if ((uint)e.ErrorCode == 0x80131304) throw new GetValueException("Unavailable in optimized code"); throw; } return new Value(context.AppDomain, corVal); } /// public override string ToString() { return this.FullName; } IntPtr IOverloadable.GetSignarture() { return methodProps.SigBlob.Adress; } DebugType IDebugMemberInfo.MemberType { get { return (DebugType)this.ReturnType; } } } }