/source2/Debug/Cosmos.Debug.VSDebugEngine/AD7.Impl/AD7Process.cs
C# | 524 lines | 381 code | 78 blank | 65 comment | 66 complexity | cdc523f9fb142a3ee4f9122817c10183 MD5 | raw file
Possible License(s): BSD-2-Clause
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.Diagnostics;
- using System.IO;
- using System.IO.Pipes;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Windows.Forms;
- using Cosmos.Build.Common;
- using Cosmos.Debug.Common;
- using Microsoft.VisualStudio;
- using Microsoft.VisualStudio.Debugger.Interop;
- using Microsoft.Win32;
-
- namespace Cosmos.Debug.VSDebugEngine {
- public class AD7Process : IDebugProcess2 {
- public Guid ID = Guid.NewGuid();
- protected EngineCallback mCallback;
- public AD7Thread mThread;
- protected AD7Engine mEngine;
- public UInt32? mCurrentAddress = null;
- protected readonly NameValueCollection mDebugInfo;
- protected LaunchType mLaunch;
- internal DebugInfo mDebugInfoDb;
- protected int mProcessExitEventSent = 0;
- // Cached stack frame. See comments in AD7Thread regading this.
- public IEnumDebugFrameInfo2 mStackFrame;
-
- // Connection to target environment. Usually serial but is
- // abstracted to allow other transports (ethernet, etc)
- public DebugConnector mDbgConnector;
- //
- // These are static because we need them persistent between debug
- // sessions to avoid reconnection issues. But they are not created
- // until the debug session is ready the first time so that we know
- // the debug window pipes are already ready.
- //
- // Pipe for writing responses to communicate with Cosmos.VS.Windows
- static private Cosmos.Debug.Common.PipeClient mDebugDownPipe = null;
- // Pipe to receive messages from Cosmos.VS.Windows
- static private Cosmos.Debug.Common.PipeServer mDebugUpPipe = null;
-
- Host.Base mHost;
-
- public string mISO;
- public string mProjectFile;
-
- protected void DbgCmdRegisters(byte[] aData) {
- mDebugDownPipe.SendCommand(Debugger2Windows.Registers, aData);
- }
-
- protected void DbgCmdFrame(byte[] aData) {
- mDebugDownPipe.SendCommand(Debugger2Windows.Frame, aData);
- }
-
- protected void DbgCmdPong(byte[] aData) {
- mDebugDownPipe.SendCommand(Debugger2Windows.PongDebugStub, aData);
- }
-
- protected void DbgCmdStack(byte[] aData) {
- mDebugDownPipe.SendCommand(Debugger2Windows.Stack, aData);
- }
-
- private void mDebugUpPipe_DataPacketReceived(byte aCmd, byte[] aData) {
- switch (aCmd) {
- case Windows2Debugger.Noop:
- // do nothing
- break;
-
- case Windows2Debugger.PingVSIP:
- mDebugDownPipe.SendCommand(Debugger2Windows.PongVSIP);
- break;
-
- case Windows2Debugger.PingDebugStub:
- mDbgConnector.Ping();
- break;
-
- case Windows2Debugger.SetAsmBreak:
- string xLabel = Encoding.UTF8.GetString(aData);
- UInt32 xAddress = mDebugInfoDb.AddressOfLabel(xLabel);
- mDbgConnector.SetAsmBreakpoint(xAddress);
- mDbgConnector.Continue();
- //mDebugDownPipe.SendCommand(VsipUi.OutputPane, xAddress.ToString());
- break;
-
- default:
- throw new Exception(String.Format("Command value '{0}' not supported in method AD7Process.mDebugUpPipe_DataPacketReceived.", aCmd));
- }
- }
-
- private void DebugConnectorConnected() {
- OutputText("Connected to DebugStub.");
- }
-
- /// <summary>Instanciate the <see cref="DebugConnector"/> that will handle communications
- /// between this debug engine hosted process and the emulation environment used to run the
- /// debugged Cosmos kernel. Actual connector to be instanciated is discovered from Cosmos
- /// project properties.</summary>
- private void CreateDebugConnector() {
- mDbgConnector = null;
-
- string xPort = mDebugInfo[BuildProperties.VisualStudioDebugPortString];
- var xParts = (null == xPort) ? null : xPort.Split(' ');
- if ((null == xParts) || (2 > xParts.Length)) {
- throw new Exception(string.Format(
- "The '{0}' Cosmos project file property is either ill-formed or missing.",
- BuildProperties.VisualStudioDebugPortString));
- }
- string xPortType = xParts[0].ToLower();
- string xPortParam = xParts[1].ToLower();
-
- OutputText("Starting debug connector.");
- if (xPortType == "pipe:") {
- mDbgConnector = new Cosmos.Debug.Common.DebugConnectorPipeServer(xPortParam);
- } else if (xPortType == "serial:") {
- mDbgConnector = new Cosmos.Debug.Common.DebugConnectorSerial(xPortParam);
- }
-
- if (mDbgConnector == null) {
- throw new Exception("No debug connector found.");
- }
- mDbgConnector.SetConnectionHandler(DebugConnectorConnected);
- mDbgConnector.CmdBreak += new Action<UInt32>(DbgCmdBreak);
- mDbgConnector.CmdTrace += new Action<UInt32>(DbgCmdTrace);
- mDbgConnector.CmdText += new Action<string>(DbgCmdText);
- mDbgConnector.CmdStarted += new Action(DbgCmdStarted);
- mDbgConnector.OnDebugMsg += new Action<string>(DebugMsg);
- mDbgConnector.ConnectionLost += new Action<Exception>(DbgConnector_ConnectionLost);
- mDbgConnector.CmdRegisters += new Action<byte[]>(DbgCmdRegisters);
- mDbgConnector.CmdFrame += new Action<byte[]>(DbgCmdFrame);
- mDbgConnector.CmdStack += new Action<byte[]>(DbgCmdStack);
- mDbgConnector.CmdPong += new Action<byte[]>(DbgCmdPong);
- }
-
- internal AD7Process(NameValueCollection aDebugInfo, EngineCallback aCallback, AD7Engine aEngine, IDebugPort2 aPort) {
- mCallback = aCallback;
- mDebugInfo = aDebugInfo;
-
- mLaunch = (LaunchType)Enum.Parse(typeof(LaunchType), aDebugInfo[BuildProperties.LaunchString]);
-
- if (mDebugDownPipe == null) {
- mDebugDownPipe = new Cosmos.Debug.Common.PipeClient(Pipes.DownName);
-
- mDebugUpPipe = new Cosmos.Debug.Common.PipeServer(Pipes.UpName);
- mDebugUpPipe.DataPacketReceived += new Action<byte, byte[]>(mDebugUpPipe_DataPacketReceived);
- mDebugUpPipe.Start();
- }
-
- // Must be after mDebugDownPipe is initialized
- OutputClear();
- OutputText("Debugger process initialized.");
-
- mISO = mDebugInfo["ISOFile"];
- OutputText("Using ISO file " + mISO + ".");
- mProjectFile = mDebugInfo["ProjectFile"];
- //
- bool xUseGDB = string.Equals(mDebugInfo[BuildProperties.EnableGDBString], "true", StringComparison.InvariantCultureIgnoreCase);
- OutputText("GDB " + (xUseGDB ? "Enabled" : "Disabled") + ".");
- //
- var xGDBClient = false;
- Boolean.TryParse(mDebugInfo[BuildProperties.StartCosmosGDBString], out xGDBClient);
-
- switch (mLaunch)
- {
- case LaunchType.VMware:
- mHost = new Host.VMware(mDebugInfo, xUseGDB);
- break;
- case LaunchType.Slave:
- mHost = new Host.Slave(mDebugInfo, xUseGDB);
- break;
- case LaunchType.Bochs:
- // The project has been created on another machine or Bochs has been uninstalled since the project has
- // been created.
- if (!BochsSupport.BochsEnabled) { throw new Exception(ResourceStrings.BochsIsNotInstalled); }
- string bochsConfigurationFileName = mDebugInfo[BuildProperties.BochsEmulatorConfigurationFileString];
- if (string.IsNullOrEmpty(bochsConfigurationFileName)) {
- bochsConfigurationFileName = BuildProperties.BochsDefaultConfigurationFileName;
- }
- if (!Path.IsPathRooted(bochsConfigurationFileName)) {
- // Assume the configuration file name is relative to project output path.
- bochsConfigurationFileName = Path.Combine(new FileInfo(mDebugInfo["ProjectFile"]).Directory.FullName,
- mDebugInfo["OutputPath"], bochsConfigurationFileName);
- }
- FileInfo bochsConfigurationFile = new FileInfo(bochsConfigurationFileName);
- // TODO : What if the configuration file doesn't exist ? This will throw a FileNotFoundException in
- // the Bochs class constructor. Is this appropriate behavior ?
- mHost = new Host.Bochs(mDebugInfo, xUseGDB, bochsConfigurationFile);
- ((Host.Bochs)mHost).FixBochsConfiguration(new KeyValuePair<string, string>[]
- { new KeyValuePair<string, string>("IsoFileName", mISO) }
- );
- break;
- default:
- throw new Exception("Invalid Launch value: '" + mLaunch + "'.");
- }
- mHost.OnShutDown += HostShutdown;
-
- string xDbPath = Path.ChangeExtension(mISO, "mdf");
- if (!File.Exists(xDbPath)) {
- throw new Exception("Debug data file " + xDbPath + " not found. Could be a omitted build process of Cosmos project so that not created.");
- }
-
- mDebugInfoDb = new DebugInfo(xDbPath);
- mDebugInfoDb.LoadLookups();
-
- CreateDebugConnector();
- aEngine.BPMgr.SetDebugConnector(mDbgConnector);
-
- mEngine = aEngine;
- mThread = new AD7Thread(aEngine, this);
- mCallback.OnThreadStart(mThread);
- mPort = aPort;
-
- if (xUseGDB && xGDBClient) {
- LaunchGdbClient();
- }
- }
-
- protected void LaunchGdbClient() {
- OutputText("Launching GDB client.");
- if (File.Exists(Cosmos.Build.Common.CosmosPaths.GdbClientExe)) {
- var xPSInfo = new ProcessStartInfo(Cosmos.Build.Common.CosmosPaths.GdbClientExe);
- xPSInfo.Arguments = "\"" + Path.ChangeExtension(mProjectFile, ".cgdb") + "\"" + @" /Connect";
- xPSInfo.UseShellExecute = false;
- xPSInfo.RedirectStandardInput = false;
- xPSInfo.RedirectStandardError = false;
- xPSInfo.RedirectStandardOutput = false;
- xPSInfo.CreateNoWindow = false;
- Process.Start(xPSInfo);
- } else {
- MessageBox.Show(string.Format(
- "The GDB-Client could not be found at \"{0}\". Please deactivate it under \"Properties/Debug/Enable GDB\"",
- Cosmos.Build.Common.CosmosPaths.GdbClientExe), "GDB-Client", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
- }
- }
-
- private void DbgConnector_ConnectionLost(Exception e) {
- if (Interlocked.CompareExchange(ref mProcessExitEventSent, 1, 0) == 1) {
- return;
- }
- if (mDbgConnector != null) {
- mEngine.Callback.OnProcessExit(0);
- }
- }
-
- // Shows a message in the output window of VS. Needs special treatment,
- // because normally VS only shows msgs from debugged process, not internal
- // stuff like us.
- public void DebugMsg(string aMsg) {
- mCallback.OnOutputString(aMsg + "\n");
- }
-
- protected void DbgCmdStarted() {
- OutputText("DebugStub handshake completed.");
- DebugMsg("RmtDbg: Started");
-
- // OK, now debugger is ready. Send it a list of breakpoints that were set before
- // program run.
- foreach (var xBP in mEngine.BPMgr.mPendingBPs) {
- foreach (var xBBP in xBP.mBoundBPs) {
- mDbgConnector.SetBreakpoint(xBBP.RemoteID, xBBP.mAddress);
- }
- }
- mDbgConnector.SendCmd(Vs2Ds.BatchEnd);
- }
-
- void DbgCmdText(string obj) {
- mCallback.OnOutputStringUser(obj + "\r\n");
- }
-
- internal AD7Thread Thread {
- get {
- return mThread;
- }
- }
-
- void DbgCmdTrace(UInt32 aAddress) {
- DebugMsg("TraceReceived: " + aAddress);
- }
-
- void DbgCmdBreak(UInt32 aAddress) {
- // aAddress will be actual address. Call and other methods push return to (after op), but DS
- // corrects for us and sends us actual op address.
- DebugMsg("DbgCmdBreak " + aAddress + " / " + aAddress.ToString("X8").ToUpper());
-
- var xActionPoints = new List<object>();
- var xBoundBreakpoints = new List<IDebugBoundBreakpoint2>();
-
- // Search the BPs and find ones that match our address.
- foreach (var xBP in mEngine.BPMgr.mPendingBPs) {
- foreach (var xBBP in xBP.mBoundBPs) {
- if (xBBP.mAddress == aAddress) {
- xBoundBreakpoints.Add(xBBP);
- }
- }
- }
-
- mStackFrame = null;
- mCurrentAddress = aAddress;
- if (xBoundBreakpoints.Count == 0) {
- // if no matching breakpoints are found then its one of the following:
- // - Stepping operation
- // - Code based break
- // - Asm stepping
-
- if (mStepping) {
- mCallback.OnStepComplete();
- mStepping = false;
- } else {
- mCallback.OnBreakpoint(mThread, new List<IDebugBoundBreakpoint2>());
- }
- } else {
- // Found a bound breakpoint
- mCallback.OnBreakpoint(mThread, xBoundBreakpoints.AsReadOnly());
- }
- RequestFullDebugStubUpdate();
- }
-
- protected void RequestFullDebugStubUpdate() {
- // We catch and resend data rather than using a second serial port because
- // while this would work fine in a VM, it would require 2 serial ports
- // when real hardware is used.
- SendAssembly();
- mDbgConnector.SendRegisters();
- mDbgConnector.SendFrame();
- mDbgConnector.SendStack();
- }
-
- public int Attach(IDebugEventCallback2 pCallback, Guid[] rgguidSpecificEngines, uint celtSpecificEngines, int[] rghrEngineAttach) {
- Trace.WriteLine(new StackTrace(false).GetFrame(0).GetMethod().GetFullName());
- throw new NotImplementedException();
- }
-
- public int CanDetach() {
- throw new NotImplementedException();
- }
-
- public int CauseBreak() {
- throw new NotImplementedException();
- }
-
- public int Detach() {
- throw new NotImplementedException();
- }
-
- public int EnumPrograms(out IEnumDebugPrograms2 ppEnum) {
- throw new NotImplementedException();
- }
-
- public int EnumThreads(out IEnumDebugThreads2 ppEnum) {
- var xEnum = new AD7ThreadEnum(new IDebugThread2[] { mThread });
- ppEnum = xEnum;
- return VSConstants.S_OK;
- }
-
- public int GetAttachedSessionName(out string pbstrSessionName) {
- throw new NotImplementedException();
- }
-
- public int GetInfo(enum_PROCESS_INFO_FIELDS Fields, PROCESS_INFO[] pProcessInfo) {
- throw new NotImplementedException();
- }
-
- public int GetName(enum_GETNAME_TYPE gnType, out string pbstrName) {
- throw new NotImplementedException();
- }
-
- public readonly Guid PhysID = Guid.NewGuid();
- public int GetPhysicalProcessId(AD_PROCESS_ID[] pProcessId) {
- // http://blogs.msdn.com/b/jacdavis/archive/2008/05/01/what-to-do-if-your-debug-engine-doesn-t-create-real-processes.aspx
- // http://social.msdn.microsoft.com/Forums/en/vsx/thread/fe809686-e5f9-439d-9e52-00017e12300f
- pProcessId[0].guidProcessId = PhysID;
- pProcessId[0].ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID;
-
- return VSConstants.S_OK;
- }
-
- private IDebugPort2 mPort = null;
- public int GetPort(out IDebugPort2 ppPort) {
- if (mPort == null) {
- throw new Exception("Error");
- }
- ppPort = mPort;
- return VSConstants.S_OK;
- }
-
- public int GetProcessId(out Guid pguidProcessId) {
- pguidProcessId = ID;
- return VSConstants.S_OK;
- }
-
- public int GetServer(out IDebugCoreServer2 ppServer) {
- throw new NotImplementedException();
- }
-
- public int Terminate() {
- OutputText("Debugger terminating.");
-
- mHost.Stop();
-
- OutputText("Debugger terminated.");
- return VSConstants.S_OK;
- }
-
- internal void ResumeFromLaunch() {
- mHost.Start();
- }
-
- void HostShutdown(object sender, EventArgs e) {
- //AD7ThreadDestroyEvent.Send(mEngine, mThread, (uint)mProcess.ExitCode);
- //mCallback.OnProgramDestroy((uint)mProcess.ExitCode);
-
- // We dont use process info any more, but have to call this to tell
- // VS to stop debugging.
- if (Interlocked.CompareExchange(ref mProcessExitEventSent, 1, 0) == 0) {
- mCallback.OnProcessExit(0);
- }
-
- if (mDbgConnector != null) {
- mDbgConnector.Dispose();
- mDbgConnector = null;
- }
- if (mDebugInfoDb != null) {
- // Commented for debugging, so we can look at the DB after
- //mDebugInfoDb.DeleteDB();
- mDebugInfoDb.Dispose();
- mDebugInfoDb = null;
- }
- }
-
- internal void Continue() { // F5
- mCurrentAddress = null;
- mDbgConnector.Continue();
- }
-
- bool mStepping = false;
- internal void Step(enum_STEPKIND aKind) {
- if (aKind == enum_STEPKIND.STEP_INTO) { // F11
- mStepping = true;
- mDbgConnector.SendCmd(Vs2Ds.StepInto);
-
- } else if (aKind == enum_STEPKIND.STEP_OVER) { // F10
- mStepping = true;
- mDbgConnector.SendCmd(Vs2Ds.StepOver);
-
- } else if (aKind == enum_STEPKIND.STEP_OUT) { // Shift-F11
- mStepping = true;
- mDbgConnector.SendCmd(Vs2Ds.StepOut);
-
- } else if (aKind == enum_STEPKIND.STEP_BACKWARDS) {
- // STEP_BACKWARDS - Supported at all by VS?
- //
- // Possibly, by dragging the execution location up
- // or down through the source code? -Orvid
- MessageBox.Show("Step backwards is not supported.");
- mCallback.OnStepComplete(); // Have to call this otherwise VS gets "stuck"
-
- } else {
- MessageBox.Show("Unknown step type requested.");
- mCallback.OnStepComplete(); // Have to call this otherwise VS gets "stuck"
- }
- }
-
- public void SendAssembly() {
- UInt32 xAddress = mCurrentAddress.Value;
- var xSourceInfos = mDebugInfoDb.GetSourceInfos(xAddress);
-
- // Because of Asm breakpoints the address we have might be in the middle of a C# line.
- // So we find the closest address to ours that is less or equal to ours.
- var xQry = from x in xSourceInfos
- where x.Key <= xAddress
- orderby x.Key descending
- select x.Value;
- var xValue = xQry.FirstOrDefault();
- if (xValue == null) {
- return;
- }
-
- // Create list of asm labels that belong to this line of C#.
- var xMappings = from x in xSourceInfos
- where x.Value.SourceFile == xValue.SourceFile
- && x.Value.Line == xValue.Line
- && x.Value.Column == xValue.Column
- select x.Key;
- var xLabels = new List<string>();
- foreach (uint xAddr in xMappings) {
- foreach (string xLabel in mDebugInfoDb.GetLabels(xAddr)) {
- xLabels.Add(xLabel + ":");
- }
- }
-
- // Get assembly source
- var xCode = AsmSource.GetSourceForLabels(Path.ChangeExtension(mISO, ".asm"), xLabels);
-
- // Get label for current address.
- // A single address can have multiple labels (IL, Asm). Because of this we search
- // for the one with the Asm tag. We dont have the tags in this debug info though,
- // so instead if there is more than one label we use the longest one which is the Asm tag.
- var xCurrentLabels = mDebugInfoDb.GetLabels(mCurrentAddress.Value);
- if (xCurrentLabels.Length > 0) {
- string xCurrentLabel = xCurrentLabels.OrderBy(q => q.Length).Last();
- // Insert it to the first line of our data stream
- xCode.Insert(0, xCurrentLabel + "\r\n");
- mDebugDownPipe.SendCommand(Debugger2Windows.AssemblySource, Encoding.UTF8.GetBytes(xCode.ToString()));
- }
- }
-
- //TODO: At some point this will probably need to be exposed for access outside of AD7Process
- protected void OutputText(string aText) {
- // With Bochs this method may be invoked before the pipe is created.
- if (null == mDebugDownPipe) { return; }
- mDebugDownPipe.SendCommand(Debugger2Windows.OutputPane, Encoding.UTF8.GetBytes(aText + "\r\n"));
- }
-
- protected void OutputClear() {
- // With Bochs this method may be invoked before the pipe is created.
- if (null == mDebugDownPipe) { return; }
- mDebugDownPipe.SendCommand(Debugger2Windows.OutputClear);
- }
- }
- }