diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Eval.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Eval.cs index 7491c34625..9538c79137 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Eval.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Eval.cs @@ -34,6 +34,7 @@ namespace Debugger string description; ICorDebugEval corEval; + Thread thread; Value result; EvalState state; @@ -91,7 +92,7 @@ namespace Debugger this.description = description; this.state = EvalState.Evaluating; - this.corEval = CreateCorEval(appDomain); + this.corEval = CreateCorEval(appDomain, out this.thread); try { evalStarter(this); @@ -116,38 +117,43 @@ namespace Debugger } appDomain.Process.ActiveEvals.Add(this); - appDomain.Process.AsyncContinue(DebuggeeStateAction.Keep); + + if (appDomain.Process.Options.SuspendOtherThreads) { + appDomain.Process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] { thread }); + } else { + appDomain.Process.AsyncContinue(DebuggeeStateAction.Keep, this.Process.UnsuspendedThreads); + } } - static ICorDebugEval CreateCorEval(AppDomain appDomain) + static ICorDebugEval CreateCorEval(AppDomain appDomain, out Thread thread) { appDomain.Process.AssertPaused(); - Thread targetThread = appDomain.Process.SelectedThread; + thread = appDomain.Process.SelectedThread; - if (targetThread.CorThread.GetAppDomain().GetID() != appDomain.ID) { - foreach(Thread thread in appDomain.Process.Threads) { - if (thread.CorThread.GetAppDomain().GetID() == appDomain.ID) { - targetThread = thread; + if (thread.CorThread.GetAppDomain().GetID() != appDomain.ID) { + foreach(Thread t in appDomain.Process.Threads) { + if (t.CorThread.GetAppDomain().GetID() == appDomain.ID && + !t.Suspended && + !t.IsMostRecentStackFrameNative && + t.IsAtSafePoint) + { + thread = t; break; } } } - if (targetThread == null) { + if (thread == null) throw new GetValueException("Can not evaluate because no thread is selected"); - } - if (targetThread.IsMostRecentStackFrameNative) { + if (thread.IsMostRecentStackFrameNative) throw new GetValueException("Can not evaluate because native frame is on top of stack"); - } - if (!targetThread.IsAtSafePoint) { + if (!thread.IsAtSafePoint) throw new GetValueException("Can not evaluate because thread is not at a safe point"); - } - if (targetThread.Suspended) { + if (thread.Suspended) throw new GetValueException("Can not evaluate on suspended thread"); - } - return targetThread.CorThread.CreateEval(); + return thread.CorThread.CreateEval(); } internal bool IsCorEval(ICorDebugEval corEval) @@ -279,7 +285,8 @@ namespace Debugger { if (value == null) { ICorDebugClass corClass = appDomain.ObjectType.CorType.GetClass(); - ICorDebugEval corEval = CreateCorEval(appDomain); + Thread thread; + ICorDebugEval corEval = CreateCorEval(appDomain, out thread); ICorDebugValue corValue = corEval.CreateValue((uint)CorElementType.CLASS, corClass); return new Value(appDomain, corValue); } else if (value is string) { diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebug.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebug.cs index 8ab8eaa420..19f23928c0 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebug.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebug.cs @@ -1527,7 +1527,7 @@ namespace Debugger.Interop.CorDebug [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)] void __GetCount(out uint pcelt); [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)] - void __Next([In] uint celt, [Out] IntPtr threads, out uint pceltFetched); + void __Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray)] ICorDebugThread[] threads, out uint pceltFetched); } [ComImport, Guid("D613F0BB-ACE1-4C19-BD72-E4C08D5DA7F5"), InterfaceType((short) 1)] diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebugExtensionMethods.generated.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebugExtensionMethods.generated.cs index 3d51066c9a..613c074eba 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebugExtensionMethods.generated.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Interop/CorDebugExtensionMethods.generated.cs @@ -3145,7 +3145,7 @@ namespace Debugger.Interop.CorDebug return pcelt; } - public static uint Next(this ICorDebugThreadEnum instance, uint celt, IntPtr threads) + public static uint Next(this ICorDebugThreadEnum instance, uint celt, ICorDebugThread[] threads) { uint pceltFetched; instance.__Next(celt, threads, out pceltFetched); diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/ManagedCallback.cs b/src/AddIns/Misc/Debugger/Debugger.Core/ManagedCallback.cs index 1b20b9d58e..5ee77b107b 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/ManagedCallback.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/ManagedCallback.cs @@ -53,7 +53,7 @@ namespace Debugger if (process.IsPaused && process.PauseSession.PausedReason == PausedReason.ForcedBreak) { process.TraceMessage("Processing post-break callback"); // This compensates for the break call and we are in normal callback handling mode - process.AsyncContinue(DebuggeeStateAction.Keep); + process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] {}); // Start of call back - create new pause session (as usual) process.NotifyPaused(pausedReason); // Make sure we stay pause after the callback is handled @@ -83,19 +83,22 @@ namespace Debugger void ExitCallback() { bool hasQueuedCallbacks = process.CorProcess.HasQueuedCallbacks(); - if (hasQueuedCallbacks) { + if (hasQueuedCallbacks) process.TraceMessage("Process has queued callbacks"); - } - // Ignore events during property evaluation - if (!pauseOnNextExit || process.Evaluating || hasQueuedCallbacks) { - process.AsyncContinue(DebuggeeStateAction.Keep); - } else { - if (process.Options.Verbose) { + if (hasQueuedCallbacks) { + // Exception has Exception2 queued after it + process.AsyncContinue(DebuggeeStateAction.Keep, null); + } else if (process.Evaluating) { + // Ignore events during property evaluation + process.AsyncContinue(DebuggeeStateAction.Keep, null); + } else if (pauseOnNextExit) { + if (process.Options.Verbose) process.TraceMessage("Callback exit: Paused"); - } pauseOnNextExit = false; Pause(); + } else { + process.AsyncContinue(DebuggeeStateAction.Keep, null); } isInCallback = false; diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Options.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Options.cs index 8150a63cb2..28855bd793 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Options.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Options.cs @@ -17,5 +17,6 @@ namespace Debugger public bool StepOverFieldAccessProperties = true; public bool Verbose = false; public string[] SymbolsSearchPaths = new string[0]; + public bool SuspendOtherThreads = false; } } diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Process.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Process.cs index 563eae1cf6..0ea378c4ea 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Process.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Process.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using Debugger.Interop.CorDebug; using ICSharpCode.NRefactory.Ast; using ICSharpCode.NRefactory.Visitors; +using System.Runtime.InteropServices; namespace Debugger { @@ -246,8 +247,8 @@ namespace Debugger protected virtual void OnResumed() { AssertRunning(); - // No real purpose - just additional check - if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback."); + if (callbackInterface.IsInCallback) + throw new DebuggerException("Can not raise event within callback."); TraceMessage ("Debugger event: OnResumed()"); if (Resumed != null) { Resumed(this, new ProcessEventArgs(this)); @@ -399,15 +400,53 @@ namespace Debugger WaitForPause(); } + internal Thread[] UnsuspendedThreads { + get { + List threadsToRun = new List(this.Threads.Count); + foreach(Thread t in this.Threads) { + if (!t.Suspended) + threadsToRun.Add(t); + } + return threadsToRun.ToArray(); + } + } + + /// + /// Resume execution and run all threads not marked by the user as susspended. + /// public void AsyncContinue() { - AsyncContinue(DebuggeeStateAction.Clear); + AsyncContinue(DebuggeeStateAction.Clear, this.UnsuspendedThreads); } - internal void AsyncContinue(DebuggeeStateAction action) + internal void AsyncContinue(DebuggeeStateAction action, Thread[] threadsToRun) { AssertPaused(); + if (threadsToRun != null) { +// corProcess.SetAllThreadsDebugState(CorDebugThreadState.THREAD_SUSPEND, null); +// TODO: There is second unreported thread, stopping it prevents the debugee from exiting +// uint count = corProcess.EnumerateThreads().GetCount(); +// ICorDebugThread[] ts = new ICorDebugThread[count]; +// corProcess.EnumerateThreads().Next((uint)ts.Length, ts); +// uint helper = corProcess.GetHelperThreadID(); + try { + foreach(Thread t in this.Threads) { + t.CorThread.SetDebugState(CorDebugThreadState.THREAD_SUSPEND); + } + foreach(Thread t in threadsToRun) { + t.CorThread.SetDebugState(CorDebugThreadState.THREAD_RUN); + } + } catch (COMException e) { + // The state of the thread is invalid. (Exception from HRESULT: 0x8013132D) + // It can happen for threads that have not started yet + if ((uint)e.ErrorCode == 0x8013132D) { + } else { + throw; + } + } + } + NotifyResumed(action); corProcess.Continue(0); if (this.Options.Verbose) { diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/StackFrame.cs b/src/AddIns/Misc/Debugger/Debugger.Core/StackFrame.cs index f26cca5def..db349d8a2e 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/StackFrame.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/StackFrame.cs @@ -185,7 +185,7 @@ namespace Debugger { Stepper.StepOut(this, "normal"); - process.AsyncContinue(DebuggeeStateAction.Clear); + AsyncContinue(); } void AsyncStep(bool stepIn) @@ -213,7 +213,16 @@ namespace Debugger Stepper.StepOver(this, nextSt.StepRanges, "normal"); } - process.AsyncContinue(DebuggeeStateAction.Clear); + AsyncContinue(); + } + + void AsyncContinue() + { + if (process.Options.SuspendOtherThreads) { + process.AsyncContinue(DebuggeeStateAction.Clear, new Thread[] { this.Thread }); + } else { + process.AsyncContinue(DebuggeeStateAction.Clear, this.Process.UnsuspendedThreads); + } } /// diff --git a/src/AddIns/Misc/Debugger/Debugger.Core/Thread.cs b/src/AddIns/Misc/Debugger/Debugger.Core/Thread.cs index 9018504f09..613c3f4639 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Core/Thread.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Core/Thread.cs @@ -123,19 +123,7 @@ namespace Debugger } } - /// Returns false if the thread is in invalid state - public bool Suspended { - get { - process.AssertPaused(); - - if (!IsInValidState) return false; - - return (CorThread.GetDebugState() == CorDebugThreadState.THREAD_SUSPEND); - } - set { - CorThread.SetDebugState((value==true) ? CorDebugThreadState.THREAD_SUSPEND : CorDebugThreadState.THREAD_RUN); - } - } + public bool Suspended { get; set; } /// Returns Normal if the thread is in invalid state public ThreadPriority Priority { @@ -239,8 +227,7 @@ namespace Debugger // May happen in release code with does not have any symbols return false; } - - process.AsyncContinue(DebuggeeStateAction.Keep); + process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] { this /* needed */ }); process.WaitForPause(); return true; } diff --git a/src/AddIns/Misc/Debugger/Debugger.Tests/Tests/ExpressionEvaluator_Tests.cs b/src/AddIns/Misc/Debugger/Debugger.Tests/Tests/ExpressionEvaluator_Tests.cs index 31b9091c62..e15b2fd01f 100644 --- a/src/AddIns/Misc/Debugger/Debugger.Tests/Tests/ExpressionEvaluator_Tests.cs +++ b/src/AddIns/Misc/Debugger/Debugger.Tests/Tests/ExpressionEvaluator_Tests.cs @@ -116,6 +116,8 @@ namespace Debugger.Tests { new ExpressionEvaluator_Tests().Fun("function argument"); } + + static bool WorkerThreadMoved = false; public unsafe void Fun(string arg) { @@ -136,7 +138,13 @@ namespace Debugger.Tests int*[][,] complexType1 = new int*[][,] { new int*[,] { { (int*)0xDA1D } } }; A.B.C[][,] complexType2 = new A.B.C[0][,]; + System.Threading.Thread bgWork = new System.Threading.Thread( + delegate() { WorkerThreadMoved = true; } + ); + System.Diagnostics.Debugger.Break(); + bgWork.Start(); + System.Threading.Thread.Sleep(100); } } } @@ -192,6 +200,8 @@ namespace Debugger.Tests { public void ExpressionEvaluator_Tests() { StartTest(); + process.SelectedStackFrame.StepOver(); + process.SelectedStackFrame.StepOver(); // Start worker thread EvalAll(expressionsInput); @@ -248,6 +258,10 @@ namespace Debugger.Tests { ObjectDump("TypesIdentitcal", object.ReferenceEquals(locType, valType)); ObjectDump("TypesEqual", locType == valType); + ObjectDump("WorkerThreadMoved", process.SelectedStackFrame.GetThisValue().GetMemberValue("WorkerThreadMoved").AsString); + process.SelectedStackFrame.StepOver(); + ObjectDump("WorkerThreadMoved", process.SelectedStackFrame.GetThisValue().GetMemberValue("WorkerThreadMoved").AsString); + EndTest(); } @@ -291,7 +305,9 @@ namespace Debugger.Tests { mscorlib.dll (No symbols) ExpressionEvaluator_Tests.exe (Has symbols) - Break ExpressionEvaluator_Tests.cs:139,4-139,40 + Break ExpressionEvaluator_Tests.cs:145,4-145,40 + StepComplete ExpressionEvaluator_Tests.cs:146,4-146,19 + StepComplete ExpressionEvaluator_Tests.cs:147,4-147,39 b = 1 i = 4 @@ -421,6 +437,19 @@ namespace Debugger.Tests { typeof(Debugger.Tests.ExpressionEvaluator_Tests.A<System.Int32>.B.C<System.Char>[][,]) = Debugger.Tests.ExpressionEvaluator_Tests+A`1+B+C`1[System.Int32,System.Char][,][] (ok) True True + + + + StepComplete ExpressionEvaluator_Tests.cs:148,3-148,4 + + +