// 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.Runtime.InteropServices; using Debugger.Interop.CorDebug; using Debugger.Interop.CorSym; using ICSharpCode.NRefactory.Ast; using ICSharpCode.NRefactory.Visitors; namespace Debugger { internal enum DebuggeeStateAction { Keep, Clear } /// /// Debug Mode Flags. /// public enum DebugModeFlag { /// /// Run in the same mode as without debugger. /// Default, /// /// Run in forced optimized mode. /// Optimized, /// /// Run in debug mode (easy inspection) but slower. /// Debug, /// /// Run in ENC mode (ENC possible) but even slower than debug /// Enc } public class Process: DebuggerObject { NDebugger debugger; ICorDebugProcess corProcess; ManagedCallback callbackInterface; EvalCollection activeEvals; ModuleCollection modules; ThreadCollection threads; AppDomainCollection appDomains; string workingDirectory; public NDebugger Debugger { get { return debugger; } } internal ICorDebugProcess CorProcess { get { return corProcess; } } public Options Options { get { return debugger.Options; } } public string DebuggeeVersion { get { return debugger.DebuggeeVersion; } } internal ManagedCallback CallbackInterface { get { return callbackInterface; } } public EvalCollection ActiveEvals { get { return activeEvals; } } internal bool Evaluating { get { return activeEvals.Count > 0; } } public ModuleCollection Modules { get { return modules; } } public ThreadCollection Threads { get { return threads; } } public Thread SelectedThread { get { return this.Threads.Selected; } set { this.Threads.Selected = value; } } public StackFrame SelectedStackFrame { get { if (SelectedThread == null) { return null; } else { return SelectedThread.SelectedStackFrame; } } } public SourcecodeSegment NextStatement { get { if (SelectedStackFrame == null || IsRunning) { return null; } else { return SelectedStackFrame.NextStatement; } } } public bool BreakAtBeginning { get; set; } public AppDomainCollection AppDomains { get { return appDomains; } } List steppers = new List(); internal List Steppers { get { return steppers; } } public string WorkingDirectory { get { return workingDirectory; } } public static DebugModeFlag DebugMode { get; set; } internal Process(NDebugger debugger, ICorDebugProcess corProcess, string workingDirectory) { this.debugger = debugger; this.corProcess = corProcess; this.workingDirectory = workingDirectory; this.callbackInterface = new ManagedCallback(this); activeEvals = new EvalCollection(debugger); modules = new ModuleCollection(debugger); modules.Added += OnModulesAdded; threads = new ThreadCollection(debugger); appDomains = new AppDomainCollection(debugger); } static unsafe public Process CreateProcess(NDebugger debugger, string filename, string workingDirectory, string arguments) { debugger.TraceMessage("Executing " + filename + " " + arguments); uint[] processStartupInfo = new uint[17]; processStartupInfo[0] = sizeof(uint) * 17; uint[] processInfo = new uint[4]; ICorDebugProcess outProcess; if (workingDirectory == null || workingDirectory == "") { workingDirectory = System.IO.Path.GetDirectoryName(filename); } _SECURITY_ATTRIBUTES secAttr = new _SECURITY_ATTRIBUTES(); secAttr.bInheritHandle = 0; secAttr.lpSecurityDescriptor = IntPtr.Zero; secAttr.nLength = (uint)sizeof(_SECURITY_ATTRIBUTES); fixed (uint* pprocessStartupInfo = processStartupInfo) fixed (uint* pprocessInfo = processInfo) outProcess = debugger.CorDebug.CreateProcess( filename, // lpApplicationName // If we do not prepend " ", the first argument migh just get lost " " + arguments, // lpCommandLine ref secAttr, // lpProcessAttributes ref secAttr, // lpThreadAttributes 1,//TRUE // bInheritHandles 0x00000010 /*CREATE_NEW_CONSOLE*/, // dwCreationFlags IntPtr.Zero, // lpEnvironment workingDirectory, // lpCurrentDirectory (uint)pprocessStartupInfo, // lpStartupInfo (uint)pprocessInfo, // lpProcessInformation, CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS // debuggingFlags ); return new Process(debugger, outProcess, workingDirectory); } /// Fired when System.Diagnostics.Trace.WriteLine() is called in debuged process public event EventHandler LogMessage; protected internal virtual void OnLogMessage(MessageEventArgs arg) { TraceMessage ("Debugger event: OnLogMessage"); if (LogMessage != null) { LogMessage(this, arg); } } public void TraceMessage(string message, params object[] args) { if (args.Length > 0) message = string.Format(message, args); System.Diagnostics.Debug.WriteLine("Debugger:" + message); debugger.OnDebuggerTraceMessage(new MessageEventArgs(this, message)); } /// Read the specified amount of memory at the given memory address /// The content of the memory. The amount of the read memory may be less then requested. public unsafe byte[] ReadMemory(ulong address, int size) { byte[] buffer = new byte[size]; int readCount; fixed(byte* pBuffer = buffer) { readCount = (int)corProcess.ReadMemory(address, (uint)size, new IntPtr(pBuffer)); } if (readCount != size) Array.Resize(ref buffer, readCount); return buffer; } /// Writes the given buffer at the specified memory address /// The number of bytes written public unsafe int WriteMemory(ulong address, byte[] buffer) { if (buffer.Length == 0) return 0; int written; fixed(byte* pBuffer = buffer) { written = (int)corProcess.WriteMemory(address, (uint)buffer.Length, new IntPtr(pBuffer)); } return written; } #region Exceptions bool pauseOnHandledException = false; public event EventHandler ExceptionThrown; public bool PauseOnHandledException { get { return pauseOnHandledException; } set { pauseOnHandledException = value; } } protected internal virtual void OnExceptionThrown(ExceptionEventArgs e) { TraceMessage ("Debugger event: OnExceptionThrown()"); if (ExceptionThrown != null) { ExceptionThrown(this, e); } } #endregion // State control for the process internal bool TerminateCommandIssued = false; internal Queue BreakpointHitEventQueue = new Queue(); internal Dictionary ExpressionsCache = new Dictionary(); #region Events public event EventHandler Paused; public event EventHandler Resumed; // HACK: public public virtual void OnPaused() { AssertPaused(); // No real purpose - just additional check if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback."); TraceMessage ("Debugger event: OnPaused()"); if (Paused != null) { foreach(Delegate d in Paused.GetInvocationList()) { if (IsRunning) { TraceMessage ("Skipping OnPaused delegate because process has resumed"); break; } if (this.TerminateCommandIssued || this.HasExited) { TraceMessage ("Skipping OnPaused delegate because process has exited"); break; } d.DynamicInvoke(this, new ProcessEventArgs(this)); } } } protected virtual void OnResumed() { AssertRunning(); if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback."); TraceMessage ("Debugger event: OnResumed()"); if (Resumed != null) { Resumed(this, new ProcessEventArgs(this)); } } #endregion #region PauseSession & DebugeeState PauseSession pauseSession; DebuggeeState debuggeeState; /// /// Indentification of the current debugger session. This value changes whenever debugger is continued /// public PauseSession PauseSession { get { return pauseSession; } } /// /// Indentification of the state of the debugee. This value changes whenever the state of the debugee significatntly changes /// public DebuggeeState DebuggeeState { get { return debuggeeState; } } /// Puts the process into a paused state internal void NotifyPaused(PausedReason pauseReason) { AssertRunning(); pauseSession = new PauseSession(this, pauseReason); if (debuggeeState == null) { debuggeeState = new DebuggeeState(this); } } /// Puts the process into a resumed state internal void NotifyResumed(DebuggeeStateAction action) { AssertPaused(); pauseSession = null; if (action == DebuggeeStateAction.Clear) { if (debuggeeState == null) throw new DebuggerException("Debugee state already cleared"); debuggeeState = null; this.ExpressionsCache.Clear(); } } /// Sets up the eviroment and raises user events internal void RaisePausedEvents() { AssertPaused(); DisableAllSteppers(); CheckSelectedStackFrames(); SelectMostRecentStackFrameWithLoadedSymbols(); if (this.PauseSession.PausedReason == PausedReason.Exception) { ExceptionEventArgs args = new ExceptionEventArgs(this, this.SelectedThread.CurrentException, this.SelectedThread.CurrentExceptionType, this.SelectedThread.CurrentExceptionIsUnhandled); OnExceptionThrown(args); // The event could have resumed or killed the process if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; } while(BreakpointHitEventQueue.Count > 0) { Breakpoint breakpoint = BreakpointHitEventQueue.Dequeue(); breakpoint.NotifyHit(); // The event could have resumed or killed the process if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; } OnPaused(); // The event could have resumed the process if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; } #endregion internal void AssertPaused() { if (IsRunning) { throw new DebuggerException("Process is not paused."); } } internal void AssertRunning() { if (IsPaused) { throw new DebuggerException("Process is not running."); } } public bool IsRunning { get { return pauseSession == null; } } public bool IsPaused { get { return !IsRunning; } } bool hasExited = false; public event EventHandler Exited; public bool HasExited { get { return hasExited; } } internal void NotifyHasExited() { if(!hasExited) { hasExited = true; if (Exited != null) { Exited(this, new ProcessEventArgs(this)); } // Expire pause seesion first if (IsPaused) { NotifyResumed(DebuggeeStateAction.Clear); } debugger.Processes.Remove(this); } } public void Break() { AssertRunning(); corProcess.Stop(uint.MaxValue); // Infinite; ignored anyway NotifyPaused(PausedReason.ForcedBreak); RaisePausedEvents(); } public void Detach() { if (IsRunning) { corProcess.Stop(uint.MaxValue); NotifyPaused(PausedReason.ForcedBreak); } // This is necessary for detach foreach(Stepper s in this.Steppers) { if (s.CorStepper.IsActive() == 1) { s.CorStepper.Deactivate(); } } this.Steppers.Clear(); corProcess.Detach(); // modules foreach(Module m in this.Modules) { m.Dispose(); } this.modules.Clear(); // threads this.threads.Clear(); NotifyHasExited(); } public void Continue() { AsyncContinue(); WaitForPause(); } internal Thread[] UnsuspendedThreads { get { List unsuspendedThreads = new List(this.Threads.Count); foreach(Thread t in this.Threads) { if (!t.Suspended) unsuspendedThreads.Add(t); } return unsuspendedThreads.ToArray(); } } /// /// Resume execution and run all threads not marked by the user as susspended. /// public void AsyncContinue() { AsyncContinue(DebuggeeStateAction.Clear, this.UnsuspendedThreads, CorDebugThreadState.THREAD_RUN); } internal CorDebugThreadState NewThreadState = CorDebugThreadState.THREAD_RUN; /// Null to keep current setting /// What happens to created threads. Null to keep current setting internal void AsyncContinue(DebuggeeStateAction action, Thread[] threadsToRun, CorDebugThreadState? newThreadState) { AssertPaused(); if (threadsToRun != null) { // corProcess.SetAllThreadsDebugState(CorDebugThreadState.THREAD_SUSPEND, null); // Note: There is unreported thread, stopping it prevents the debugee from exiting // It is not corProcess.GetHelperThreadID // ICorDebugThread[] ts = new ICorDebugThread[corProcess.EnumerateThreads().GetCount()]; // corProcess.EnumerateThreads().Next((uint)ts.Length, ts); foreach(Thread t in this.Threads) { CorDebugThreadState state = Array.IndexOf(threadsToRun, t) == -1 ? CorDebugThreadState.THREAD_SUSPEND : CorDebugThreadState.THREAD_RUN; try { t.CorThread.SetDebugState(state); } catch (COMException e) { // The state of the thread is invalid. (Exception from HRESULT: 0x8013132D) // It can happen for example when thread has not started yet if ((uint)e.ErrorCode == 0x8013132D) { // TraceMessage("Can not suspend thread - The state of the thread is invalid. Thread ID = " + t.CorThread.GetID()); } else { throw; } } } } if (newThreadState != null) { this.NewThreadState = newThreadState.Value; } NotifyResumed(action); corProcess.Continue(0); if (this.Options.Verbose) { this.TraceMessage("Continue"); } if (action == DebuggeeStateAction.Clear) { OnResumed(); } } /// Terminates the execution of the process public void Terminate() { AsyncTerminate(); // Wait until ExitProcess callback is received WaitForExit(); } /// Terminates the execution of the process public void AsyncTerminate() { // Resume stoped tread if (this.IsPaused) { // We might get more callbacks so we should maintain consistent sate //AsyncContinue(); // Continue the process to get remaining callbacks } // Expose race condition - drain callback queue System.Threading.Thread.Sleep(0); // Stop&terminate - both must be called corProcess.Stop(uint.MaxValue); corProcess.Terminate(0); this.TerminateCommandIssued = true; // Do not mark the process as exited // This is done once ExitProcess callback is received } void SelectSomeThread() { if (this.SelectedThread != null && !this.SelectedThread.IsInValidState) { this.SelectedThread = null; } if (this.SelectedThread == null) { foreach(Thread thread in this.Threads) { if (thread.IsInValidState) { this.SelectedThread = thread; break; } } } } internal void CheckSelectedStackFrames() { foreach(Thread thread in this.Threads) { if (thread.IsInValidState) { if (thread.SelectedStackFrame != null && thread.SelectedStackFrame.IsInvalid) { thread.SelectedStackFrame = null; } } else { thread.SelectedStackFrame = null; } } } internal void SelectMostRecentStackFrameWithLoadedSymbols() { SelectSomeThread(); if (this.SelectedThread != null) { this.SelectedThread.SelectedStackFrame = null; foreach (StackFrame stackFrame in this.SelectedThread.Callstack) { if (stackFrame.HasSymbols) { if (this.Options.StepOverDebuggerAttributes && stackFrame.MethodInfo.IsNonUserCode) continue; this.SelectedThread.SelectedStackFrame = stackFrame; break; } } } } internal Stepper GetStepper(ICorDebugStepper corStepper) { foreach(Stepper stepper in this.Steppers) { if (stepper.IsCorStepper(corStepper)) { return stepper; } } throw new DebuggerException("Stepper is not in collection"); } internal void DisableAllSteppers() { foreach(Thread thread in this.Threads) { thread.CurrentStepIn = null; } foreach(Stepper stepper in this.Steppers) { stepper.Ignore = true; } } /// /// Waits until the debugger pauses unless it is already paused. /// Use PausedReason to find out why it paused. /// public void WaitForPause() { while(this.IsRunning && !this.HasExited) { debugger.MTA2STA.WaitForCall(); debugger.MTA2STA.PerformAllCalls(); } if (this.HasExited) throw new ProcessExitedException(); } public void WaitForPause(TimeSpan timeout) { System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); while(this.IsRunning && !this.HasExited) { TimeSpan timeLeft = timeout - watch.Elapsed; if (timeLeft <= TimeSpan.FromMilliseconds(10)) break; //this.TraceMessage("Time left: " + timeLeft.TotalMilliseconds); debugger.MTA2STA.WaitForCall(timeLeft); debugger.MTA2STA.PerformAllCalls(); } if (this.HasExited) throw new ProcessExitedException(); } /// /// Waits until the precesses exits. /// public void WaitForExit() { while(!this.HasExited) { debugger.MTA2STA.WaitForCall(); debugger.MTA2STA.PerformAllCalls(); } } #region Break at begining private void OnModulesAdded(object sender, CollectionItemEventArgs e) { if (BreakAtBeginning) { if (e.Item.SymReader == null) return; // No symbols try { // create a BP at entry point uint entryPoint = e.Item.SymReader.GetUserEntryPoint(); if (entryPoint == 0) return; // no EP var mainFunction = e.Item.CorModule.GetFunctionFromToken(entryPoint); var corBreakpoint = mainFunction.CreateBreakpoint(); corBreakpoint.Activate(1); // create a SD BP var breakpoint = new Breakpoint(this.debugger, corBreakpoint); this.debugger.Breakpoints.Add(breakpoint); breakpoint.Hit += delegate { if (breakpoint != null) breakpoint.Remove(); breakpoint = null; }; } catch { // the app does not have an entry point - COM exception } BreakAtBeginning = false; } } #endregion } }