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.");
}
/// Instanciate the 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.
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(DbgCmdBreak);
mDbgConnector.CmdTrace += new Action(DbgCmdTrace);
mDbgConnector.CmdText += new Action(DbgCmdText);
mDbgConnector.CmdStarted += new Action(DbgCmdStarted);
mDbgConnector.OnDebugMsg += new Action(DebugMsg);
mDbgConnector.ConnectionLost += new Action(DbgConnector_ConnectionLost);
mDbgConnector.CmdRegisters += new Action(DbgCmdRegisters);
mDbgConnector.CmdFrame += new Action(DbgCmdFrame);
mDbgConnector.CmdStack += new Action(DbgCmdStack);
mDbgConnector.CmdPong += new Action(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(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[]
{ new KeyValuePair("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