Browse Source

Refactor handling of debuggee exceptions

newNRvisualizers
David Srbecký 13 years ago
parent
commit
7d1735c54f
  1. 26
      src/AddIns/Debugger/Debugger.AddIn/Service/DebuggeeExceptionForm.cs
  2. 164
      src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs
  3. 1
      src/AddIns/Debugger/Debugger.Core/Debugger.Core.csproj
  4. 110
      src/AddIns/Debugger/Debugger.Core/Exception.cs
  5. 23
      src/AddIns/Debugger/Debugger.Core/ManagedCallback.cs
  6. 11
      src/AddIns/Debugger/Debugger.Core/NDebugger.cs
  7. 2
      src/AddIns/Debugger/Debugger.Core/Process.cs
  8. 28
      src/AddIns/Debugger/Debugger.Core/Thread.cs
  9. 14
      src/AddIns/Debugger/Debugger.Tests/DebuggerTestsBase.cs
  10. 2
      src/AddIns/Debugger/Debugger.Tests/Tests/Exception_Custom.cs

26
src/AddIns/Debugger/Debugger.AddIn/Service/DebuggeeExceptionForm.cs

@ -15,13 +15,15 @@ namespace ICSharpCode.SharpDevelop.Services @@ -15,13 +15,15 @@ namespace ICSharpCode.SharpDevelop.Services
internal sealed partial class DebuggeeExceptionForm
{
Process process;
bool isUnhandled;
public Debugger.Exception Exception { get; private set; }
public bool Break { get; set; }
DebuggeeExceptionForm(Process process)
{
InitializeComponent();
this.Break = true;
this.process = process;
this.process.Exited += ProcessHandler;
@ -58,18 +60,20 @@ namespace ICSharpCode.SharpDevelop.Services @@ -58,18 +60,20 @@ namespace ICSharpCode.SharpDevelop.Services
this.process.Resumed -= ProcessHandler;
}
public static void Show(Process process, string title, string message, string stacktrace, Bitmap icon, bool isUnhandled, Debugger.Exception exception)
public static bool Show(Process process, string title, string type, string stacktrace, Bitmap icon, bool isUnhandled)
{
DebuggeeExceptionForm form = new DebuggeeExceptionForm(process);
form.Text = title;
form.pictureBox.Image = icon;
form.lblExceptionText.Text = message;
form.lblExceptionText.Text = type;
form.exceptionView.Text = stacktrace;
form.isUnhandled = isUnhandled;
form.btnContinue.Enabled = !isUnhandled;
form.Exception = exception;
form.Show(SD.WinForms.MainWin32Window);
// Showing the form as dialg seems like a resonable thing in the presence of potentially multiple
// concurent debugger evetns
form.ShowDialog(SD.WinForms.MainWin32Window);
return form.Break;
}
void ExceptionViewDoubleClick(object sender, EventArgs e)
@ -106,10 +110,8 @@ namespace ICSharpCode.SharpDevelop.Services @@ -106,10 +110,8 @@ namespace ICSharpCode.SharpDevelop.Services
void BtnBreakClick(object sender, EventArgs e)
{
if (Exception.IsUnhandled)
Close();
else if (((WindowsDebugger)DebuggerService.CurrentDebugger).BreakAndInterceptHandledException(Exception))
Close();
this.Break = true;
Close();
}
void BtnStopClick(object sender, EventArgs e)
@ -120,7 +122,7 @@ namespace ICSharpCode.SharpDevelop.Services @@ -120,7 +122,7 @@ namespace ICSharpCode.SharpDevelop.Services
void BtnContinueClick(object sender, EventArgs e)
{
this.process.AsyncContinue();
this.Break = false;
Close();
}
}

164
src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
@ -512,35 +513,106 @@ namespace ICSharpCode.SharpDevelop.Services @@ -512,35 +513,106 @@ namespace ICSharpCode.SharpDevelop.Services
CurrentThread = e.Thread;
CurrentStackFrame = CurrentThread != null ? CurrentThread.MostRecentUserStackFrame : null;
if (e.ExceptionThrown != null) {
HandleException(e);
return;
// We can have several events happening at the same time
bool breakProcess = e.Break;
// Handle thrown exceptions
foreach(Thread exceptionThread in e.ExceptionsThrown) {
JumpToCurrentLine();
Thread evalThread = exceptionThread;
bool isUnhandled = (exceptionThread.CurrentExceptionType == ExceptionType.Unhandled);
Value exception = exceptionThread.CurrentException.GetPermanentReferenceOfHeapValue();
List<Value> innerExceptions = new List<Value>();
for(Value innerException = exception; !innerException.IsNull; innerException = innerException.GetFieldValue("_innerException")) {
innerExceptions.Add(innerException.GetPermanentReferenceOfHeapValue());
}
// Get the exception description
string stacktrace = string.Empty;
for(int i = 0; i < innerExceptions.Count; i++) {
if (i > 0) {
stacktrace += " ---> ";
}
stacktrace += innerExceptions[i].Type.FullName;
Value messageValue = innerExceptions[i].GetFieldValue("_message");
if (!messageValue.IsNull) {
stacktrace += ": " + messageValue.AsString();
}
}
stacktrace += Environment.NewLine + Environment.NewLine;
// Get the stacktrace
string formatSymbols = StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}");
string formatNoSymbols = StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}");
if (isUnhandled) {
// Need to intercept now so that we can evaluate properties
// Intercept may fail (eg StackOverflow)
if (exceptionThread.InterceptException()) {
try {
// Try to evaluate the StackTrace property to get the .NET formated stacktrace
for(int i = innerExceptions.Count - 1; i >= 0; i--) {
Value stackTraceValue = innerExceptions[i].GetPropertyValue(evalThread, "StackTrace");
if (!stackTraceValue.IsNull) {
stacktrace += stackTraceValue.AsString() + Environment.NewLine;
}
if (i > 0) {
stacktrace += " " + StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}") + Environment.NewLine;
}
}
} catch (GetValueException) {
stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols);
}
} else {
stacktrace += StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}") + Environment.NewLine + Environment.NewLine;
stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols);
}
} else {
// Do not intercept handled expetions
stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols);
}
string title = isUnhandled ? StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Unhandled}") : StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Handled}");
string type = string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Message}"), exception.Type);
Bitmap icon = WinFormsResourceService.GetBitmap(isUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning");
if (DebuggeeExceptionForm.Show(e.Process, title, type, stacktrace, icon, isUnhandled)) {
breakProcess = true;
// The dialog box is allowed to kill the process
if (e.Process.HasExited) {
return;
}
// Intercept handled exception *after* the user decided to break
if (!isUnhandled) {
if (!exceptionThread.InterceptException()) {
MessageService.ShowError("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptHandledException}");
}
}
}
}
bool breakpointHit = false;
// Handle breakpoints
foreach (Breakpoint breakpoint in e.BreakpointsHit) {
var bookmark = SD.BookmarkManager.Bookmarks.OfType<BreakpointBookmark>().First(bm => bm.InternalBreakpointObject == breakpoint);
if (string.IsNullOrEmpty(bookmark.Condition)) {
breakpointHit = true;
breakProcess = true;
} else {
if (EvaluateCondition(bookmark.Condition)) {
breakpointHit = true;
breakProcess = true;
DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAtBecause}") + "\n", bookmark.LineNumber, bookmark.FileName, bookmark.Condition));
}
}
}
// We can have several, potentially conditional, breakpoints at the same time
// ... as well as stepper happening on the same line
if (e.Break || breakpointHit) {
LoggingService.Info("Jump to current line");
if (breakProcess) {
JumpToCurrentLine();
RefreshPads();
} else {
e.Process.AsyncContinue();
}
RefreshPads();
}
void debuggedProcess_DebuggingResumed(object sender, DebuggerEventArgs e)
@ -554,53 +626,6 @@ namespace ICSharpCode.SharpDevelop.Services @@ -554,53 +626,6 @@ namespace ICSharpCode.SharpDevelop.Services
RefreshPads();
}
void HandleException(DebuggerPausedEventArgs e)
{
JumpToCurrentLine();
StringBuilder stacktraceBuilder = new StringBuilder();
if (e.ExceptionThrown.IsUnhandled) {
// Need to intercept now so that we can evaluate properties
if (e.Thread.InterceptException(e.ExceptionThrown)) {
stacktraceBuilder.AppendLine(e.ExceptionThrown.ToString());
string stackTrace;
try {
stackTrace = e.ExceptionThrown.GetStackTrace(e.Thread, StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}"));
} catch (GetValueException) {
stackTrace = e.Thread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"));
}
stacktraceBuilder.Append(stackTrace);
} else {
// For example, happens on stack overflow
stacktraceBuilder.AppendLine(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}"));
stacktraceBuilder.AppendLine(e.ExceptionThrown.ToString());
stacktraceBuilder.Append(e.Thread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}")));
}
} else {
stacktraceBuilder.AppendLine(e.ExceptionThrown.ToString());
stacktraceBuilder.Append(e.Thread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}")));
}
string title = e.ExceptionThrown.IsUnhandled ? StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Unhandled}") : StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Handled}");
string message = string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Message}"), e.ExceptionThrown.Type);
Bitmap icon = WinFormsResourceService.GetBitmap(e.ExceptionThrown.IsUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning");
DebuggeeExceptionForm.Show(e.Process, title, message, stacktraceBuilder.ToString(), icon, e.ExceptionThrown.IsUnhandled, e.ExceptionThrown);
RefreshPads();
}
public bool BreakAndInterceptHandledException(Debugger.Exception exception)
{
if (!CurrentThread.InterceptException(exception)) {
MessageService.ShowError("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptHandledException}");
return false;
}
JumpToCurrentLine();
return true;
}
public static Value Evaluate(string code)
{
if (CurrentStackFrame == null || CurrentStackFrame.NextStatement == null)
@ -620,7 +645,14 @@ namespace ICSharpCode.SharpDevelop.Services @@ -620,7 +645,14 @@ namespace ICSharpCode.SharpDevelop.Services
// if (debuggedProcess.IsSelectedFrameForced()) {
if (CurrentThread != null && CurrentStackFrame.HasSymbols) {
JumpToSourceCode();
if (CurrentProcess == null || CurrentStackFrame == null)
return;
SourcecodeSegment nextStatement = CurrentStackFrame.NextStatement;
if (nextStatement != null) {
DebuggerService.RemoveCurrentLineMarker();
DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn);
}
} else {
#warning JumpToDecompiledCode(CurrentStackFrame);
}
@ -635,18 +667,6 @@ namespace ICSharpCode.SharpDevelop.Services @@ -635,18 +667,6 @@ namespace ICSharpCode.SharpDevelop.Services
// }
// }
}
void JumpToSourceCode()
{
if (CurrentProcess == null || CurrentStackFrame == null)
return;
SourcecodeSegment nextStatement = CurrentStackFrame.NextStatement;
if (nextStatement != null) {
DebuggerService.RemoveCurrentLineMarker();
DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn);
}
}
/*
void JumpToDecompiledCode(Debugger.StackFrame frame)

1
src/AddIns/Debugger/Debugger.Core/Debugger.Core.csproj

@ -69,7 +69,6 @@ @@ -69,7 +69,6 @@
<Compile Include="Breakpoint.cs" />
<Compile Include="DebuggerException.cs" />
<Compile Include="Eval.cs" />
<Compile Include="Exception.cs" />
<Compile Include="Interop\Common.cs" />
<Compile Include="Interop\CorDebug.cs" />
<Compile Include="Interop\CorDebugExtensionMethods.cs" />

110
src/AddIns/Debugger/Debugger.Core/Exception.cs

@ -1,110 +0,0 @@ @@ -1,110 +0,0 @@
// 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.Text;
namespace Debugger
{
enum ExceptionType
{
FirstChance = 1,
UserFirstChance = 2,
CatchHandlerFound = 3,
Unhandled = 4,
}
/// <summary> This convenience class provides access to an exception within the debugee. </summary>
/// <seealso cref="System.Exception" />
public class Exception: DebuggerObject
{
Value exception;
public Value Value {
get { return exception; }
}
ExceptionType ExceptionType { get; set; }
public bool IsUnhandled {
get { return this.ExceptionType == ExceptionType.Unhandled; }
}
internal Exception(Value exception, ExceptionType exceptionType)
{
this.exception = exception;
this.ExceptionType = exceptionType;
}
/// <summary> The <c>GetType().FullName</c> of the exception. </summary>
/// <seealso cref="System.Exception" />
public string Type {
get {
return exception.Type.FullName;
}
}
/// <summary> The <c>Message</c> property of the exception. </summary>
/// <seealso cref="System.Exception" />
public string Message {
get {
Value message = exception.GetFieldValue("_message");
return message.IsNull ? string.Empty : message.AsString();
}
}
/// <summary> The <c>InnerException</c> property of the exception. </summary>
/// <seealso cref="System.Exception" />
public Exception InnerException {
get {
Value innerException = exception.GetFieldValue("_innerException");
return innerException.IsNull ? null : new Exception(innerException, this.ExceptionType);
}
}
public void MakeValuePermanent()
{
exception = exception.GetPermanentReferenceOfHeapValue();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(this.Type);
if (!string.IsNullOrEmpty(this.Message)) {
sb.Append(": ");
sb.Append(this.Message);
}
if (this.InnerException != null) {
sb.Append(" ---> ");
sb.Append(this.InnerException.ToString());
}
return sb.ToString();
}
public string GetStackTrace(Thread evalThread)
{
return GetStackTrace(evalThread, "--- End of inner exception stack trace ---");
}
/// <summary> Returs formated stacktrace for the exception </summary>
/// <exception cref="GetValueException"> Getting the stacktrace involves property
/// evaluation so GetValueException can be thrown in some cicumstances. </exception>
public string GetStackTrace(Thread evalThread, string endOfInnerExceptionFormat)
{
StringBuilder sb = new StringBuilder();
if (this.InnerException != null) {
sb.Append(this.InnerException.GetStackTrace(evalThread, endOfInnerExceptionFormat));
sb.Append(" ");
sb.Append(endOfInnerExceptionFormat);
sb.AppendLine();
}
// Note that evaluation is not possible after a stackoverflow exception
Value stackTrace = exception.GetPropertyValue(evalThread, "StackTrace");
if (!stackTrace.IsNull) {
sb.Append(stackTrace.AsString());
sb.AppendLine();
}
return sb.ToString();
}
}
}

23
src/AddIns/Debugger/Debugger.Core/ManagedCallback.cs

@ -48,12 +48,7 @@ namespace Debugger @@ -48,12 +48,7 @@ namespace Debugger
// The event will be raised as soon as the callback queue is drained.
DebuggerPausedEventArgs GetPausedEventArgs()
{
if (pausedEventArgs == null) {
pausedEventArgs = new DebuggerPausedEventArgs();
pausedEventArgs.Process = process;
pausedEventArgs.BreakpointsHit = new List<Breakpoint>();
}
return pausedEventArgs;
return pausedEventArgs ?? (pausedEventArgs = new DebuggerPausedEventArgs(process));
}
void EnterCallback(string name, ICorDebugProcess pProcess)
@ -67,8 +62,9 @@ namespace Debugger @@ -67,8 +62,9 @@ namespace Debugger
if (process.IsPaused) {
process.TraceMessage("Processing post-break callback");
// Decrese the "break count" from 2 to 1 - does not actually continue
// TODO: This inccorectly marks the debugger as running
process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] {}, null);
// Make sure we stay pauses after the callback is handled
// Make sure we stay paused after the callback is handled
pauseOnNextExit = true;
return;
}
@ -98,9 +94,7 @@ namespace Debugger @@ -98,9 +94,7 @@ namespace Debugger
if (hasQueuedCallbacks)
process.TraceMessage("Process has queued callbacks");
// TODO: Can we drain the queue?
if (hasQueuedCallbacks && (pausedEventArgs == null || pausedEventArgs.ExceptionThrown == null)) {
// Process queued callbacks if no exception occurred
if (hasQueuedCallbacks) {
process.AsyncContinue(DebuggeeStateAction.Keep, null, null);
} else if (process.Evaluating) {
// Ignore events during property evaluation
@ -534,11 +528,12 @@ namespace Debugger @@ -534,11 +528,12 @@ namespace Debugger
bool pauseOnHandled = !process.Evaluating && process.Options != null && process.Options.PauseOnHandledExceptions;
if (exceptionType == ExceptionType.Unhandled || (pauseOnHandled && exceptionType == ExceptionType.CatchHandlerFound)) {
Value value = new Value(process.GetAppDomain(pAppDomain), pThread.GetCurrentException()).GetPermanentReferenceOfHeapValue();
if (GetPausedEventArgs().ExceptionThrown != null)
throw new DebuggerException("Exception is already being processed");
GetPausedEventArgs().ExceptionThrown = new Exception(value, exceptionType);
// Multiple exceptions can happen at the same time on multiple threads
// (I have managed to create a test application to trigger it)
Thread thread = process.GetThread(pThread);
thread.CurrentExceptionType = exceptionType;
GetPausedEventArgs().ExceptionsThrown.Add(thread);
pauseOnNextExit = true;
}

11
src/AddIns/Debugger/Debugger.Core/NDebugger.cs

@ -320,11 +320,18 @@ namespace Debugger @@ -320,11 +320,18 @@ namespace Debugger
/// <summary> Breakpoints hit </summary>
public List<Breakpoint> BreakpointsHit { get; set; }
/// <summary> Exception thrown </summary>
public Exception ExceptionThrown { get; set; }
/// <summary> Threads which have exceptions </summary>
public List<Thread> ExceptionsThrown { get; set; }
/// <summary> Break, stepper or any other pause reason. </summary>
public bool Break { get; set; }
public DebuggerPausedEventArgs(Process process)
{
this.Process = process;
this.BreakpointsHit = new List<Breakpoint>();
this.ExceptionsThrown = new List<Thread>();
}
}
[Serializable]

2
src/AddIns/Debugger/Debugger.Core/Process.cs

@ -402,7 +402,7 @@ namespace Debugger @@ -402,7 +402,7 @@ namespace Debugger
corProcess.Stop(uint.MaxValue); // Infinite; ignored anyway
NotifyPaused();
OnPaused(new DebuggerPausedEventArgs() { Process = this });
OnPaused(new DebuggerPausedEventArgs(this) { Break = true });
}
public void Detach()

28
src/AddIns/Debugger/Debugger.Core/Thread.cs

@ -11,6 +11,14 @@ using Debugger.Interop.CorDebug; @@ -11,6 +11,14 @@ using Debugger.Interop.CorDebug;
namespace Debugger
{
public enum ExceptionType
{
FirstChance = 1,
UserFirstChance = 2,
CatchHandlerFound = 3,
Unhandled = 4,
}
public class Thread: DebuggerObject
{
// AppDomain for thread can be changing
@ -45,6 +53,16 @@ namespace Debugger @@ -45,6 +53,16 @@ namespace Debugger
set { currentStepIn = value; }
}
[Debugger.Tests.Ignore]
public ExceptionType CurrentExceptionType { get; set; }
[Debugger.Tests.Ignore]
public Value CurrentException {
get {
return new Value(this.AppDomain, this.CorThread.GetCurrentException());
}
}
/// <summary> From time to time the thread may be in invalid state. </summary>
public bool IsInValidState {
get {
@ -143,11 +161,9 @@ namespace Debugger @@ -143,11 +161,9 @@ namespace Debugger
}
}
/// <summary> Tryies to intercept the current exception.
/// The intercepted expression stays available through the CurrentException property. </summary>
/// <returns> False, if the exception was already intercepted or
/// if it can not be intercepted. </returns>
public bool InterceptException(Exception exception)
/// <summary> Tryies to intercept the current exception. </summary>
/// <returns> False, if the exception can not be intercepted. </returns>
public bool InterceptException()
{
if (!(this.CorThread is ICorDebugThread2)) return false; // Is the debuggee .NET 2.0?
if (this.CorThread.GetCurrentException() == null) return false; // Is there any exception
@ -166,8 +182,6 @@ namespace Debugger @@ -166,8 +182,6 @@ namespace Debugger
if (mostRecentUnoptimized == null) return false;
try {
// Interception will expire the CorValue so keep permanent reference
exception.MakeValuePermanent();
((ICorDebugThread2)this.CorThread).InterceptCurrentException(mostRecentUnoptimized.CorILFrame);
} catch (COMException e) {
// 0x80131C02: Cannot intercept this exception

14
src/AddIns/Debugger/Debugger.Tests/DebuggerTestsBase.cs

@ -218,16 +218,12 @@ namespace Debugger.Tests @@ -218,16 +218,12 @@ namespace Debugger.Tests
} else {
this.CurrentStackFrame = null;
}
if (e.ExceptionThrown != null) {
StringBuilder msg = new StringBuilder();
if (CurrentThread.InterceptException(e.ExceptionThrown)) {
msg.Append(e.ExceptionThrown.ToString());
} else {
// For example, happens on stack overflow
msg.Append("Could not intercept: ");
msg.Append(e.ExceptionThrown.ToString());
foreach(Thread exceptionThread in e.ExceptionsThrown) {
Value exception = exceptionThread.CurrentException;
LogEvent("ExceptionThrown", exception.Type.FullName);
if (!exceptionThread.InterceptException()) {
LogEvent("CanNotInterceptException", exception.Type.FullName);
}
LogEvent("ExceptionThrown", msg.ToString());
}
LogEvent("Paused", CurrentStackFrame != null && CurrentStackFrame.NextStatement != null ? CurrentStackFrame.NextStatement.ToString() : string.Empty);
};

2
src/AddIns/Debugger/Debugger.Tests/Tests/Exception_Custom.cs

@ -49,7 +49,7 @@ namespace Debugger.Tests { @@ -49,7 +49,7 @@ namespace Debugger.Tests {
<Started />
<ModuleLoaded>mscorlib.dll (No symbols)</ModuleLoaded>
<ModuleLoaded>Exception_Custom.exe (Has symbols)</ModuleLoaded>
<ExceptionThrown>Debugger.Tests.MyException: test2 ---&gt; Debugger.Tests.MyException: test1</ExceptionThrown>
<ExceptionThrown>Debugger.Tests.MyException</ExceptionThrown>
<Paused>Exception_Custom.cs:23,5-23,39</Paused>
<Exited />
</Test>

Loading…
Cancel
Save