PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/source2/Debug/Cosmos.Debug.VSDebugEngine/AD7.Impl/AD7Process.cs

https://bitbucket.org/mvptracker/cosmos
C# | 524 lines | 381 code | 78 blank | 65 comment | 66 complexity | cdc523f9fb142a3ee4f9122817c10183 MD5 | raw file
Possible License(s): BSD-2-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.IO.Pipes;
  8. using System.Linq;
  9. using System.Runtime.InteropServices;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Windows.Forms;
  13. using Cosmos.Build.Common;
  14. using Cosmos.Debug.Common;
  15. using Microsoft.VisualStudio;
  16. using Microsoft.VisualStudio.Debugger.Interop;
  17. using Microsoft.Win32;
  18. namespace Cosmos.Debug.VSDebugEngine {
  19. public class AD7Process : IDebugProcess2 {
  20. public Guid ID = Guid.NewGuid();
  21. protected EngineCallback mCallback;
  22. public AD7Thread mThread;
  23. protected AD7Engine mEngine;
  24. public UInt32? mCurrentAddress = null;
  25. protected readonly NameValueCollection mDebugInfo;
  26. protected LaunchType mLaunch;
  27. internal DebugInfo mDebugInfoDb;
  28. protected int mProcessExitEventSent = 0;
  29. // Cached stack frame. See comments in AD7Thread regading this.
  30. public IEnumDebugFrameInfo2 mStackFrame;
  31. // Connection to target environment. Usually serial but is
  32. // abstracted to allow other transports (ethernet, etc)
  33. public DebugConnector mDbgConnector;
  34. //
  35. // These are static because we need them persistent between debug
  36. // sessions to avoid reconnection issues. But they are not created
  37. // until the debug session is ready the first time so that we know
  38. // the debug window pipes are already ready.
  39. //
  40. // Pipe for writing responses to communicate with Cosmos.VS.Windows
  41. static private Cosmos.Debug.Common.PipeClient mDebugDownPipe = null;
  42. // Pipe to receive messages from Cosmos.VS.Windows
  43. static private Cosmos.Debug.Common.PipeServer mDebugUpPipe = null;
  44. Host.Base mHost;
  45. public string mISO;
  46. public string mProjectFile;
  47. protected void DbgCmdRegisters(byte[] aData) {
  48. mDebugDownPipe.SendCommand(Debugger2Windows.Registers, aData);
  49. }
  50. protected void DbgCmdFrame(byte[] aData) {
  51. mDebugDownPipe.SendCommand(Debugger2Windows.Frame, aData);
  52. }
  53. protected void DbgCmdPong(byte[] aData) {
  54. mDebugDownPipe.SendCommand(Debugger2Windows.PongDebugStub, aData);
  55. }
  56. protected void DbgCmdStack(byte[] aData) {
  57. mDebugDownPipe.SendCommand(Debugger2Windows.Stack, aData);
  58. }
  59. private void mDebugUpPipe_DataPacketReceived(byte aCmd, byte[] aData) {
  60. switch (aCmd) {
  61. case Windows2Debugger.Noop:
  62. // do nothing
  63. break;
  64. case Windows2Debugger.PingVSIP:
  65. mDebugDownPipe.SendCommand(Debugger2Windows.PongVSIP);
  66. break;
  67. case Windows2Debugger.PingDebugStub:
  68. mDbgConnector.Ping();
  69. break;
  70. case Windows2Debugger.SetAsmBreak:
  71. string xLabel = Encoding.UTF8.GetString(aData);
  72. UInt32 xAddress = mDebugInfoDb.AddressOfLabel(xLabel);
  73. mDbgConnector.SetAsmBreakpoint(xAddress);
  74. mDbgConnector.Continue();
  75. //mDebugDownPipe.SendCommand(VsipUi.OutputPane, xAddress.ToString());
  76. break;
  77. default:
  78. throw new Exception(String.Format("Command value '{0}' not supported in method AD7Process.mDebugUpPipe_DataPacketReceived.", aCmd));
  79. }
  80. }
  81. private void DebugConnectorConnected() {
  82. OutputText("Connected to DebugStub.");
  83. }
  84. /// <summary>Instanciate the <see cref="DebugConnector"/> that will handle communications
  85. /// between this debug engine hosted process and the emulation environment used to run the
  86. /// debugged Cosmos kernel. Actual connector to be instanciated is discovered from Cosmos
  87. /// project properties.</summary>
  88. private void CreateDebugConnector() {
  89. mDbgConnector = null;
  90. string xPort = mDebugInfo[BuildProperties.VisualStudioDebugPortString];
  91. var xParts = (null == xPort) ? null : xPort.Split(' ');
  92. if ((null == xParts) || (2 > xParts.Length)) {
  93. throw new Exception(string.Format(
  94. "The '{0}' Cosmos project file property is either ill-formed or missing.",
  95. BuildProperties.VisualStudioDebugPortString));
  96. }
  97. string xPortType = xParts[0].ToLower();
  98. string xPortParam = xParts[1].ToLower();
  99. OutputText("Starting debug connector.");
  100. if (xPortType == "pipe:") {
  101. mDbgConnector = new Cosmos.Debug.Common.DebugConnectorPipeServer(xPortParam);
  102. } else if (xPortType == "serial:") {
  103. mDbgConnector = new Cosmos.Debug.Common.DebugConnectorSerial(xPortParam);
  104. }
  105. if (mDbgConnector == null) {
  106. throw new Exception("No debug connector found.");
  107. }
  108. mDbgConnector.SetConnectionHandler(DebugConnectorConnected);
  109. mDbgConnector.CmdBreak += new Action<UInt32>(DbgCmdBreak);
  110. mDbgConnector.CmdTrace += new Action<UInt32>(DbgCmdTrace);
  111. mDbgConnector.CmdText += new Action<string>(DbgCmdText);
  112. mDbgConnector.CmdStarted += new Action(DbgCmdStarted);
  113. mDbgConnector.OnDebugMsg += new Action<string>(DebugMsg);
  114. mDbgConnector.ConnectionLost += new Action<Exception>(DbgConnector_ConnectionLost);
  115. mDbgConnector.CmdRegisters += new Action<byte[]>(DbgCmdRegisters);
  116. mDbgConnector.CmdFrame += new Action<byte[]>(DbgCmdFrame);
  117. mDbgConnector.CmdStack += new Action<byte[]>(DbgCmdStack);
  118. mDbgConnector.CmdPong += new Action<byte[]>(DbgCmdPong);
  119. }
  120. internal AD7Process(NameValueCollection aDebugInfo, EngineCallback aCallback, AD7Engine aEngine, IDebugPort2 aPort) {
  121. mCallback = aCallback;
  122. mDebugInfo = aDebugInfo;
  123. mLaunch = (LaunchType)Enum.Parse(typeof(LaunchType), aDebugInfo[BuildProperties.LaunchString]);
  124. if (mDebugDownPipe == null) {
  125. mDebugDownPipe = new Cosmos.Debug.Common.PipeClient(Pipes.DownName);
  126. mDebugUpPipe = new Cosmos.Debug.Common.PipeServer(Pipes.UpName);
  127. mDebugUpPipe.DataPacketReceived += new Action<byte, byte[]>(mDebugUpPipe_DataPacketReceived);
  128. mDebugUpPipe.Start();
  129. }
  130. // Must be after mDebugDownPipe is initialized
  131. OutputClear();
  132. OutputText("Debugger process initialized.");
  133. mISO = mDebugInfo["ISOFile"];
  134. OutputText("Using ISO file " + mISO + ".");
  135. mProjectFile = mDebugInfo["ProjectFile"];
  136. //
  137. bool xUseGDB = string.Equals(mDebugInfo[BuildProperties.EnableGDBString], "true", StringComparison.InvariantCultureIgnoreCase);
  138. OutputText("GDB " + (xUseGDB ? "Enabled" : "Disabled") + ".");
  139. //
  140. var xGDBClient = false;
  141. Boolean.TryParse(mDebugInfo[BuildProperties.StartCosmosGDBString], out xGDBClient);
  142. switch (mLaunch)
  143. {
  144. case LaunchType.VMware:
  145. mHost = new Host.VMware(mDebugInfo, xUseGDB);
  146. break;
  147. case LaunchType.Slave:
  148. mHost = new Host.Slave(mDebugInfo, xUseGDB);
  149. break;
  150. case LaunchType.Bochs:
  151. // The project has been created on another machine or Bochs has been uninstalled since the project has
  152. // been created.
  153. if (!BochsSupport.BochsEnabled) { throw new Exception(ResourceStrings.BochsIsNotInstalled); }
  154. string bochsConfigurationFileName = mDebugInfo[BuildProperties.BochsEmulatorConfigurationFileString];
  155. if (string.IsNullOrEmpty(bochsConfigurationFileName)) {
  156. bochsConfigurationFileName = BuildProperties.BochsDefaultConfigurationFileName;
  157. }
  158. if (!Path.IsPathRooted(bochsConfigurationFileName)) {
  159. // Assume the configuration file name is relative to project output path.
  160. bochsConfigurationFileName = Path.Combine(new FileInfo(mDebugInfo["ProjectFile"]).Directory.FullName,
  161. mDebugInfo["OutputPath"], bochsConfigurationFileName);
  162. }
  163. FileInfo bochsConfigurationFile = new FileInfo(bochsConfigurationFileName);
  164. // TODO : What if the configuration file doesn't exist ? This will throw a FileNotFoundException in
  165. // the Bochs class constructor. Is this appropriate behavior ?
  166. mHost = new Host.Bochs(mDebugInfo, xUseGDB, bochsConfigurationFile);
  167. ((Host.Bochs)mHost).FixBochsConfiguration(new KeyValuePair<string, string>[]
  168. { new KeyValuePair<string, string>("IsoFileName", mISO) }
  169. );
  170. break;
  171. default:
  172. throw new Exception("Invalid Launch value: '" + mLaunch + "'.");
  173. }
  174. mHost.OnShutDown += HostShutdown;
  175. string xDbPath = Path.ChangeExtension(mISO, "mdf");
  176. if (!File.Exists(xDbPath)) {
  177. throw new Exception("Debug data file " + xDbPath + " not found. Could be a omitted build process of Cosmos project so that not created.");
  178. }
  179. mDebugInfoDb = new DebugInfo(xDbPath);
  180. mDebugInfoDb.LoadLookups();
  181. CreateDebugConnector();
  182. aEngine.BPMgr.SetDebugConnector(mDbgConnector);
  183. mEngine = aEngine;
  184. mThread = new AD7Thread(aEngine, this);
  185. mCallback.OnThreadStart(mThread);
  186. mPort = aPort;
  187. if (xUseGDB && xGDBClient) {
  188. LaunchGdbClient();
  189. }
  190. }
  191. protected void LaunchGdbClient() {
  192. OutputText("Launching GDB client.");
  193. if (File.Exists(Cosmos.Build.Common.CosmosPaths.GdbClientExe)) {
  194. var xPSInfo = new ProcessStartInfo(Cosmos.Build.Common.CosmosPaths.GdbClientExe);
  195. xPSInfo.Arguments = "\"" + Path.ChangeExtension(mProjectFile, ".cgdb") + "\"" + @" /Connect";
  196. xPSInfo.UseShellExecute = false;
  197. xPSInfo.RedirectStandardInput = false;
  198. xPSInfo.RedirectStandardError = false;
  199. xPSInfo.RedirectStandardOutput = false;
  200. xPSInfo.CreateNoWindow = false;
  201. Process.Start(xPSInfo);
  202. } else {
  203. MessageBox.Show(string.Format(
  204. "The GDB-Client could not be found at \"{0}\". Please deactivate it under \"Properties/Debug/Enable GDB\"",
  205. Cosmos.Build.Common.CosmosPaths.GdbClientExe), "GDB-Client", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
  206. }
  207. }
  208. private void DbgConnector_ConnectionLost(Exception e) {
  209. if (Interlocked.CompareExchange(ref mProcessExitEventSent, 1, 0) == 1) {
  210. return;
  211. }
  212. if (mDbgConnector != null) {
  213. mEngine.Callback.OnProcessExit(0);
  214. }
  215. }
  216. // Shows a message in the output window of VS. Needs special treatment,
  217. // because normally VS only shows msgs from debugged process, not internal
  218. // stuff like us.
  219. public void DebugMsg(string aMsg) {
  220. mCallback.OnOutputString(aMsg + "\n");
  221. }
  222. protected void DbgCmdStarted() {
  223. OutputText("DebugStub handshake completed.");
  224. DebugMsg("RmtDbg: Started");
  225. // OK, now debugger is ready. Send it a list of breakpoints that were set before
  226. // program run.
  227. foreach (var xBP in mEngine.BPMgr.mPendingBPs) {
  228. foreach (var xBBP in xBP.mBoundBPs) {
  229. mDbgConnector.SetBreakpoint(xBBP.RemoteID, xBBP.mAddress);
  230. }
  231. }
  232. mDbgConnector.SendCmd(Vs2Ds.BatchEnd);
  233. }
  234. void DbgCmdText(string obj) {
  235. mCallback.OnOutputStringUser(obj + "\r\n");
  236. }
  237. internal AD7Thread Thread {
  238. get {
  239. return mThread;
  240. }
  241. }
  242. void DbgCmdTrace(UInt32 aAddress) {
  243. DebugMsg("TraceReceived: " + aAddress);
  244. }
  245. void DbgCmdBreak(UInt32 aAddress) {
  246. // aAddress will be actual address. Call and other methods push return to (after op), but DS
  247. // corrects for us and sends us actual op address.
  248. DebugMsg("DbgCmdBreak " + aAddress + " / " + aAddress.ToString("X8").ToUpper());
  249. var xActionPoints = new List<object>();
  250. var xBoundBreakpoints = new List<IDebugBoundBreakpoint2>();
  251. // Search the BPs and find ones that match our address.
  252. foreach (var xBP in mEngine.BPMgr.mPendingBPs) {
  253. foreach (var xBBP in xBP.mBoundBPs) {
  254. if (xBBP.mAddress == aAddress) {
  255. xBoundBreakpoints.Add(xBBP);
  256. }
  257. }
  258. }
  259. mStackFrame = null;
  260. mCurrentAddress = aAddress;
  261. if (xBoundBreakpoints.Count == 0) {
  262. // if no matching breakpoints are found then its one of the following:
  263. // - Stepping operation
  264. // - Code based break
  265. // - Asm stepping
  266. if (mStepping) {
  267. mCallback.OnStepComplete();
  268. mStepping = false;
  269. } else {
  270. mCallback.OnBreakpoint(mThread, new List<IDebugBoundBreakpoint2>());
  271. }
  272. } else {
  273. // Found a bound breakpoint
  274. mCallback.OnBreakpoint(mThread, xBoundBreakpoints.AsReadOnly());
  275. }
  276. RequestFullDebugStubUpdate();
  277. }
  278. protected void RequestFullDebugStubUpdate() {
  279. // We catch and resend data rather than using a second serial port because
  280. // while this would work fine in a VM, it would require 2 serial ports
  281. // when real hardware is used.
  282. SendAssembly();
  283. mDbgConnector.SendRegisters();
  284. mDbgConnector.SendFrame();
  285. mDbgConnector.SendStack();
  286. }
  287. public int Attach(IDebugEventCallback2 pCallback, Guid[] rgguidSpecificEngines, uint celtSpecificEngines, int[] rghrEngineAttach) {
  288. Trace.WriteLine(new StackTrace(false).GetFrame(0).GetMethod().GetFullName());
  289. throw new NotImplementedException();
  290. }
  291. public int CanDetach() {
  292. throw new NotImplementedException();
  293. }
  294. public int CauseBreak() {
  295. throw new NotImplementedException();
  296. }
  297. public int Detach() {
  298. throw new NotImplementedException();
  299. }
  300. public int EnumPrograms(out IEnumDebugPrograms2 ppEnum) {
  301. throw new NotImplementedException();
  302. }
  303. public int EnumThreads(out IEnumDebugThreads2 ppEnum) {
  304. var xEnum = new AD7ThreadEnum(new IDebugThread2[] { mThread });
  305. ppEnum = xEnum;
  306. return VSConstants.S_OK;
  307. }
  308. public int GetAttachedSessionName(out string pbstrSessionName) {
  309. throw new NotImplementedException();
  310. }
  311. public int GetInfo(enum_PROCESS_INFO_FIELDS Fields, PROCESS_INFO[] pProcessInfo) {
  312. throw new NotImplementedException();
  313. }
  314. public int GetName(enum_GETNAME_TYPE gnType, out string pbstrName) {
  315. throw new NotImplementedException();
  316. }
  317. public readonly Guid PhysID = Guid.NewGuid();
  318. public int GetPhysicalProcessId(AD_PROCESS_ID[] pProcessId) {
  319. // http://blogs.msdn.com/b/jacdavis/archive/2008/05/01/what-to-do-if-your-debug-engine-doesn-t-create-real-processes.aspx
  320. // http://social.msdn.microsoft.com/Forums/en/vsx/thread/fe809686-e5f9-439d-9e52-00017e12300f
  321. pProcessId[0].guidProcessId = PhysID;
  322. pProcessId[0].ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID;
  323. return VSConstants.S_OK;
  324. }
  325. private IDebugPort2 mPort = null;
  326. public int GetPort(out IDebugPort2 ppPort) {
  327. if (mPort == null) {
  328. throw new Exception("Error");
  329. }
  330. ppPort = mPort;
  331. return VSConstants.S_OK;
  332. }
  333. public int GetProcessId(out Guid pguidProcessId) {
  334. pguidProcessId = ID;
  335. return VSConstants.S_OK;
  336. }
  337. public int GetServer(out IDebugCoreServer2 ppServer) {
  338. throw new NotImplementedException();
  339. }
  340. public int Terminate() {
  341. OutputText("Debugger terminating.");
  342. mHost.Stop();
  343. OutputText("Debugger terminated.");
  344. return VSConstants.S_OK;
  345. }
  346. internal void ResumeFromLaunch() {
  347. mHost.Start();
  348. }
  349. void HostShutdown(object sender, EventArgs e) {
  350. //AD7ThreadDestroyEvent.Send(mEngine, mThread, (uint)mProcess.ExitCode);
  351. //mCallback.OnProgramDestroy((uint)mProcess.ExitCode);
  352. // We dont use process info any more, but have to call this to tell
  353. // VS to stop debugging.
  354. if (Interlocked.CompareExchange(ref mProcessExitEventSent, 1, 0) == 0) {
  355. mCallback.OnProcessExit(0);
  356. }
  357. if (mDbgConnector != null) {
  358. mDbgConnector.Dispose();
  359. mDbgConnector = null;
  360. }
  361. if (mDebugInfoDb != null) {
  362. // Commented for debugging, so we can look at the DB after
  363. //mDebugInfoDb.DeleteDB();
  364. mDebugInfoDb.Dispose();
  365. mDebugInfoDb = null;
  366. }
  367. }
  368. internal void Continue() { // F5
  369. mCurrentAddress = null;
  370. mDbgConnector.Continue();
  371. }
  372. bool mStepping = false;
  373. internal void Step(enum_STEPKIND aKind) {
  374. if (aKind == enum_STEPKIND.STEP_INTO) { // F11
  375. mStepping = true;
  376. mDbgConnector.SendCmd(Vs2Ds.StepInto);
  377. } else if (aKind == enum_STEPKIND.STEP_OVER) { // F10
  378. mStepping = true;
  379. mDbgConnector.SendCmd(Vs2Ds.StepOver);
  380. } else if (aKind == enum_STEPKIND.STEP_OUT) { // Shift-F11
  381. mStepping = true;
  382. mDbgConnector.SendCmd(Vs2Ds.StepOut);
  383. } else if (aKind == enum_STEPKIND.STEP_BACKWARDS) {
  384. // STEP_BACKWARDS - Supported at all by VS?
  385. //
  386. // Possibly, by dragging the execution location up
  387. // or down through the source code? -Orvid
  388. MessageBox.Show("Step backwards is not supported.");
  389. mCallback.OnStepComplete(); // Have to call this otherwise VS gets "stuck"
  390. } else {
  391. MessageBox.Show("Unknown step type requested.");
  392. mCallback.OnStepComplete(); // Have to call this otherwise VS gets "stuck"
  393. }
  394. }
  395. public void SendAssembly() {
  396. UInt32 xAddress = mCurrentAddress.Value;
  397. var xSourceInfos = mDebugInfoDb.GetSourceInfos(xAddress);
  398. // Because of Asm breakpoints the address we have might be in the middle of a C# line.
  399. // So we find the closest address to ours that is less or equal to ours.
  400. var xQry = from x in xSourceInfos
  401. where x.Key <= xAddress
  402. orderby x.Key descending
  403. select x.Value;
  404. var xValue = xQry.FirstOrDefault();
  405. if (xValue == null) {
  406. return;
  407. }
  408. // Create list of asm labels that belong to this line of C#.
  409. var xMappings = from x in xSourceInfos
  410. where x.Value.SourceFile == xValue.SourceFile
  411. && x.Value.Line == xValue.Line
  412. && x.Value.Column == xValue.Column
  413. select x.Key;
  414. var xLabels = new List<string>();
  415. foreach (uint xAddr in xMappings) {
  416. foreach (string xLabel in mDebugInfoDb.GetLabels(xAddr)) {
  417. xLabels.Add(xLabel + ":");
  418. }
  419. }
  420. // Get assembly source
  421. var xCode = AsmSource.GetSourceForLabels(Path.ChangeExtension(mISO, ".asm"), xLabels);
  422. // Get label for current address.
  423. // A single address can have multiple labels (IL, Asm). Because of this we search
  424. // for the one with the Asm tag. We dont have the tags in this debug info though,
  425. // so instead if there is more than one label we use the longest one which is the Asm tag.
  426. var xCurrentLabels = mDebugInfoDb.GetLabels(mCurrentAddress.Value);
  427. if (xCurrentLabels.Length > 0) {
  428. string xCurrentLabel = xCurrentLabels.OrderBy(q => q.Length).Last();
  429. // Insert it to the first line of our data stream
  430. xCode.Insert(0, xCurrentLabel + "\r\n");
  431. mDebugDownPipe.SendCommand(Debugger2Windows.AssemblySource, Encoding.UTF8.GetBytes(xCode.ToString()));
  432. }
  433. }
  434. //TODO: At some point this will probably need to be exposed for access outside of AD7Process
  435. protected void OutputText(string aText) {
  436. // With Bochs this method may be invoked before the pipe is created.
  437. if (null == mDebugDownPipe) { return; }
  438. mDebugDownPipe.SendCommand(Debugger2Windows.OutputPane, Encoding.UTF8.GetBytes(aText + "\r\n"));
  439. }
  440. protected void OutputClear() {
  441. // With Bochs this method may be invoked before the pipe is created.
  442. if (null == mDebugDownPipe) { return; }
  443. mDebugDownPipe.SendCommand(Debugger2Windows.OutputClear);
  444. }
  445. }
  446. }