// // // // // $Revision: 2285 $ // using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using Debugger.Wrappers.CorDebug; using Debugger.Wrappers.CorSym; using Debugger.Wrappers.MetaData; namespace Debugger { /// /// A function (or also a method or frame) which is being executed on /// some thread. Use to obtain arguments or local variables. /// public class Function: DebuggerObject, IExpirable { Process process; Module module; ICorDebugFunction corFunction; ICorDebugILFrame corILFrame; object corILFramePauseSession; Stepper stepOutStepper; bool steppedOut = false; Thread thread; FrameID frameID; MethodProps methodProps; /// The process in which this function is executed [Debugger.Tests.Ignore] public Process Process { get { return process; } } public ICorDebugFunction CorFunction { get { return corFunction; } } /// The name of the function (eg "ToString") public string Name { get { return methodProps.Name; } } /// Metadata token of the function [Debugger.Tests.Ignore] public uint Token { get { return methodProps.Token; } } /// A module in which the function is defined [Debugger.Tests.SummaryOnly] public Module Module { get { return module; } } /// A thread in which the function is executed [Debugger.Tests.Ignore] public Thread Thread { get { return thread; } } /// True if the function is static public bool IsStatic { get { return methodProps.IsStatic; } } /// True if the function has symbols defined. /// (That is has accesss to the .pdb file) public bool HasSymbols { get { return GetSegmentForOffet(0) != null; } } /// The class that defines this function internal ICorDebugClass ContaingClass { // TODO: Use DebugType get { return corFunction.Class; } } /// True if function stepped out and is not longer valid. public bool HasExpired { get { return steppedOut || Module.Unloaded; } } /// Occurs when function expires and is no longer usable public event EventHandler Expired; /// Is called when function expires and is no longer usable internal protected virtual void OnExpired(EventArgs e) { if (!steppedOut) { steppedOut = true; process.TraceMessage("Function " + this.ToString() + " expired"); if (Expired != null) { Expired(this, e); } } } internal Function(Thread thread, FrameID frameID, ICorDebugILFrame corILFrame) { this.process = thread.Process; this.thread = thread; this.frameID = frameID; this.CorILFrame = corILFrame; corFunction = corILFrame.Function; module = process.GetModule(corFunction.Module); methodProps = module.MetaData.GetMethodProps(corFunction.Token); // Force some callback when function steps out so that we can expire it stepOutStepper = new Stepper(this, "Function Tracker"); stepOutStepper.StepOut(); stepOutStepper.PauseWhenComplete = false; process.TraceMessage("Function " + this.ToString() + " created"); } /// Returns diagnostic description of the frame public override string ToString() { return methodProps.Name + "(" + frameID.ToString() + ")"; } internal ICorDebugILFrame CorILFrame { get { if (HasExpired) throw new DebuggerException("Function has expired"); if (corILFramePauseSession != process.PauseSession) { CorILFrame = thread.GetFrameAt(frameID).As(); } return corILFrame; } set { if (value == null) throw new DebuggerException("Can not set frame to null"); corILFrame = value; corILFramePauseSession = process.PauseSession; } } internal uint corInstructionPtr { get { uint corInstructionPtr; CorILFrame.GetIP(out corInstructionPtr); return corInstructionPtr; } } public ISymUnmanagedReader symReader { get { if (module.SymbolsLoaded == false) return null; if (module.SymReader == null) return null; return module.SymReader; } } public ISymUnmanagedMethod symMethod { get { if (symReader == null) { return null; } else { try { return symReader.GetMethod(methodProps.Token); } catch { return null; } } } } /// Step into next instruction public void StepInto() { Step(true); } /// Step over next instruction public void StepOver() { Step(false); } /// Step out of the function public void StepOut() { new Stepper(this, "Function step out").StepOut(); process.Continue(); } private unsafe void Step(bool stepIn) { if (Module.SymbolsLoaded == false) { throw new DebuggerException("Unable to step. No symbols loaded."); } SourcecodeSegment nextSt; nextSt = NextStatement; if (nextSt == null) { throw new DebuggerException("Unable to step. Next statement not aviable"); } if (stepIn) { new Stepper(this, "Function step in").StepIn(nextSt.StepRanges); // Without JMC step in which ends in code without symblols is cotinued. // The next step over ensures that we at least do step over. new Stepper(this, "Safety step over").StepOver(nextSt.StepRanges); } else { new Stepper(this, "Function step over").StepOver(nextSt.StepRanges); } process.Continue(); } /// /// Get the information about the next statement to be executed. /// /// Returns null on error. /// public SourcecodeSegment NextStatement { get { return GetSegmentForOffet(corInstructionPtr); } } /// /// Returns null on error. /// /// 'ILStart <= ILOffset <= ILEnd' and this range includes at least /// the returned area of source code. (May incude some extra compiler generated IL too) /// public SourcecodeSegment GetSegmentForOffet(uint offset) { ISymUnmanagedMethod symMethod; symMethod = this.symMethod; if (symMethod == null) { return null; } uint sequencePointCount = symMethod.SequencePointCount; SequencePoint[] sequencePoints = symMethod.SequencePoints; SourcecodeSegment retVal = new SourcecodeSegment(); // Get i for which: offsets[i] <= offset < offsets[i + 1] // or fallback to first element if offset < offsets[0] for (int i = (int)sequencePointCount - 1; i >= 0; i--) // backwards if (sequencePoints[i].Offset <= offset || i == 0) { // Set inforamtion about current IL range int codeSize = (int)corFunction.ILCode.Size; retVal.ILOffset = (int)offset; retVal.ILStart = (int)sequencePoints[i].Offset; retVal.ILEnd = (i + 1 < sequencePointCount) ? (int)sequencePoints[i+1].Offset : codeSize; // 0xFeeFee means "code generated by compiler" // If we are in generated sequence use to closest real one instead, // extend the ILStart and ILEnd to include the 'real' sequence int k = i; // Look ahead for 'real' sequence while (i + 1 < sequencePointCount && sequencePoints[i].Line == 0xFeeFee){ i++; retVal.ILEnd = (i + 1 < sequencePointCount) ? (int)sequencePoints[i+1].Offset : codeSize; } // Look back for 'real' sequence while (i - 1 >= 0 && sequencePoints[i].Line == 0xFeeFee) { i--; retVal.ILStart = (int)sequencePoints[i].Offset; } // Wow, there are no 'real' sequences if (sequencePoints[i].Line == 0xFeeFee) { return null; } retVal.ModuleFilename = module.FullPath; retVal.SourceFullFilename = sequencePoints[i].Document.URL; /*if (retVal.SourceFullFilename != null && Path.GetFileName(retVal.SourceFullFilename).StartsWith("_temp$")) { retVal.SourceFullFilename = Path.Combine(Path.GetDirectoryName(retVal.SourceFullFilename),Path.GetFileName(retVal.SourceFullFilename).Substring(6)); System.Diagnostics.Debug.WriteLine(retVal.SourceFullFilename); }*/ retVal.StartLine = (int)sequencePoints[i].Line; retVal.StartColumn = (int)sequencePoints[i].Column; retVal.EndLine = (int)sequencePoints[i].EndLine; retVal.EndColumn = (int)sequencePoints[i].EndColumn; List stepRanges = new List(); for (int j = 0; j < sequencePointCount; j++) { // Step over compiler generated sequences and current statement // 0xFeeFee means "code generated by compiler" if (sequencePoints[j].Line == 0xFeeFee || j == i || ( j > i && (sequencePoints[j].Line == sequencePoints[i].Line && sequencePoints[i].Document.URL == sequencePoints[j].Document.URL))) { // line + document must be same // Add start offset or remove last end (to connect two ranges into one) if (stepRanges.Count > 0 && stepRanges[stepRanges.Count - 1] == sequencePoints[j].Offset) { stepRanges.RemoveAt(stepRanges.Count - 1); } else { stepRanges.Add((int)sequencePoints[j].Offset); } // Add end offset | handle last sequence point if (j + 1 < sequencePointCount) { stepRanges.Add((int)sequencePoints[j+1].Offset); } else { stepRanges.Add(codeSize); } } } retVal.StepRanges = stepRanges.ToArray(); return retVal; } return null; } /// /// Determine whether the instrustion pointer can be set to given location /// /// Best possible location. Null is not possible. public SourcecodeSegment CanSetIP(string filename, int line, int column) { return SetIP(true, filename, line, column); } /// /// Set the instrustion pointer to given location /// /// Best possible location. Null is not possible. public SourcecodeSegment SetIP(string filename, int line, int column) { return SetIP(false, filename, line, column); } SourcecodeSegment SetIP(bool simulate, string filename, int line, int column) { process.AssertPaused(); SourcecodeSegment suggestion = new SourcecodeSegment(filename, line, column, column); ICorDebugFunction corFunction; int ilOffset; if (!suggestion.GetFunctionAndOffset(this.Module, false, out corFunction, out ilOffset)) { return null; } else { if (corFunction.Token != methodProps.Token) { return null; } else { try { if (simulate) { CorILFrame.CanSetIP((uint)ilOffset); } else { // invalidates all frames and chains for the current thread CorILFrame.SetIP((uint)ilOffset); process.NotifyPaused(new PauseSession(PausedReason.SetIP)); process.Pause(false); } } catch { return null; } return GetSegmentForOffet((uint)ilOffset); } } } /// Gets value of given name which is accessible from this function /// Null if not found public NamedValue GetValue(string name) { if (name == "this") { return ThisValue; } if (Arguments.Contains(name)) { return Arguments[name]; } if (LocalVariables.Contains(name)) { return LocalVariables[name]; } if (ContaingClassVariables.Contains(name)) { return ContaingClassVariables[name]; } return null; } /// /// Gets all variables in the lexical scope of the function. /// That is, arguments, local variables and varables of the containing class. /// [Debugger.Tests.Ignore] // Accessible though others public NamedValueCollection Variables { get { return new NamedValueCollection(GetVariables()); } } IEnumerable GetFields() { foreach(NamedValue nv in Fields) { yield return nv; } } IEnumerable GetVariables() { if (!IsStatic) { yield return ThisValue; } foreach(NamedValue namedValue in Arguments) { yield return namedValue; } foreach(NamedValue namedValue in LocalVariables) { yield return namedValue; } foreach(NamedValue namedValue in ContaingClassVariables) { yield return namedValue; } } NamedValue thisValueCache; /// /// Gets the instance of the class asociated with the current frame. /// That is, 'this' in C#. /// public NamedValue ThisValue { get { if (IsStatic) throw new DebuggerException("Static method does not have 'this'."); if (thisValueCache == null) { thisValueCache = new NamedValue( "this", process, new IExpirable[] {this}, new IMutable[] {}, delegate { return ThisCorValue; } ); } return thisValueCache; } } ICorDebugValue ThisCorValue { get { if (this.HasExpired) throw new CannotGetValueException("FUNCTION_HAS_EXPIRED"); try { return CorILFrame.GetArgument(0); } catch (COMException e) { // System.Runtime.InteropServices.COMException (0x80131304): An IL variable is not available at the current native IP. (See Forum-8640) if ((uint)e.ErrorCode == 0x80131304) throw new CannotGetValueException("Not available in the current state"); throw; } } } /// /// Gets all accessible members of the class that defines this function. /// public NamedValueCollection ContaingClassVariables { get { // TODO: Should work for static if (!IsStatic) { return ThisValue.GetMembers(); } else { return Fields; } } } private DebugType declaringType; public DebugType DeclaringType { get { if (declaringType != null) return declaringType; declaringType = DebugType.Create(process,ContaingClass); return declaringType; } } /// Gets the name of given parameter /// Zero-based index public string GetParameterName(int index) { // index = 0 is return parameter try { return module.MetaData.GetParamForMethodIndex(methodProps.Token, (uint)index + 1).Name; } catch { return String.Empty; } } /// Total number of arguments (excluding implicit 'this' argument) public int ArgumentCount { get { ICorDebugValueEnum argumentEnum = CorILFrame.EnumerateArguments(); uint argCount = argumentEnum.Count; if (!IsStatic) { argCount--; // Remove 'this' from count } return (int)argCount; } } /// Gets argument with a given index /// Zero-based index public MethodArgument GetArgument(int index) { return new MethodArgument( GetParameterName(index), index, process, new IExpirable[] {this}, new IMutable[] {process.DebugeeState}, delegate { return GetArgumentCorValue(index); } ); } ICorDebugValue GetArgumentCorValue(int index) { if (this.HasExpired) throw new CannotGetValueException("FUNCTION_HAS_EXPIRED"); try { // Non-static functions include 'this' as first argument return CorILFrame.GetArgument((uint)(IsStatic? index : (index + 1))); } catch (COMException e) { if ((uint)e.ErrorCode == 0x80131304) throw new CannotGetValueException("Unavailable in optimized code"); throw; } } NamedValueCollection argumentsCache; /// Gets all arguments of the function. public NamedValueCollection Arguments { get { if (argumentsCache == null) { DateTime startTime = Util.HighPrecisionTimer.Now; argumentsCache = new NamedValueCollection(ArgumentsEnum); TimeSpan totalTime = Util.HighPrecisionTimer.Now - startTime; process.TraceMessage("Loaded Arguments for " + this.ToString() + " (" + totalTime.TotalMilliseconds + " ms)"); } return argumentsCache; } } IEnumerable ArgumentsEnum { get { for (int i = 0; i < ArgumentCount; i++) { yield return GetArgument(i); } } } NamedValueCollection localVariablesCache; NamedValueCollection fieldsCache; public NamedValueCollection Fields { get { if (fieldsCache == null) { fieldsCache = new NamedValueCollection(FieldsEnum); } return fieldsCache; } } /// Gets all local variables of the function. public NamedValueCollection LocalVariables { get { if (localVariablesCache == null) { DateTime startTime = Util.HighPrecisionTimer.Now; localVariablesCache = new NamedValueCollection(LocalVariablesEnum); TimeSpan totalTime = Util.HighPrecisionTimer.Now - startTime; process.TraceMessage("Loaded LocalVariables for " + this.ToString() + " (" + totalTime.TotalMilliseconds + " ms)"); } return localVariablesCache; } } IEnumerable LocalVariablesEnum { get { if (symMethod != null) { // TODO: Is this needed? ISymUnmanagedScope symRootScope = symMethod.RootScope; foreach(LocalVariable var in GetLocalVariablesInScope(symRootScope)) { if (!var.Name.StartsWith("CS$")) { // TODO: Generalize yield return var; } } } } } IEnumerable FieldsEnum { get { if (symMethod != null) { ISymUnmanagedScope symRootScope = symMethod.RootScope; IList flds = DeclaringType.GetFields(BindingFlags.All); foreach (FieldInfo fi in flds) if (!fi.Name.Contains("$") && fi.IsStatic) yield return fi.GetValue(null); } } } IEnumerable GetLocalVariablesInScope(ISymUnmanagedScope symScope) { foreach (ISymUnmanagedVariable symVar in symScope.Locals) { yield return GetLocalVariable(symVar); } foreach(ISymUnmanagedScope childScope in symScope.Children) { foreach(LocalVariable var in GetLocalVariablesInScope(childScope)) { yield return var; } } } LocalVariable GetLocalVariable(ISymUnmanagedVariable symVar) { return new LocalVariable( symVar.Name, process, new IExpirable[] {this}, new IMutable[] {process.DebugeeState}, delegate { return GetCorValueOfLocalVariable(symVar); } ); } ICorDebugValue GetCorValueOfLocalVariable(ISymUnmanagedVariable symVar) { if (this.HasExpired) throw new CannotGetValueException("FUNCTION_HAS_EXPIRED"); try { return CorILFrame.GetLocalVariable((uint)symVar.AddressField1); } catch (COMException e) { if ((uint)e.ErrorCode == 0x80131304) throw new CannotGetValueException("Unavailable in optimized code"); throw; } } } }