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.
419 lines
13 KiB
419 lines
13 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.Linq; |
|
using System.Runtime.InteropServices; |
|
using Debugger.Interop.CorSym; |
|
using Debugger.MetaData; |
|
using Debugger.Interop.CorDebug; |
|
using Debugger.Interop.MetaData; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
|
|
namespace Debugger |
|
{ |
|
/// <summary> |
|
/// A stack frame which is being executed on some thread. |
|
/// Use to obtain arguments or local variables. |
|
/// </summary> |
|
public class StackFrame: DebuggerObject |
|
{ |
|
ICorDebugILFrame corILFrame; |
|
long corILFramePauseSession; |
|
|
|
List<LocalVariable> localVariables; |
|
|
|
/// <summary> The process in which this stack frame is executed </summary> |
|
public AppDomain AppDomain { get; private set; } |
|
|
|
public Process Process { get; private set; } |
|
|
|
/// <summary> A thread in which the stack frame is executed </summary> |
|
public Thread Thread { get; private set; } |
|
|
|
/// <summary> Internal index of the stack chain. The value is increasing with age. </summary> |
|
public uint ChainIndex { get; private set; } |
|
|
|
/// <summary> Internal index of the stack frame. The value is increasing with age. </summary> |
|
public uint FrameIndex { get; private set; } |
|
|
|
[Debugger.Tests.Ignore] |
|
public Module Module { get; private set; } |
|
|
|
public IMethod MethodInfo { get; private set; } |
|
|
|
internal ICorDebugFunction CorFunction { get; private set; } |
|
|
|
/// <summary> True if the stack frame has symbols defined. |
|
/// (That is has accesss to the .pdb file) </summary> |
|
public bool HasSymbols { |
|
get { |
|
return GetSegmentForOffset(0) != null; |
|
} |
|
} |
|
|
|
internal ISymUnmanagedMethod SymMethod { |
|
get { |
|
if (this.Module.SymReader == null) { |
|
return null; |
|
} |
|
try { |
|
return this.Module.SymReader.GetMethod(this.CorFunction.GetToken()); |
|
} catch (COMException) { |
|
// Can not find the method |
|
// eg. Compiler generated constructors are not in symbol store |
|
return null; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> Returns true is this incance can not be used any more. </summary> |
|
public bool IsInvalid { |
|
get { |
|
try { |
|
object frame = this.CorILFrame; |
|
return false; |
|
} catch (DebuggerException) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
internal StackFrame(Thread thread, ICorDebugILFrame corILFrame, uint chainIndex, uint frameIndex) |
|
{ |
|
this.Process = thread.Process; |
|
this.Thread = thread; |
|
this.AppDomain = this.Process.GetAppDomain(corILFrame.GetFunction().GetClass().GetModule().GetAssembly().GetAppDomain()); |
|
this.corILFrame = corILFrame; |
|
this.corILFramePauseSession = this.Process.PauseSession; |
|
this.CorFunction = corILFrame.GetFunction(); |
|
this.ChainIndex = chainIndex; |
|
this.FrameIndex = frameIndex; |
|
|
|
// Class parameters are first, then the method ones |
|
List<ICorDebugType> typeArgs = ((ICorDebugILFrame2)corILFrame).EnumerateTypeParameters().ToList(); |
|
|
|
this.Module = thread.Process.GetModule(this.CorFunction.GetModule()); |
|
this.MethodInfo = this.Module.Assembly.Compilation.Import(this.CorFunction, typeArgs); |
|
} |
|
|
|
/// <summary> Returns diagnostic description of the frame </summary> |
|
public override string ToString() |
|
{ |
|
return this.MethodInfo.ToString(); |
|
} |
|
|
|
internal ICorDebugILFrame CorILFrame { |
|
get { |
|
if (corILFramePauseSession != this.Process.PauseSession) { |
|
// Reobtain the stackframe |
|
StackFrame stackFrame = this.Thread.GetStackFrameAt(this.ChainIndex, this.FrameIndex); |
|
if (stackFrame.MethodInfo.UnresolvedMember != this.MethodInfo.UnresolvedMember) |
|
throw new DebuggerException("The stack frame on the thread does not represent the same method anymore"); |
|
corILFrame = stackFrame.corILFrame; |
|
corILFramePauseSession = stackFrame.corILFramePauseSession; |
|
} |
|
return corILFrame; |
|
} |
|
} |
|
|
|
[Debugger.Tests.Ignore] |
|
public int IP { |
|
get { |
|
uint corInstructionPtr; |
|
CorDebugMappingResult mappingResult; |
|
CorILFrame.GetIP(out corInstructionPtr, out mappingResult); |
|
return (int)corInstructionPtr; |
|
} |
|
} |
|
|
|
public int[] ILRanges { get; set; } |
|
|
|
public int SourceCodeLine { get; set; } |
|
|
|
SourcecodeSegment GetSegmentForOffset(int offset) |
|
{ |
|
if (SourceCodeLine != 0) |
|
return SourcecodeSegment.ResolveForIL(this.Module, this.CorFunction, SourceCodeLine, offset, ILRanges); |
|
return SourcecodeSegment.Resolve(this.Module, this.SymMethod, this.CorFunction, offset); |
|
} |
|
|
|
/// <summary> Step into next instruction </summary> |
|
public void StepInto() |
|
{ |
|
AsyncStepInto(); |
|
this.Process.WaitForPause(); |
|
} |
|
|
|
/// <summary> Step over next instruction </summary> |
|
public void StepOver() |
|
{ |
|
AsyncStepOver(); |
|
this.Process.WaitForPause(); |
|
} |
|
|
|
/// <summary> Step out of the stack frame </summary> |
|
public void StepOut() |
|
{ |
|
AsyncStepOut(); |
|
this.Process.WaitForPause(); |
|
} |
|
|
|
/// <summary> Step into next instruction </summary> |
|
public void AsyncStepInto() |
|
{ |
|
AsyncStep(true); |
|
} |
|
|
|
/// <summary> Step over next instruction </summary> |
|
public void AsyncStepOver() |
|
{ |
|
AsyncStep(false); |
|
} |
|
|
|
/// <summary> Step out of the stack frame </summary> |
|
public void AsyncStepOut() |
|
{ |
|
Stepper.StepOut(this, "normal"); |
|
|
|
this.Process.AsyncContinue(DebuggeeStateAction.Clear); |
|
} |
|
|
|
void AsyncStep(bool stepIn) |
|
{ |
|
int[] stepRanges; |
|
if (ILRanges == null) { |
|
SourcecodeSegment nextSt = NextStatement; |
|
if (nextSt == null) { |
|
throw new DebuggerException("Unable to step. Next statement not aviable"); |
|
} |
|
stepRanges = nextSt.StepRanges; |
|
} else { |
|
stepRanges = ILRanges; |
|
} |
|
|
|
if (stepIn) { |
|
Stepper stepInStepper = Stepper.StepIn(this, stepRanges, "normal"); |
|
this.Thread.CurrentStepIn = stepInStepper; |
|
Stepper clearCurrentStepIn = Stepper.StepOut(this, "clear current step in"); |
|
clearCurrentStepIn.StepComplete += delegate { |
|
if (this.Thread.CurrentStepIn == stepInStepper) { |
|
this.Thread.CurrentStepIn = null; |
|
} |
|
}; |
|
clearCurrentStepIn.Ignore = true; |
|
} else { |
|
Stepper.StepOver(this, stepRanges, "normal"); |
|
} |
|
|
|
this.Process.AsyncContinue(DebuggeeStateAction.Clear); |
|
} |
|
|
|
/// <summary> |
|
/// Get the information about the next statement to be executed. |
|
/// |
|
/// Returns null on error. |
|
/// </summary> |
|
public SourcecodeSegment NextStatement { |
|
get { |
|
return GetSegmentForOffset(IP); |
|
} |
|
} |
|
|
|
public SourcecodeSegment SetIP(string filename, int line, int column, bool dryRun) |
|
{ |
|
this.Process.AssertPaused(); |
|
|
|
SourcecodeSegment segment = SourcecodeSegment.Resolve(this.Module, filename, line, column); |
|
|
|
if (segment != null && segment.CorFunction.GetToken() == this.MethodInfo.GetMetadataToken()) { |
|
try { |
|
if (dryRun) { |
|
CorILFrame.CanSetIP((uint)segment.ILStart); |
|
} else { |
|
CorILFrame.SetIP((uint)segment.ILStart); |
|
// Invalidates all frames and chains for the current thread |
|
this.Process.NotifyResumed(DebuggeeStateAction.Keep); |
|
this.Process.NotifyPaused(); |
|
} |
|
} catch { |
|
return null; |
|
} |
|
return segment; |
|
} |
|
return null; |
|
} |
|
|
|
/// <summary> Get instance of 'this'. </summary> |
|
/// <param name="followCapture"> Try to find captured 'this' for delegates and enumerators. </param> |
|
[Debugger.Tests.Ignore] |
|
public Value GetThisValue(bool followCapture) |
|
{ |
|
if (followCapture) { |
|
foreach(LocalVariable loc in GetLocalVariables(this.IP)) { |
|
if (loc.IsThis) |
|
return loc.GetValue(this); |
|
} |
|
return null; |
|
} else { |
|
return new Value(this.AppDomain, GetThisCorValue()); |
|
} |
|
} |
|
|
|
ICorDebugValue GetThisCorValue() |
|
{ |
|
if (this.MethodInfo.IsStatic) throw new GetValueException("Static method does not have 'this'."); |
|
ICorDebugValue corValue; |
|
try { |
|
corValue = 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 GetValueException("Not available in the current state"); |
|
throw; |
|
} |
|
// This can be 'by ref' for value types |
|
if (corValue.GetTheType() == (uint)CorElementType.BYREF) { |
|
corValue = ((ICorDebugReferenceValue)corValue).Dereference(); |
|
} |
|
return corValue; |
|
} |
|
|
|
/// <summary> Total number of arguments (excluding implicit 'this' argument) </summary> |
|
public int ArgumentCount { |
|
get { |
|
ICorDebugValueEnum argumentEnum = CorILFrame.EnumerateArguments(); |
|
uint argCount = argumentEnum.GetCount(); |
|
if (!this.MethodInfo.IsStatic) { |
|
argCount--; // Remove 'this' from count |
|
} |
|
return (int)argCount; |
|
} |
|
} |
|
|
|
/// <summary> Gets argument with a given name </summary> |
|
public Value GetArgumentValue(string name) |
|
{ |
|
for (int i = 0; i < this.MethodInfo.Parameters.Count; i++) { |
|
if (this.MethodInfo.Parameters[i].Name == name) { |
|
return GetArgumentValue(i); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/// <summary> Gets argument with a given index </summary> |
|
/// <param name="index"> Zero-based index </param> |
|
public Value GetArgumentValue(int index) |
|
{ |
|
return new Value(this.AppDomain, GetArgumentCorValue(index)); |
|
} |
|
|
|
ICorDebugValue GetArgumentCorValue(int index) |
|
{ |
|
this.Process.AssertPaused(); |
|
|
|
ICorDebugValue corValue; |
|
try { |
|
// Non-static methods include 'this' as first argument |
|
corValue = CorILFrame.GetArgument((uint)(this.MethodInfo.IsStatic? index : (index + 1))); |
|
} catch (COMException e) { |
|
if ((uint)e.ErrorCode == 0x80131304) throw new GetValueException("Unavailable in optimized code"); |
|
throw; |
|
} |
|
// Method arguments can be passed 'by ref' |
|
if (corValue.GetTheType() == (uint)CorElementType.BYREF) { |
|
try { |
|
corValue = ((ICorDebugReferenceValue)corValue).Dereference(); |
|
} catch (COMException e) { |
|
if ((uint)e.ErrorCode == 0x80131305) { |
|
// A reference value was found to be bad during dereferencing. |
|
// This can sometimes happen after a stack overflow |
|
throw new GetValueException("Bad reference"); |
|
} else { |
|
throw; |
|
} |
|
} |
|
} |
|
return corValue; |
|
} |
|
|
|
/// <summary> Get all local variables </summary> |
|
public IEnumerable<LocalVariable> GetLocalVariables() |
|
{ |
|
if (localVariables == null) { |
|
var symMethod = this.SymMethod; |
|
// Note that the user might load symbols later |
|
if (symMethod == null) |
|
return new List<LocalVariable>(); |
|
|
|
localVariables = LocalVariable.GetLocalVariables(this.MethodInfo, symMethod); |
|
} |
|
return localVariables; |
|
} |
|
|
|
/// <summary> Get local variables valid at the given IL offset </summary> |
|
public IEnumerable<LocalVariable> GetLocalVariables(int offset) |
|
{ |
|
return GetLocalVariables().Where(v => v.StartOffset <= offset && offset < v.EndOffset); |
|
} |
|
|
|
/// <summary> Get local variable with given name which is valid at the current IP </summary> |
|
/// <returns> Null if not found </returns> |
|
public Value GetLocalVariableValue(string name) |
|
{ |
|
var loc = GetLocalVariables(this.IP).Where(v => v.Name == name).FirstOrDefault(); |
|
if (loc == null) |
|
throw new GetValueException("Local variable \"{0}\" not found", name); |
|
return loc.GetValue(this); |
|
} |
|
|
|
/// <summary> Gets value indicating whether this method should be stepped over according to current options </summary> |
|
public bool IsNonUserCode { |
|
get { |
|
Options opt = this.Process.Options; |
|
|
|
if (opt.StepOverNoSymbols) { |
|
if (this.SymMethod == null) return true; |
|
} |
|
if (opt.StepOverDebuggerAttributes) { |
|
string[] debuggerAttributes = { |
|
typeof(System.Diagnostics.DebuggerStepThroughAttribute).FullName, |
|
typeof(System.Diagnostics.DebuggerNonUserCodeAttribute).FullName, |
|
typeof(System.Diagnostics.DebuggerHiddenAttribute).FullName |
|
}; |
|
if (this.MethodInfo.Attributes.Any(a => debuggerAttributes.Contains(a.AttributeType.FullName))) return true; |
|
if (this.MethodInfo.DeclaringType.GetDefinition().Attributes.Any(a => debuggerAttributes.Contains(a.AttributeType.FullName))) return true; |
|
} |
|
if (opt.StepOverAllProperties) { |
|
if (this.MethodInfo.IsAccessor) return true; |
|
} |
|
if (opt.StepOverFieldAccessProperties) { |
|
if (this.MethodInfo.IsAccessor && this.Module.GetBackingFieldToken(this.CorFunction) != 0) return true; |
|
} |
|
return false; |
|
} |
|
} |
|
|
|
internal void MarkAsNonUserCode() |
|
{ |
|
((ICorDebugFunction2)this.CorFunction).SetJMCStatus(0 /* false */); |
|
} |
|
|
|
public override bool Equals(object obj) |
|
{ |
|
StackFrame other = obj as StackFrame; |
|
return |
|
other != null && |
|
other.Thread == this.Thread && |
|
other.ChainIndex == this.ChainIndex && |
|
other.FrameIndex == this.FrameIndex && |
|
other.MethodInfo == this.MethodInfo; |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return (int)(this.MethodInfo.GetMetadataToken() ^ this.FrameIndex); |
|
} |
|
} |
|
}
|
|
|