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.

699 lines
19 KiB

  1. // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
  2. // This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Runtime.InteropServices;
  6. using Debugger.Interop.CorDebug;
  7. using Debugger.Interop.CorSym;
  8. using ICSharpCode.NRefactory.CSharp;
  9. using ICSharpCode.NRefactory.Visitors;
  10. namespace Debugger
  11. {
  12. internal enum DebuggeeStateAction { Keep, Clear }
  13. /// <summary>
  14. /// Debug Mode Flags.
  15. /// </summary>
  16. public enum DebugModeFlag
  17. {
  18. /// <summary>
  19. /// Run in the same mode as without debugger.
  20. /// </summary>
  21. Default,
  22. /// <summary>
  23. /// Run in forced optimized mode.
  24. /// </summary>
  25. Optimized,
  26. /// <summary>
  27. /// Run in debug mode (easy inspection) but slower.
  28. /// </summary>
  29. Debug,
  30. /// <summary>
  31. /// Run in ENC mode (ENC possible) but even slower than debug
  32. /// </summary>
  33. Enc
  34. }
  35. public class Process: DebuggerObject
  36. {
  37. NDebugger debugger;
  38. ICorDebugProcess corProcess;
  39. ManagedCallback callbackInterface;
  40. EvalCollection activeEvals;
  41. ModuleCollection modules;
  42. ThreadCollection threads;
  43. AppDomainCollection appDomains;
  44. string workingDirectory;
  45. public NDebugger Debugger {
  46. get { return debugger; }
  47. }
  48. internal ICorDebugProcess CorProcess {
  49. get { return corProcess; }
  50. }
  51. public Options Options {
  52. get { return debugger.Options; }
  53. }
  54. public string DebuggeeVersion {
  55. get { return debugger.DebuggeeVersion; }
  56. }
  57. internal ManagedCallback CallbackInterface {
  58. get { return callbackInterface; }
  59. }
  60. public EvalCollection ActiveEvals {
  61. get { return activeEvals; }
  62. }
  63. internal bool Evaluating {
  64. get { return activeEvals.Count > 0; }
  65. }
  66. public ModuleCollection Modules {
  67. get { return modules; }
  68. }
  69. public ThreadCollection Threads {
  70. get { return threads; }
  71. }
  72. public Thread SelectedThread {
  73. get { return this.Threads.Selected; }
  74. set { this.Threads.Selected = value; }
  75. }
  76. public StackFrame SelectedStackFrame {
  77. get {
  78. if (SelectedThread == null) {
  79. return null;
  80. } else {
  81. return SelectedThread.SelectedStackFrame;
  82. }
  83. }
  84. }
  85. public SourcecodeSegment NextStatement {
  86. get {
  87. if (SelectedStackFrame == null || IsRunning) {
  88. return null;
  89. } else {
  90. return SelectedStackFrame.NextStatement;
  91. }
  92. }
  93. }
  94. public bool BreakAtBeginning {
  95. get;
  96. set;
  97. }
  98. public AppDomainCollection AppDomains {
  99. get { return appDomains; }
  100. }
  101. List<Stepper> steppers = new List<Stepper>();
  102. internal List<Stepper> Steppers {
  103. get { return steppers; }
  104. }
  105. public string WorkingDirectory {
  106. get { return workingDirectory; }
  107. }
  108. public static DebugModeFlag DebugMode { get; set; }
  109. internal Process(NDebugger debugger, ICorDebugProcess corProcess, string workingDirectory)
  110. {
  111. this.debugger = debugger;
  112. this.corProcess = corProcess;
  113. this.workingDirectory = workingDirectory;
  114. this.callbackInterface = new ManagedCallback(this);
  115. activeEvals = new EvalCollection(debugger);
  116. modules = new ModuleCollection(debugger);
  117. modules.Added += OnModulesAdded;
  118. threads = new ThreadCollection(debugger);
  119. appDomains = new AppDomainCollection(debugger);
  120. }
  121. static unsafe public Process CreateProcess(NDebugger debugger, string filename, string workingDirectory, string arguments)
  122. {
  123. debugger.TraceMessage("Executing " + filename + " " + arguments);
  124. uint[] processStartupInfo = new uint[17];
  125. processStartupInfo[0] = sizeof(uint) * 17;
  126. uint[] processInfo = new uint[4];
  127. ICorDebugProcess outProcess;
  128. if (workingDirectory == null || workingDirectory == "") {
  129. workingDirectory = System.IO.Path.GetDirectoryName(filename);
  130. }
  131. _SECURITY_ATTRIBUTES secAttr = new _SECURITY_ATTRIBUTES();
  132. secAttr.bInheritHandle = 0;
  133. secAttr.lpSecurityDescriptor = IntPtr.Zero;
  134. secAttr.nLength = (uint)sizeof(_SECURITY_ATTRIBUTES);
  135. fixed (uint* pprocessStartupInfo = processStartupInfo)
  136. fixed (uint* pprocessInfo = processInfo)
  137. outProcess =
  138. debugger.CorDebug.CreateProcess(
  139. filename, // lpApplicationName
  140. // If we do not prepend " ", the first argument migh just get lost
  141. " " + arguments, // lpCommandLine
  142. ref secAttr, // lpProcessAttributes
  143. ref secAttr, // lpThreadAttributes
  144. 1,//TRUE // bInheritHandles
  145. 0x00000010 /*CREATE_NEW_CONSOLE*/, // dwCreationFlags
  146. IntPtr.Zero, // lpEnvironment
  147. workingDirectory, // lpCurrentDirectory
  148. (uint)pprocessStartupInfo, // lpStartupInfo
  149. (uint)pprocessInfo, // lpProcessInformation,
  150. CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS // debuggingFlags
  151. );
  152. return new Process(debugger, outProcess, workingDirectory);
  153. }
  154. /// <summary> Fired when System.Diagnostics.Trace.WriteLine() is called in debuged process </summary>
  155. public event EventHandler<MessageEventArgs> LogMessage;
  156. protected internal virtual void OnLogMessage(MessageEventArgs arg)
  157. {
  158. TraceMessage ("Debugger event: OnLogMessage");
  159. if (LogMessage != null) {
  160. LogMessage(this, arg);
  161. }
  162. }
  163. public void TraceMessage(string message, params object[] args)
  164. {
  165. if (args.Length > 0)
  166. message = string.Format(message, args);
  167. System.Diagnostics.Debug.WriteLine("Debugger:" + message);
  168. debugger.OnDebuggerTraceMessage(new MessageEventArgs(this, message));
  169. }
  170. /// <summary> Read the specified amount of memory at the given memory address </summary>
  171. /// <returns> The content of the memory. The amount of the read memory may be less then requested. </returns>
  172. public unsafe byte[] ReadMemory(ulong address, int size)
  173. {
  174. byte[] buffer = new byte[size];
  175. int readCount;
  176. fixed(byte* pBuffer = buffer) {
  177. readCount = (int)corProcess.ReadMemory(address, (uint)size, new IntPtr(pBuffer));
  178. }
  179. if (readCount != size) Array.Resize(ref buffer, readCount);
  180. return buffer;
  181. }
  182. /// <summary> Writes the given buffer at the specified memory address </summary>
  183. /// <returns> The number of bytes written </returns>
  184. public unsafe int WriteMemory(ulong address, byte[] buffer)
  185. {
  186. if (buffer.Length == 0) return 0;
  187. int written;
  188. fixed(byte* pBuffer = buffer) {
  189. written = (int)corProcess.WriteMemory(address, (uint)buffer.Length, new IntPtr(pBuffer));
  190. }
  191. return written;
  192. }
  193. #region Exceptions
  194. bool pauseOnHandledException = false;
  195. public event EventHandler<ExceptionEventArgs> ExceptionThrown;
  196. public bool PauseOnHandledException {
  197. get { return pauseOnHandledException; }
  198. set { pauseOnHandledException = value; }
  199. }
  200. protected internal virtual void OnExceptionThrown(ExceptionEventArgs e)
  201. {
  202. TraceMessage ("Debugger event: OnExceptionThrown()");
  203. if (ExceptionThrown != null) {
  204. ExceptionThrown(this, e);
  205. }
  206. }
  207. #endregion
  208. // State control for the process
  209. internal bool TerminateCommandIssued = false;
  210. internal Queue<Breakpoint> BreakpointHitEventQueue = new Queue<Breakpoint>();
  211. internal Dictionary<AstNode, TypedValue> ExpressionsCache = new Dictionary<AstNode, TypedValue>();
  212. #region Events
  213. public event EventHandler<ProcessEventArgs> Paused;
  214. public event EventHandler<ProcessEventArgs> Resumed;
  215. // HACK: public
  216. public virtual void OnPaused()
  217. {
  218. AssertPaused();
  219. // No real purpose - just additional check
  220. if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback.");
  221. TraceMessage ("Debugger event: OnPaused()");
  222. if (Paused != null) {
  223. foreach(Delegate d in Paused.GetInvocationList()) {
  224. if (IsRunning) {
  225. TraceMessage ("Skipping OnPaused delegate because process has resumed");
  226. break;
  227. }
  228. if (this.TerminateCommandIssued || this.HasExited) {
  229. TraceMessage ("Skipping OnPaused delegate because process has exited");
  230. break;
  231. }
  232. d.DynamicInvoke(this, new ProcessEventArgs(this));
  233. }
  234. }
  235. }
  236. protected virtual void OnResumed()
  237. {
  238. AssertRunning();
  239. if (callbackInterface.IsInCallback)
  240. throw new DebuggerException("Can not raise event within callback.");
  241. TraceMessage ("Debugger event: OnResumed()");
  242. if (Resumed != null) {
  243. Resumed(this, new ProcessEventArgs(this));
  244. }
  245. }
  246. #endregion
  247. #region PauseSession & DebugeeState
  248. PauseSession pauseSession;
  249. DebuggeeState debuggeeState;
  250. /// <summary>
  251. /// Identification of the current debugger session. This value changes whenever debugger is continued
  252. /// </summary>
  253. public PauseSession PauseSession {
  254. get { return pauseSession; }
  255. }
  256. /// <summary>
  257. /// Identification of the state of the debugee. This value changes whenever the state of the debugee significantly changes
  258. /// </summary>
  259. public DebuggeeState DebuggeeState {
  260. get { return debuggeeState; }
  261. }
  262. /// <summary> Puts the process into a paused state </summary>
  263. internal void NotifyPaused(PausedReason pauseReason)
  264. {
  265. AssertRunning();
  266. pauseSession = new PauseSession(this, pauseReason);
  267. if (debuggeeState == null) {
  268. debuggeeState = new DebuggeeState(this);
  269. }
  270. }
  271. /// <summary> Puts the process into a resumed state </summary>
  272. internal void NotifyResumed(DebuggeeStateAction action)
  273. {
  274. AssertPaused();
  275. pauseSession = null;
  276. if (action == DebuggeeStateAction.Clear) {
  277. if (debuggeeState == null) throw new DebuggerException("Debugee state already cleared");
  278. debuggeeState = null;
  279. this.ExpressionsCache.Clear();
  280. }
  281. }
  282. /// <summary> Sets up the environment and raises user events </summary>
  283. internal void RaisePausedEvents()
  284. {
  285. AssertPaused();
  286. DisableAllSteppers();
  287. CheckSelectedStackFrames();
  288. SelectMostRecentStackFrameWithLoadedSymbols();
  289. if (this.PauseSession.PausedReason == PausedReason.Exception) {
  290. ExceptionEventArgs args = new ExceptionEventArgs(this, this.SelectedThread.CurrentException, this.SelectedThread.CurrentExceptionType, this.SelectedThread.CurrentExceptionIsUnhandled);
  291. OnExceptionThrown(args);
  292. // The event could have resumed or killed the process
  293. if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return;
  294. }
  295. while(BreakpointHitEventQueue.Count > 0) {
  296. Breakpoint breakpoint = BreakpointHitEventQueue.Dequeue();
  297. breakpoint.NotifyHit();
  298. // The event could have resumed or killed the process
  299. if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return;
  300. }
  301. OnPaused();
  302. // The event could have resumed the process
  303. if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return;
  304. }
  305. #endregion
  306. internal void AssertPaused()
  307. {
  308. if (IsRunning) {
  309. throw new DebuggerException("Process is not paused.");
  310. }
  311. }
  312. internal void AssertRunning()
  313. {
  314. if (IsPaused) {
  315. throw new DebuggerException("Process is not running.");
  316. }
  317. }
  318. public bool IsRunning {
  319. get { return pauseSession == null; }
  320. }
  321. public bool IsPaused {
  322. get { return !IsRunning; }
  323. }
  324. bool hasExited = false;
  325. public event EventHandler Exited;
  326. public bool HasExited {
  327. get {
  328. return hasExited;
  329. }
  330. }
  331. internal void NotifyHasExited()
  332. {
  333. if(!hasExited) {
  334. hasExited = true;
  335. if (Exited != null) {
  336. Exited(this, new ProcessEventArgs(this));
  337. }
  338. // Expire pause seesion first
  339. if (IsPaused) {
  340. NotifyResumed(DebuggeeStateAction.Clear);
  341. }
  342. debugger.Processes.Remove(this);
  343. }
  344. }
  345. public void Break()
  346. {
  347. AssertRunning();
  348. corProcess.Stop(uint.MaxValue); // Infinite; ignored anyway
  349. NotifyPaused(PausedReason.ForcedBreak);
  350. RaisePausedEvents();
  351. }
  352. public void Detach()
  353. {
  354. if (IsRunning) {
  355. corProcess.Stop(uint.MaxValue);
  356. NotifyPaused(PausedReason.ForcedBreak);
  357. }
  358. // This is necessary for detach
  359. foreach(Stepper s in this.Steppers) {
  360. if (s.CorStepper.IsActive() == 1) {
  361. s.CorStepper.Deactivate();
  362. }
  363. }
  364. this.Steppers.Clear();
  365. corProcess.Detach();
  366. // modules
  367. foreach(Module m in this.Modules)
  368. {
  369. m.Dispose();
  370. }
  371. this.modules.Clear();
  372. // threads
  373. this.threads.Clear();
  374. NotifyHasExited();
  375. }
  376. public void Continue()
  377. {
  378. AsyncContinue();
  379. WaitForPause();
  380. }
  381. internal Thread[] UnsuspendedThreads {
  382. get {
  383. List<Thread> unsuspendedThreads = new List<Thread>(this.Threads.Count);
  384. foreach(Thread t in this.Threads) {
  385. if (!t.Suspended)
  386. unsuspendedThreads.Add(t);
  387. }
  388. return unsuspendedThreads.ToArray();
  389. }
  390. }
  391. /// <summary>
  392. /// Resume execution and run all threads not marked by the user as susspended.
  393. /// </summary>
  394. public void AsyncContinue()
  395. {
  396. AsyncContinue(DebuggeeStateAction.Clear, this.UnsuspendedThreads, CorDebugThreadState.THREAD_RUN);
  397. }
  398. internal CorDebugThreadState NewThreadState = CorDebugThreadState.THREAD_RUN;
  399. /// <param name="threadsToRun"> Null to keep current setting </param>
  400. /// <param name="newThreadState"> What happens to created threads. Null to keep current setting </param>
  401. internal void AsyncContinue(DebuggeeStateAction action, Thread[] threadsToRun, CorDebugThreadState? newThreadState)
  402. {
  403. AssertPaused();
  404. if (threadsToRun != null) {
  405. // corProcess.SetAllThreadsDebugState(CorDebugThreadState.THREAD_SUSPEND, null);
  406. // Note: There is unreported thread, stopping it prevents the debugee from exiting
  407. // It is not corProcess.GetHelperThreadID
  408. // ICorDebugThread[] ts = new ICorDebugThread[corProcess.EnumerateThreads().GetCount()];
  409. // corProcess.EnumerateThreads().Next((uint)ts.Length, ts);
  410. foreach(Thread t in this.Threads) {
  411. CorDebugThreadState state = Array.IndexOf(threadsToRun, t) == -1 ? CorDebugThreadState.THREAD_SUSPEND : CorDebugThreadState.THREAD_RUN;
  412. try {
  413. t.CorThread.SetDebugState(state);
  414. } catch (COMException e) {
  415. // The state of the thread is invalid. (Exception from HRESULT: 0x8013132D)
  416. // It can happen for example when thread has not started yet
  417. if ((uint)e.ErrorCode == 0x8013132D) {
  418. // TraceMessage("Can not suspend thread - The state of the thread is invalid. Thread ID = " + t.CorThread.GetID());
  419. } else {
  420. throw;
  421. }
  422. }
  423. }
  424. }
  425. if (newThreadState != null) {
  426. this.NewThreadState = newThreadState.Value;
  427. }
  428. NotifyResumed(action);
  429. corProcess.Continue(0);
  430. if (this.Options.Verbose) {
  431. this.TraceMessage("Continue");
  432. }
  433. if (action == DebuggeeStateAction.Clear) {
  434. OnResumed();
  435. }
  436. }
  437. /// <summary> Terminates the execution of the process </summary>
  438. public void Terminate()
  439. {
  440. AsyncTerminate();
  441. // Wait until ExitProcess callback is received
  442. WaitForExit();
  443. }
  444. /// <summary> Terminates the execution of the process </summary>
  445. public void AsyncTerminate()
  446. {
  447. // Resume stoped tread
  448. if (this.IsPaused) {
  449. // We might get more callbacks so we should maintain consistent sate
  450. //AsyncContinue(); // Continue the process to get remaining callbacks
  451. }
  452. // Expose race condition - drain callback queue
  453. System.Threading.Thread.Sleep(0);
  454. // Stop&terminate - both must be called
  455. corProcess.Stop(uint.MaxValue);
  456. corProcess.Terminate(0);
  457. this.TerminateCommandIssued = true;
  458. // Do not mark the process as exited
  459. // This is done once ExitProcess callback is received
  460. }
  461. void SelectSomeThread()
  462. {
  463. if (this.SelectedThread != null && !this.SelectedThread.IsInValidState) {
  464. this.SelectedThread = null;
  465. }
  466. if (this.SelectedThread == null) {
  467. foreach(Thread thread in this.Threads) {
  468. if (thread.IsInValidState) {
  469. this.SelectedThread = thread;
  470. break;
  471. }
  472. }
  473. }
  474. }
  475. internal void CheckSelectedStackFrames()
  476. {
  477. foreach(Thread thread in this.Threads) {
  478. if (thread.IsInValidState) {
  479. if (thread.SelectedStackFrame != null && thread.SelectedStackFrame.IsInvalid) {
  480. thread.SelectedStackFrame = null;
  481. }
  482. } else {
  483. thread.SelectedStackFrame = null;
  484. }
  485. }
  486. }
  487. internal void SelectMostRecentStackFrameWithLoadedSymbols()
  488. {
  489. SelectSomeThread();
  490. if (this.SelectedThread != null) {
  491. this.SelectedThread.SelectedStackFrame = null;
  492. foreach (StackFrame stackFrame in this.SelectedThread.Callstack) {
  493. if (stackFrame.HasSymbols) {
  494. if (this.Options.StepOverDebuggerAttributes && stackFrame.MethodInfo.IsNonUserCode)
  495. continue;
  496. this.SelectedThread.SelectedStackFrame = stackFrame;
  497. break;
  498. }
  499. }
  500. }
  501. }
  502. internal Stepper GetStepper(ICorDebugStepper corStepper)
  503. {
  504. foreach(Stepper stepper in this.Steppers) {
  505. if (stepper.IsCorStepper(corStepper)) {
  506. return stepper;
  507. }
  508. }
  509. throw new DebuggerException("Stepper is not in collection");
  510. }
  511. internal void DisableAllSteppers()
  512. {
  513. foreach(Thread thread in this.Threads) {
  514. thread.CurrentStepIn = null;
  515. }
  516. foreach(Stepper stepper in this.Steppers) {
  517. stepper.Ignore = true;
  518. }
  519. }
  520. /// <summary>
  521. /// Waits until the debugger pauses unless it is already paused.
  522. /// Use PausedReason to find out why it paused.
  523. /// </summary>
  524. public void WaitForPause()
  525. {
  526. while(this.IsRunning && !this.HasExited) {
  527. debugger.MTA2STA.WaitForCall();
  528. debugger.MTA2STA.PerformAllCalls();
  529. }
  530. if (this.HasExited) throw new ProcessExitedException();
  531. }
  532. public void WaitForPause(TimeSpan timeout)
  533. {
  534. System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
  535. watch.Start();
  536. while(this.IsRunning && !this.HasExited) {
  537. TimeSpan timeLeft = timeout - watch.Elapsed;
  538. if (timeLeft <= TimeSpan.FromMilliseconds(10)) break;
  539. //this.TraceMessage("Time left: " + timeLeft.TotalMilliseconds);
  540. debugger.MTA2STA.WaitForCall(timeLeft);
  541. debugger.MTA2STA.PerformAllCalls();
  542. }
  543. if (this.HasExited) throw new ProcessExitedException();
  544. }
  545. /// <summary>
  546. /// Waits until the precesses exits.
  547. /// </summary>
  548. public void WaitForExit()
  549. {
  550. while(!this.HasExited) {
  551. debugger.MTA2STA.WaitForCall();
  552. debugger.MTA2STA.PerformAllCalls();
  553. }
  554. }
  555. #region Break at begining
  556. private void OnModulesAdded(object sender, CollectionItemEventArgs<Module> e)
  557. {
  558. if (BreakAtBeginning) {
  559. if (e.Item.SymReader == null) return; // No symbols
  560. try {
  561. // create a BP at entry point
  562. uint entryPoint = e.Item.SymReader.GetUserEntryPoint();
  563. if (entryPoint == 0) return; // no EP
  564. var mainFunction = e.Item.CorModule.GetFunctionFromToken(entryPoint);
  565. var corBreakpoint = mainFunction.CreateBreakpoint();
  566. corBreakpoint.Activate(1);
  567. // create a SD BP
  568. var breakpoint = new Breakpoint(this.debugger, corBreakpoint);
  569. this.debugger.Breakpoints.Add(breakpoint);
  570. breakpoint.Hit += delegate {
  571. if (breakpoint != null)
  572. breakpoint.Remove();
  573. breakpoint = null;
  574. };
  575. } catch {
  576. // the app does not have an entry point - COM exception
  577. }
  578. BreakAtBeginning = false;
  579. }
  580. if (ModulesAdded != null)
  581. ModulesAdded(this, new ModuleEventArgs(e.Item));
  582. }
  583. #endregion
  584. public event EventHandler<ModuleEventArgs> ModulesAdded;
  585. }
  586. }