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.
582 lines
19 KiB
582 lines
19 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.Diagnostics; |
|
using System.Runtime.InteropServices; |
|
using Debugger.Interop; |
|
using Debugger.Interop.CorDebug; |
|
|
|
namespace Debugger |
|
{ |
|
/// <summary> |
|
/// Handles all callbacks of a given process |
|
/// </summary> |
|
/// <remarks> |
|
/// Note that there can be a queued callback after almost any callback. |
|
/// In particular: |
|
/// - After 'break' there may be more callbacks |
|
/// - After EvalComplete there may be more callbacks (eg CreateThread from other thread) |
|
/// </remarks> |
|
class ManagedCallback |
|
{ |
|
Process process; |
|
bool pauseOnNextExit; |
|
bool isInCallback = false; |
|
|
|
List<Breakpoint> breakpointsHit = new List<Breakpoint>(); |
|
Exception exceptionThrown; |
|
|
|
[Debugger.Tests.Ignore] |
|
public Process Process { |
|
get { return process; } |
|
} |
|
|
|
public bool IsInCallback { |
|
get { return isInCallback; } |
|
} |
|
|
|
public ManagedCallback(Process process) |
|
{ |
|
this.process = process; |
|
} |
|
|
|
void EnterCallback(PausedReason pausedReason, string name, ICorDebugProcess pProcess) |
|
{ |
|
isInCallback = true; |
|
|
|
process.TraceMessage("Callback: " + name); |
|
System.Diagnostics.Debug.Assert(process.CorProcess == pProcess); |
|
|
|
// After break is pressed we may receive some messages that were already queued |
|
if (process.IsPaused && process.PauseSession.PausedReason == PausedReason.ForcedBreak) { |
|
// TODO: This does not work well if exception if being processed and the user continues it |
|
process.TraceMessage("Processing post-break callback"); |
|
// This compensates for the break call and we are in normal callback handling mode |
|
process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] {}, null); |
|
// Start of call back - create new pause session (as usual) |
|
process.NotifyPaused(pausedReason); |
|
// Make sure we stay pause after the callback is handled |
|
pauseOnNextExit = true; |
|
return; |
|
} |
|
|
|
if (process.IsRunning) { |
|
process.NotifyPaused(pausedReason); |
|
return; |
|
} |
|
|
|
throw new DebuggerException("Invalid state at the start of callback"); |
|
} |
|
|
|
void EnterCallback(PausedReason pausedReason, string name, ICorDebugAppDomain pAppDomain) |
|
{ |
|
EnterCallback(pausedReason, name, pAppDomain.GetProcess()); |
|
} |
|
|
|
void EnterCallback(PausedReason pausedReason, string name, ICorDebugThread pThread) |
|
{ |
|
EnterCallback(pausedReason, name, pThread.GetProcess()); |
|
process.SelectedThread = process.GetThread(pThread); |
|
} |
|
|
|
void ExitCallback() |
|
{ |
|
bool hasQueuedCallbacks = process.CorProcess.HasQueuedCallbacks(); |
|
if (hasQueuedCallbacks) |
|
process.TraceMessage("Process has queued callbacks"); |
|
|
|
// only process callbacks if no exception occurred |
|
if (hasQueuedCallbacks && exceptionThrown == null) { |
|
// Exception has Exception2 queued after it |
|
process.AsyncContinue(DebuggeeStateAction.Keep, null, null); |
|
} else if (process.Evaluating) { |
|
// Ignore events during property evaluation |
|
process.AsyncContinue(DebuggeeStateAction.Keep, null, null); |
|
} else if (pauseOnNextExit) { |
|
if (process.Options.Verbose) |
|
process.TraceMessage("Callback exit: Paused"); |
|
pauseOnNextExit = false; |
|
Pause(); |
|
} else { |
|
process.AsyncContinue(DebuggeeStateAction.Keep, null, null); |
|
} |
|
|
|
isInCallback = false; |
|
} |
|
|
|
void Pause() |
|
{ |
|
if (process.PauseSession.PausedReason == PausedReason.EvalComplete || |
|
process.PauseSession.PausedReason == PausedReason.ExceptionIntercepted) { |
|
// TODO: There might be qued callback after EvalComplete making this unrealiable |
|
process.DisableAllSteppers(); |
|
process.CheckSelectedStackFrames(); |
|
// Do not set selected stack frame |
|
// Do not raise events |
|
} else { |
|
DebuggerEventArgs e = new DebuggerEventArgs(process); |
|
e.BreakpointsHit = breakpointsHit.ToArray(); |
|
e.ExceptionThrown = exceptionThrown; |
|
breakpointsHit.Clear(); |
|
exceptionThrown = null; |
|
|
|
// Raise the pause event outside the callback |
|
// Warning: Make sure that process in not resumed in the meantime |
|
process.Debugger.MTA2STA.AsyncCall(delegate { process.OnPaused(e); }); |
|
} |
|
} |
|
|
|
|
|
#region Program folow control |
|
|
|
public void StepComplete(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugStepper pStepper, CorDebugStepReason reason) |
|
{ |
|
EnterCallback(PausedReason.StepComplete, "StepComplete (" + reason.ToString() + ")", pThread); |
|
|
|
Thread thread = process.GetThread(pThread); |
|
Stepper stepper = process.GetStepper(pStepper); |
|
|
|
StackFrame currentStackFrame = process.SelectedThread.MostRecentStackFrame; |
|
process.TraceMessage(" - stopped at {0} because of {1}", currentStackFrame.MethodInfo.FullName, stepper.ToString()); |
|
|
|
process.Steppers.Remove(stepper); |
|
stepper.OnStepComplete(reason); |
|
|
|
if (stepper.Ignore) { |
|
// The stepper is ignored |
|
process.TraceMessage(" - ignored"); |
|
} else if (thread.CurrentStepIn != null && |
|
thread.CurrentStepIn.StackFrame.Equals(currentStackFrame) && |
|
thread.CurrentStepIn.IsInStepRanges((int)currentStackFrame.IP)) { |
|
Stepper.StepIn(currentStackFrame, thread.CurrentStepIn.StepRanges, "finishing step in"); |
|
process.TraceMessage(" - finishing step in"); |
|
} else if (currentStackFrame.MethodInfo.StepOver) { |
|
if (process.Options.EnableJustMyCode) { |
|
currentStackFrame.MethodInfo.MarkAsNonUserCode(); |
|
process.TraceMessage(" - method {0} marked as non user code", currentStackFrame.MethodInfo.FullName); |
|
Stepper.StepIn(currentStackFrame, new int[] {0, int.MaxValue}, "seeking user code"); |
|
process.TraceMessage(" - seeking user code"); |
|
} else { |
|
Stepper.StepOut(currentStackFrame, "stepping out of non-user code"); |
|
process.TraceMessage(" - stepping out of non-user code"); |
|
} |
|
} else { |
|
// User-code method |
|
pauseOnNextExit = true; |
|
process.TraceMessage(" - pausing in user code"); |
|
} |
|
|
|
ExitCallback(); |
|
} |
|
|
|
// Warning! Marshaing of ICorBreakpoint fails in .NET 1.1 |
|
public void Breakpoint(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugBreakpoint corBreakpoint) |
|
{ |
|
EnterCallback(PausedReason.Breakpoint, "Breakpoint", pThread); |
|
|
|
Breakpoint breakpoint = process.Debugger.GetBreakpoint(corBreakpoint); |
|
// Could be tempBreakpoint |
|
if (breakpoint != null) { |
|
breakpointsHit.Add(breakpoint); |
|
} |
|
|
|
pauseOnNextExit = true; |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void BreakpointSetError(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugBreakpoint pBreakpoint, uint dwError) |
|
{ |
|
EnterCallback(PausedReason.Other, "BreakpointSetError", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void Break(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread) |
|
{ |
|
EnterCallback(PausedReason.Break, "Break", pThread); |
|
|
|
pauseOnNextExit = true; |
|
ExitCallback(); |
|
} |
|
|
|
public void ControlCTrap(ICorDebugProcess pProcess) |
|
{ |
|
EnterCallback(PausedReason.ControlCTrap, "ControlCTrap", pProcess); |
|
|
|
pauseOnNextExit = true; |
|
ExitCallback(); |
|
} |
|
|
|
public void Exception(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, int unhandled) |
|
{ |
|
// Exception2 is used in .NET Framework 2.0 |
|
|
|
if (process.DebuggeeVersion.StartsWith("v1.")) { |
|
// Forward the call to Exception2, which handles EnterCallback and ExitCallback |
|
ExceptionType exceptionType = (unhandled != 0) ? ExceptionType.Unhandled : ExceptionType.FirstChance; |
|
Exception2(pAppDomain, pThread, null, 0, (CorDebugExceptionCallbackType)exceptionType, 0); |
|
} else { |
|
// This callback should be ignored in v2 applications |
|
EnterCallback(PausedReason.Other, "Exception", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Various |
|
|
|
public void LogSwitch(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, int lLevel, uint ulReason, string pLogSwitchName, string pParentName) |
|
{ |
|
EnterCallback(PausedReason.Other, "LogSwitch", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void LogMessage(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, int lLevel, string pLogSwitchName, string pMessage) |
|
{ |
|
EnterCallback(PausedReason.Other, "LogMessage", pThread); |
|
|
|
process.OnLogMessage(new MessageEventArgs(process, lLevel, pMessage, pLogSwitchName)); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void EditAndContinueRemap(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugFunction pFunction, int fAccurate) |
|
{ |
|
EnterCallback(PausedReason.Other, "EditAndContinueRemap", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void EvalException(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugEval corEval) |
|
{ |
|
EnterCallback(PausedReason.EvalComplete, "EvalException (" + process.GetActiveEval(corEval).Description + ")", pThread); |
|
|
|
HandleEvalComplete(pAppDomain, pThread, corEval, true); |
|
} |
|
|
|
public void EvalComplete(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugEval corEval) |
|
{ |
|
EnterCallback(PausedReason.EvalComplete, "EvalComplete (" + process.GetActiveEval(corEval).Description + ")", pThread); |
|
|
|
HandleEvalComplete(pAppDomain, pThread, corEval, false); |
|
} |
|
|
|
void HandleEvalComplete(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugEval corEval, bool exception) |
|
{ |
|
// Let the eval know that the CorEval has finished |
|
Eval eval = process.GetActiveEval(corEval); |
|
eval.NotifyEvaluationComplete(!exception); |
|
process.activeEvals.Remove(eval); |
|
|
|
pauseOnNextExit = true; |
|
ExitCallback(); |
|
} |
|
|
|
public void DebuggerError(ICorDebugProcess pProcess, int errorHR, uint errorCode) |
|
{ |
|
EnterCallback(PausedReason.DebuggerError, "DebuggerError", pProcess); |
|
|
|
string errorText = String.Format("Debugger error: \nHR = 0x{0:X} \nCode = 0x{1:X}", errorHR, errorCode); |
|
|
|
if ((uint)errorHR == 0x80131C30) { |
|
errorText += "\n\nDebugging 64-bit processes is currently not supported.\n" + |
|
"If you are running a 64-bit system, this setting might help:\n" + |
|
"Project -> Project Options -> Compiling -> Target CPU = 32-bit Intel"; |
|
} |
|
|
|
if (Environment.UserInteractive) |
|
System.Windows.Forms.MessageBox.Show(errorText); |
|
else |
|
throw new DebuggerException(errorText); |
|
|
|
try { |
|
pauseOnNextExit = true; |
|
ExitCallback(); |
|
} catch (COMException) { |
|
} catch (InvalidComObjectException) { |
|
// ignore errors during shutdown after debugger error |
|
} |
|
} |
|
|
|
public void UpdateModuleSymbols(ICorDebugAppDomain pAppDomain, ICorDebugModule pModule, IStream pSymbolStream) |
|
{ |
|
EnterCallback(PausedReason.Other, "UpdateModuleSymbols", pAppDomain); |
|
|
|
Module module = process.GetModule(pModule); |
|
if (module.CorModule is ICorDebugModule3 && module.IsDynamic) { |
|
// In .NET 4.0, we use the LoadClass callback to load dynamic modules |
|
// because it always works - UpdateModuleSymbols does not. |
|
// - Simple dynamic code generation seems to trigger both callbacks. |
|
// - IronPython for some reason causes just the LoadClass callback |
|
// so we choose to rely on it out of the two. |
|
} else { |
|
// In .NET 2.0, this is the the only method and it works fine |
|
module.LoadSymbolsFromMemory(pSymbolStream); |
|
} |
|
|
|
ExitCallback(); |
|
} |
|
|
|
#endregion |
|
|
|
#region Start of Application |
|
|
|
public void CreateProcess(ICorDebugProcess pProcess) |
|
{ |
|
EnterCallback(PausedReason.Other, "CreateProcess", pProcess); |
|
|
|
// Process is added in NDebugger.Start |
|
// disable NGen |
|
if (!this.process.Options.EnableJustMyCode && !this.process.Options.StepOverNoSymbols) { |
|
ICorDebugProcess2 pProcess2 = pProcess as ICorDebugProcess2; |
|
if (pProcess2 != null && Process.DebugMode == DebugModeFlag.Debug) { |
|
try { |
|
pProcess2.SetDesiredNGENCompilerFlags((uint)CorDebugJITCompilerFlags.CORDEBUG_JIT_DISABLE_OPTIMIZATION); |
|
} catch (COMException) { |
|
// we cannot set the NGEN flag => no evaluation for optimized code. |
|
} |
|
} |
|
} |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void CreateAppDomain(ICorDebugProcess pProcess, ICorDebugAppDomain pAppDomain) |
|
{ |
|
EnterCallback(PausedReason.Other, "CreateAppDomain", pAppDomain); |
|
|
|
pAppDomain.Attach(); |
|
process.appDomains.Add(new AppDomain(process, pAppDomain)); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void LoadAssembly(ICorDebugAppDomain pAppDomain, ICorDebugAssembly pAssembly) |
|
{ |
|
EnterCallback(PausedReason.Other, "LoadAssembly", pAppDomain); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void LoadModule(ICorDebugAppDomain pAppDomain, ICorDebugModule pModule) |
|
{ |
|
EnterCallback(PausedReason.Other, "LoadModule " + pModule.GetName(), pAppDomain); |
|
|
|
Module module = new Module(process.GetAppDomain(pAppDomain), pModule); |
|
process.modules.Add(module); |
|
process.OnModuleLoaded(module); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void NameChange(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread) |
|
{ |
|
if (pAppDomain != null) { |
|
|
|
EnterCallback(PausedReason.Other, "NameChange: pAppDomain", pAppDomain); |
|
|
|
ExitCallback(); |
|
|
|
} |
|
if (pThread != null) { |
|
|
|
EnterCallback(PausedReason.Other, "NameChange: pThread", pThread); |
|
|
|
ExitCallback(); |
|
|
|
} |
|
} |
|
|
|
public void CreateThread(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread) |
|
{ |
|
// We can not use pThread since it has not been added yet |
|
// and we continue from this callback anyway |
|
EnterCallback(PausedReason.Other, "CreateThread " + pThread.GetID(), pAppDomain); |
|
|
|
Thread thread = new Thread(process, pThread); |
|
process.threads.Add(thread); |
|
|
|
thread.CorThread.SetDebugState(process.NewThreadState); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void LoadClass(ICorDebugAppDomain pAppDomain, ICorDebugClass c) |
|
{ |
|
EnterCallback(PausedReason.Other, "LoadClass", pAppDomain); |
|
|
|
Module module = process.GetModule(c.GetModule()); |
|
|
|
// Dynamic module has been extended - reload symbols to inlude new class |
|
module.LoadSymbolsDynamic(); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
#endregion |
|
|
|
#region Exit of Application |
|
|
|
public void UnloadClass(ICorDebugAppDomain pAppDomain, ICorDebugClass c) |
|
{ |
|
EnterCallback(PausedReason.Other, "UnloadClass", pAppDomain); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void UnloadModule(ICorDebugAppDomain pAppDomain, ICorDebugModule pModule) |
|
{ |
|
EnterCallback(PausedReason.Other, "UnloadModule", pAppDomain); |
|
|
|
Module module = process.GetModule(pModule); |
|
process.modules.Remove(module); |
|
process.OnModuleUnloaded(module); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void UnloadAssembly(ICorDebugAppDomain pAppDomain, ICorDebugAssembly pAssembly) |
|
{ |
|
EnterCallback(PausedReason.Other, "UnloadAssembly", pAppDomain); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void ExitThread(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread) |
|
{ |
|
Thread thread = process.TryGetThread(pThread); |
|
|
|
// ICorDebugThread is still not dead and can be used for some operations |
|
if (thread != null) { |
|
EnterCallback(PausedReason.Other, "ExitThread " + pThread.GetID(), pThread); |
|
|
|
thread.NotifyExited(); |
|
} else { |
|
EnterCallback(PausedReason.Other, "ExitThread " + pThread.GetID(), process.CorProcess); |
|
|
|
// .NET 4.0 - It seems that the API is reporting exits of threads without announcing their creation. |
|
// TODO: Remove in next .NET 4.0 beta and investigate |
|
process.TraceMessage("ERROR: Thread does not exist " + pThread.GetID()); |
|
} |
|
|
|
try { |
|
ExitCallback(); |
|
} catch (COMException e) { |
|
// For some reason this sometimes happens in .NET 1.1 |
|
process.TraceMessage("Continue failed in ExitThread callback: " + e.Message); |
|
} |
|
} |
|
|
|
public void ExitAppDomain(ICorDebugProcess pProcess, ICorDebugAppDomain pAppDomain) |
|
{ |
|
EnterCallback(PausedReason.Other, "ExitAppDomain", pAppDomain); |
|
|
|
process.appDomains.Remove(process.GetAppDomain(pAppDomain)); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void ExitProcess(ICorDebugProcess pProcess) |
|
{ |
|
// ExitProcess may be called at any time when debuggee is killed |
|
process.TraceMessage("Callback: ExitProcess"); |
|
|
|
process.OnExited(); |
|
} |
|
|
|
#endregion |
|
|
|
#region ICorDebugManagedCallback2 Members |
|
|
|
public void ChangeConnection(ICorDebugProcess pProcess, uint dwConnectionId) |
|
{ |
|
EnterCallback(PausedReason.Other, "ChangeConnection", pProcess); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void CreateConnection(ICorDebugProcess pProcess, uint dwConnectionId, IntPtr pConnName) |
|
{ |
|
EnterCallback(PausedReason.Other, "CreateConnection", pProcess); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void DestroyConnection(ICorDebugProcess pProcess, uint dwConnectionId) |
|
{ |
|
EnterCallback(PausedReason.Other, "DestroyConnection", pProcess); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void Exception2(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugFrame pFrame, uint nOffset, CorDebugExceptionCallbackType _exceptionType, uint dwFlags) |
|
{ |
|
EnterCallback(PausedReason.Exception, "Exception2 (type=" + _exceptionType.ToString() + ")", pThread); |
|
|
|
// This callback is also called from Exception(...)!!!! (the .NET 1.1 version) |
|
// Watch out for the zeros and null! |
|
// Exception -> Exception2(pAppDomain, pThread, null, 0, exceptionType, 0); |
|
|
|
ExceptionType exceptionType = (ExceptionType)_exceptionType; |
|
bool pauseOnHandled = !process.Evaluating && process.Options != null && process.Options.PauseOnHandledExceptions; |
|
|
|
if (exceptionType == ExceptionType.Unhandled || (pauseOnHandled && exceptionType == ExceptionType.CatchHandlerFound)) { |
|
// sanity check: we can only handle one exception after another |
|
// TODO : create Exception queue if CLR throws multiple exceptions |
|
Debug.Assert(exceptionThrown == null); |
|
Value value = new Value(process.GetAppDomain(pAppDomain), process.SelectedThread.CorThread.GetCurrentException()).GetPermanentReference(); |
|
exceptionThrown = new Exception(value, exceptionType); |
|
|
|
pauseOnNextExit = true; |
|
} |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void ExceptionUnwind(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, CorDebugExceptionUnwindCallbackType dwEventType, uint dwFlags) |
|
{ |
|
EnterCallback(PausedReason.ExceptionIntercepted, "ExceptionUnwind", pThread); |
|
|
|
if (dwEventType == CorDebugExceptionUnwindCallbackType.DEBUG_EXCEPTION_INTERCEPTED) { |
|
pauseOnNextExit = true; |
|
} |
|
ExitCallback(); |
|
} |
|
|
|
public void FunctionRemapComplete(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugFunction pFunction) |
|
{ |
|
EnterCallback(PausedReason.Other, "FunctionRemapComplete", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
public void FunctionRemapOpportunity(ICorDebugAppDomain pAppDomain, ICorDebugThread pThread, ICorDebugFunction pOldFunction, ICorDebugFunction pNewFunction, uint oldILOffset) |
|
{ |
|
EnterCallback(PausedReason.Other, "FunctionRemapOpportunity", pThread); |
|
|
|
ExitCallback(); |
|
} |
|
|
|
/// <exception cref="Exception">Unknown callback argument</exception> |
|
public void MDANotification(ICorDebugController c, ICorDebugThread t, ICorDebugMDA mda) |
|
{ |
|
if (c is ICorDebugAppDomain) { |
|
EnterCallback(PausedReason.Other, "MDANotification", (ICorDebugAppDomain)c); |
|
} else if (c is ICorDebugProcess){ |
|
EnterCallback(PausedReason.Other, "MDANotification", (ICorDebugProcess)c); |
|
} else { |
|
throw new System.Exception("Unknown callback argument"); |
|
} |
|
|
|
ExitCallback(); |
|
} |
|
|
|
#endregion |
|
} |
|
}
|
|
|