diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Debugger.Core.csproj b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Debugger.Core.csproj index 2251cf2463..4a5e50d26d 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Debugger.Core.csproj +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Debugger.Core.csproj @@ -377,6 +377,9 @@ + + + @@ -387,6 +390,8 @@ + + - + \ No newline at end of file diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallback.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallback.cs index 03ed5499c1..f83d288c23 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallback.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Debugger/Internal/ManagedCallback.cs @@ -122,10 +122,17 @@ namespace Debugger if (debugger.SelectedThread.LastFunction.HasSymbols) { ExitCallback_Paused(); } else { - // This should not happen with JMC enabled - debugger.TraceMessage(" - leaving code without symbols"); - - ExitCallback_Continue(); + // This can only happen when JMC is disabled (ie NET1.1 or StepOut) + if (stepper.Operation == Stepper.StepperOperation.StepOut) { + // Create new stepper and keep going + debugger.TraceMessage(" - stepping out of code without symbols at " + debugger.SelectedThread.LastFunction.ToString()); + new Stepper(debugger.SelectedThread.LastFunction, "Stepper out of code without symbols").StepOut(); + ExitCallback_Continue(); + } else { + // NET1.1: There is extra step over stepper, just keep going + debugger.TraceMessage(" - leaving code without symbols"); + ExitCallback_Continue(); + } } } else { ExitCallback_Continue(); diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/FrameID.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/FrameID.cs new file mode 100644 index 0000000000..ebb0f69223 --- /dev/null +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/FrameID.cs @@ -0,0 +1,59 @@ +// +// +// +// +// $Revision: 1534 $ +// + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +using Debugger.Wrappers.CorDebug; + +namespace Debugger +{ + /// + /// Identifies frame on thread callstack + /// + struct FrameID { + uint chainIndex; + uint frameIndex; + + public uint ChainIndex { + get { + return chainIndex; + } + } + + public uint FrameIndex { + get { + return frameIndex; + } + } + + public FrameID(uint chainIndex, uint frameIndex) + { + this.chainIndex = chainIndex; + this.frameIndex = frameIndex; + } + + public override int GetHashCode() + { + return chainIndex.GetHashCode() ^ frameIndex.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (!(obj is FrameID)) return false; + FrameID myFrameID = (FrameID)obj; + return this.chainIndex == myFrameID.chainIndex && this.frameIndex == myFrameID.frameIndex; + } + + public override string ToString() + { + return string.Format("{0},{1}", this.chainIndex, this.frameIndex); + } + } +} diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Function.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Function.cs index cf1a18b696..cc77d60622 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Function.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Function.cs @@ -29,8 +29,7 @@ namespace Debugger bool steppedOut = false; Thread thread; - uint chainIndex; - uint frameIndex; + FrameID frameID; MethodProps methodProps; @@ -42,7 +41,7 @@ namespace Debugger public string Name { get { - return methodProps.Name; // + "(" + chainIndex.ToString() + ", " + frameIndex.ToString() + ")"; + return methodProps.Name; } } @@ -100,6 +99,7 @@ namespace Debugger { if (!steppedOut) { steppedOut = true; + debugger.TraceMessage("Function " + this.ToString() + " expired"); if (Expired != null) { Expired(this, e); } @@ -130,37 +130,35 @@ namespace Debugger } } - internal Function(Thread thread, uint chainIndex, uint frameIndex, ICorDebugILFrame corILFrame) + internal Function(Thread thread, FrameID frameID, ICorDebugILFrame corILFrame) { this.debugger = thread.Debugger; this.thread = thread; - this.chainIndex = chainIndex; - this.frameIndex = frameIndex; + this.frameID = frameID; this.CorILFrame = corILFrame; corFunction = corILFrame.Function; module = debugger.GetModule(corFunction.Module); methodProps = module.MetaData.GetMethodProps(corFunction.Token); - AddTrackingStepper(); - } - - internal void AddTrackingStepper() - { - // Expiry the function when it is finished + // Force some callback when function steps out so that we can expire it stepOutStepper = new Stepper(this, "Function Tracker"); stepOutStepper.StepOut(); stepOutStepper.PauseWhenComplete = false; - stepOutStepper.StepComplete += delegate { - OnExpired(EventArgs.Empty); - }; + + debugger.TraceMessage("Function " + this.ToString() + " created"); + } + + public override string ToString() + { + return methodProps.Name + "(" + frameID.ToString() + ")"; } internal ICorDebugILFrame CorILFrame { get { if (HasExpired) throw new DebuggerException("Function has expired"); if (corILFramePauseSession != debugger.PauseSession) { - CorILFrame = thread.GetFrameAt(chainIndex, frameIndex).As(); + CorILFrame = thread.GetFrameAt(frameID).As(); } return corILFrame; } @@ -213,8 +211,7 @@ namespace Debugger public void StepOut() { - Stepper stepper = new Stepper(this); - stepper.StepOut(); + new Stepper(this, "Function step out").StepOut(); debugger.Continue(); } @@ -231,19 +228,15 @@ namespace Debugger throw new DebuggerException("Unable to step. Next statement not aviable"); } - Stepper stepper; - if (stepIn) { - stepper = new Stepper(this); - stepper.StepIn(nextSt.StepRanges); + 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); } - // Without JMC step in which ends in code without symblols is cotinued. - // The next step over ensures that we at least do step over. - - stepper = new Stepper(this); - stepper.StepOver(nextSt.StepRanges); - debugger.Continue(); } diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Stepper.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Stepper.cs index 632af702ff..3087d78f2c 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Stepper.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Stepper.cs @@ -56,6 +56,15 @@ namespace Debugger } } + public bool JustMyCode { + set { + if (corStepper.Is()) { // Is the debuggee .NET 2.0? + corStepper.SetUnmappedStopMask(CorDebugUnmappedStop.STOP_NONE); + corStepper.CastTo().SetJMC(value ? 1 : 0); + } + } + } + public Stepper(Function function, string name): this(function) { this.name = name; @@ -67,11 +76,7 @@ namespace Debugger corStepper = function.CorILFrame.CreateStepper(); - // Turn on Just-My-Code - if (corStepper.Is()) { // Is the debuggee .NET 2.0? - corStepper.SetUnmappedStopMask(CorDebugUnmappedStop.STOP_NONE); - corStepper.CastTo().SetJMC(1 /* true */); - } + JustMyCode = true; function.Thread.Steppers.Add(this); } @@ -87,15 +92,11 @@ namespace Debugger return this.corStepper == corStepper; } - // NOTE: corStepper.StepOut(); finishes when pevious frame is activated, not when function is exited - // this is important for events with multiple handlers - // NOTE: StepRange callbacks go first (probably in order), - // StepOut callback are called after that public void StepOut() { operation = StepperOperation.StepOut; - // corStepper.StepOut(); // Don't! see note - corStepper.StepRange(false, new int[] {0, int.MaxValue}); + JustMyCode = false; // Needed for multiple events. See docs\Stepping.txt + corStepper.StepOut(); } public void StepIn(int[] ranges) @@ -112,7 +113,7 @@ namespace Debugger public override string ToString() { - return string.Format("{0} in {1} pause={2} {3}", Operation, Function.Name, PauseWhenComplete, name); + return string.Format("{0} in {1} pause={2} \"{3}\"", Operation, Function.ToString(), PauseWhenComplete, name); } } } diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Thread.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Thread.cs index fab5a8b94d..979c80d8f0 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Thread.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Threads/Thread.cs @@ -179,81 +179,58 @@ namespace Debugger get { process.AssertPaused(); - ICorDebugChainEnum corChainEnum = corThread.EnumerateChains(); - uint chainIndex = corChainEnum.Count; - foreach(ICorDebugChain corChain in corChainEnum.Enumerator) { - chainIndex--; - + foreach(ICorDebugChain corChain in corThread.EnumerateChains().Enumerator) { if (corChain.IsManaged == 0) continue; // Only managed ones - - ICorDebugFrameEnum corFrameEnum = corChain.EnumerateFrames(); - uint frameIndex = corFrameEnum.Count; - foreach(ICorDebugFrame corFrame in corFrameEnum.Enumerator) { - frameIndex--; - + foreach(ICorDebugFrame corFrame in corChain.EnumerateFrames().Enumerator) { if (corFrame.Is()) { - Function function = GetFunctionFromCache(chainIndex, frameIndex, corFrame.As()); - if (function != null) { - yield return function; - } + Function function; + try { + function = GetFunctionFromCache(new FrameID(corChain.Index, corFrame.Index), corFrame.As()); + } catch (COMException) { // TODO + continue; + }; + yield return function; } } } } } - Dictionary chainCache = new Dictionary(); + Dictionary functionCache = new Dictionary(); - class Chain { - public Dictionary Frames = new Dictionary(); - } - - Function GetFunctionFromCache(uint chainIndex, uint frameIndex, ICorDebugILFrame corFrame) + Function GetFunctionFromCache(FrameID frameID, ICorDebugILFrame corFrame) { - try { - if (chainCache.ContainsKey(chainIndex) && - chainCache[chainIndex].Frames.ContainsKey(frameIndex) && - !chainCache[chainIndex].Frames[frameIndex].HasExpired) { - - Function function = chainCache[chainIndex].Frames[frameIndex]; - function.CorILFrame = corFrame; - return function; - } else { - Function function = new Function(this, chainIndex, frameIndex, corFrame.CastTo()); - if (!chainCache.ContainsKey(chainIndex)) chainCache[chainIndex] = new Chain(); - chainCache[chainIndex].Frames[frameIndex] = function; - function.Expired += delegate { chainCache[chainIndex].Frames.Remove(frameIndex); }; - return function; - } - } catch (COMException) { // TODO - return null; - }; + Function function; + if (functionCache.TryGetValue(frameID, out function) && !function.HasExpired) { + function.CorILFrame = corFrame; + return function; + } else { + function = new Function(this, frameID, corFrame); + functionCache[frameID] = function; + return function; + } } - internal ICorDebugFrame GetFrameAt(uint chainIndex, uint frameIndex) + internal ICorDebugFrame GetFrameAt(FrameID frameID) { process.AssertPaused(); ICorDebugChainEnum corChainEnum = corThread.EnumerateChains(); - if (chainIndex >= corChainEnum.Count) throw new ArgumentException("Chain index too big", "chainIndex"); - corChainEnum.Skip(corChainEnum.Count - chainIndex - 1); + if (frameID.ChainIndex >= corChainEnum.Count) throw new ArgumentException("Chain index too big", "chainIndex"); + corChainEnum.Skip(corChainEnum.Count - frameID.ChainIndex - 1); ICorDebugChain corChain = corChainEnum.Next(); if (corChain.IsManaged == 0) throw new ArgumentException("Chain is not managed", "chainIndex"); ICorDebugFrameEnum corFrameEnum = corChain.EnumerateFrames(); - if (frameIndex >= corFrameEnum.Count) throw new ArgumentException("Frame index too big", "frameIndex"); - corFrameEnum.Skip(corFrameEnum.Count - frameIndex - 1); + if (frameID.FrameIndex >= corFrameEnum.Count) throw new ArgumentException("Frame index too big", "frameIndex"); + corFrameEnum.Skip(corFrameEnum.Count - frameID.FrameIndex - 1); return corFrameEnum.Next(); } - // NOTE: During evlulation some chains may be temporaly removed - // NOTE: When two events are invoked, step outs ocurr at once when all is done - // NOTE: Step out works properly for exceptions - // NOTE: Step over works properly for exceptions - // NOTE: Evaluation kills stepper overs on active frame + // See docs\Stepping.txt internal void CheckExpirationOfFunctions() { if (debugger.Evaluating) return; @@ -266,31 +243,29 @@ namespace Debugger ICorDebugFrame lastFrame = corFrameEnum.Next(); - List expiredFunctions = new List(); - - foreach(KeyValuePair chain in chainCache) { - if (chain.Key < maxChainIndex) continue; - foreach(KeyValuePair func in chain.Value.Frames) { - if (chain.Key == maxChainIndex && func.Key <= maxFrameIndex) continue; - expiredFunctions.Add(func.Value); - } - } - - // Check the token of the last function - // TODO: Investigate: this should not happen (test case: event with two handlers) - if (lastFrame != null && - chainCache.ContainsKey(maxChainIndex) && - chainCache[maxChainIndex].Frames.ContainsKey(maxFrameIndex)) { + // Check the token of the current function - function can change if there are multiple handlers for an event + Function function; + if (lastFrame != null && + functionCache.TryGetValue(new FrameID(maxChainIndex, maxFrameIndex), out function) && + function.Token != lastFrame.FunctionToken) { - Function cachedFunction = chainCache[maxChainIndex].Frames[maxFrameIndex]; - if (cachedFunction.Token != lastFrame.FunctionToken) { - expiredFunctions.Add(cachedFunction); + functionCache.Remove(new FrameID(maxChainIndex, maxFrameIndex)); + function.OnExpired(EventArgs.Empty); + } + + // Expire all functions behind the current maximum + // Multiple functions can expire at once (test case: Step out of Button1Click in simple winforms application) + List> toBeRemoved = new List>(); + foreach(KeyValuePair kvp in functionCache) { + if ((kvp.Key.ChainIndex > maxChainIndex) || + (kvp.Key.ChainIndex == maxChainIndex && kvp.Key.FrameIndex > maxFrameIndex)) { + + toBeRemoved.Add(kvp); } } - - foreach(Function f in expiredFunctions) { - debugger.TraceMessage("Function " + f.Name + " expired. (check)"); - f.OnExpired(EventArgs.Empty); + foreach(KeyValuePair kvp in toBeRemoved){ + functionCache.Remove(kvp.Key); + kvp.Value.OnExpired(EventArgs.Empty); } } diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Variables/Evals/Eval.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Variables/Evals/Eval.cs index 1df743b686..a167807c89 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Variables/Evals/Eval.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Variables/Evals/Eval.cs @@ -154,14 +154,6 @@ namespace Debugger OnEvalStarted(new EvalEventArgs(this)); - // Stepper needs to be reset after evaluation - Function lastFunction = targetThread.LastFunction; - if (lastFunction != null) { // TODO: Investigate - EvalComplete += delegate { - lastFunction.AddTrackingStepper(); - }; - } - evalState = EvalState.Evaluating; return true; } diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChain.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChain.cs new file mode 100644 index 0000000000..0e868b69d9 --- /dev/null +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChain.cs @@ -0,0 +1,27 @@ +// +// +// +// +// $Revision$ +// + +namespace Debugger.Wrappers.CorDebug +{ + using System; + using System.Collections.Generic; + + + public partial class ICorDebugChain + { + uint index; + + public uint Index { + get { + return index; + } + set { + index = value; + } + } + } +} diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChainEnum.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChainEnum.cs index e9c25a1b93..7e12243bc5 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChainEnum.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugChainEnum.cs @@ -15,9 +15,12 @@ namespace Debugger.Wrappers.CorDebug { public IEnumerable Enumerator { get { + Reset(); + uint index = this.Count - 1; while (true) { ICorDebugChain corChain = Next(); if (corChain != null) { + corChain.Index = index--; yield return corChain; } else { break; diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrame.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrame.cs new file mode 100644 index 0000000000..6742eb9662 --- /dev/null +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrame.cs @@ -0,0 +1,27 @@ +// +// +// +// +// $Revision$ +// + +namespace Debugger.Wrappers.CorDebug +{ + using System; + using System.Collections.Generic; + + + public partial class ICorDebugFrame + { + uint index; + + public uint Index { + get { + return index; + } + set { + index = value; + } + } + } +} diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrameEnum.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrameEnum.cs index f19e2b5031..f0e797e1a8 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrameEnum.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/Src/Wrappers/CorDebug/ICorDebugFrameEnum.cs @@ -15,9 +15,12 @@ namespace Debugger.Wrappers.CorDebug { public IEnumerable Enumerator { get { + Reset(); + uint index = this.Count - 1; while (true) { ICorDebugFrame corFrame = Next(); if (corFrame != null) { + corFrame.Index = index--; yield return corFrame; } else { break; diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Project/docs/Stepping.txt b/src/AddIns/Misc/Debugger/Debugger.Core/Project/docs/Stepping.txt new file mode 100644 index 0000000000..e83afbac90 --- /dev/null +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Project/docs/Stepping.txt @@ -0,0 +1,7 @@ +== Notes == +- During evaluation some chains may be temporarly removed +- When two events are invoked and JMC is active, step out skips the second function +- Step out and step over works properly for exceptions +- Evaluation kills stepper overs on active frame +- StepRange callbacks go first (probably in order), StepOut callback are called after that +- StepRange is much slower then StepOut