Cosmos/source2/Debug/Cosmos.Debug.VSDebugEngine/AD7.Impl/AD7Process.cs
2010-07-25 19:18:10 +00:00

386 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Debugger.Interop;
using Microsoft.VisualStudio;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.IO;
using System.Collections.Specialized;
using Cosmos.Debug.Common.CDebugger;
using Cosmos.Compiler.Debug;
using Cosmos.Debug.Common;
using Cosmos.Build.Common;
namespace Cosmos.Debug.VSDebugEngine {
public class AD7Process : IDebugProcess2 {
internal Guid mID = Guid.NewGuid();
private Process mProcess;
private ProcessStartInfo mProcessStartInfo;
private EngineCallback mCallback;
private AD7Thread mThread;
private AD7Engine mEngine;
private DebugConnector mDbgConnector;
internal ReverseSourceInfos mReverseSourceMappings;
internal SourceInfos mSourceMappings;
internal uint? mCurrentAddress = null;
internal string mISO;
private readonly NameValueCollection mDebugInfo;
protected TargetHost mTargetHost;
protected void LaunchQEMU(bool aGDB) {
var xDebugConnectorStr = "-serial tcp:127.0.0.1:4444";
var xQT = "\"";
// Start QEMU
// QEMU Command Line docs: http://wiki.qemu.org/download/qemu-doc.html#sec_005finvocation
// Here we actually call our dummy/proxy program (Cosmos.Debug.HostProcess.exe) which in turn calls QEMU.
mProcessStartInfo.Arguments =
"false" // Tells proxy to use ShellExecute or not (In this case, not, ie false)
// Rest of arguments are used to launch another process and its arguments.
+ " " + xQT + Path.Combine(PathUtilities.GetQEmuDir(), "qemu.exe") + xQT // Program for our proxy to run
+ " -L " + xQT + PathUtilities.GetQEmuDir().Replace("\\", "/") + xQT // Directory for the BIOS, VGA BIOS and keymaps
+ " -cdrom " + xQT + mISO.Replace("\\", "/") + xQT // CDRom image
+ " -boot d" // Boot from the CDRom
+ " " + xDebugConnectorStr;
if (aGDB) {
mProcessStartInfo.Arguments
+= " --gdb tcp::8832" // We now use 8832 to be same as VMWare
+ "-S"; // Pause on startup, wait for GDB to connect and control
}
//#if VM_QEMU
// #if DEBUG_CONNECTOR_TCP_SERVER
// var xDebugConnectorStr = "-serial tcp:127.0.0.1:4444";
// #endif
// #if DEBUG_CONNECTOR_PIPE_CLIENT
// var xDebugConnectorStr = @"-serial pipe:CosmosDebug";
// #endif
// #if DEBUG_CONNECTOR_PIPE_SERVER
// var xDebugConnectorStr = @"-serial pipe:CosmosDebug";
// #endif
}
protected void LaunchVMWareWorkstation(bool aGDB) {
//TODO: Change to use Cosmos path
//TODO: App Roaming doesnt have the vmx.. need to update the insaller
//string xPath = Path.Combine(PathUtilities.GetBuildDir(), @"VMWare\Workstation") + @"\";
string xPath = @"M:\source\Cosmos\Build\VMWare\Workstation\";
using (var xSrc = new StreamReader(xPath + "Cosmos.vmx")) {
// This copy process also leaves the VMX writeable. VMWare doesnt like them read only.
using (var xDest = new StreamWriter(xPath + "Debug.vmx")) {
string xLine;
while ((xLine = xSrc.ReadLine()) != null) {
var xParts = xLine.Split('=');
if (xParts.Length == 2) {
string xName = xParts[0].Trim();
string xValue = xParts[1].Trim();
// We delete uuid entries so VMWare doenst ask the user "Did you move or copy" the file
if ((xName == "uuid.location") || (xName == "uuid.bios")) {
xValue = null;
} else if (xName == "ide1:0.fileName") {
//TODO: Update ISO to selected project
//xValue = @"m:\source\Cosmos\source2\Users\Kudzu\Breakpoints\bin\Debug\CosmosKernel.iso";
xValue = "\"" + mDebugInfo["ISOFile"] + "\"";
}
if (xValue != null) {
xDest.WriteLine(xName + " = " + xValue);
}
}
}
if (aGDB) {
xDest.WriteLine();
xDest.WriteLine("debugStub.listen.guest32 = \"TRUE\"");
xDest.WriteLine("debugStub.hideBreakpoints = \"TRUE\"");
xDest.WriteLine("monitor.debugOnStartGuest32 = \"TRUE\"");
xDest.WriteLine("debugStub.listen.guest32.remote = \"TRUE\"");
}
}
}
//TODO: Find this in code. This is hardcoded to default location right now.
string xVmwPath = @"C:\Program Files (x86)\VMware\VMware Workstation\";
//mProcessStartInfo.Arguments = "true \"" + xPath + "Debug.vmx\" -x -q";
// -x: Auto power on VM. Must be small x, big X means something else.
// -q: Close VMWare when VM is powered off.
// Options must come beore the vmx, and cannot use shellexecute
mProcessStartInfo.Arguments = "false \"" + xVmwPath + "vmware.exe" + "\" -x -q \"" + xPath + "Debug.vmx\"";
}
internal AD7Process(string aDebugInfo, EngineCallback aCallback, AD7Engine aEngine, IDebugPort2 aPort) {
System.Diagnostics.Debug.WriteLine("Test message");
mDebugInfo = new NameValueCollection();
NameValueCollectionHelper.LoadFromString(mDebugInfo, aDebugInfo);
mISO = mDebugInfo["ISOFile"];
var xGDBDebugStub = false;
Boolean.TryParse(mDebugInfo["EnableGDB"], out xGDBDebugStub);
mProcessStartInfo = new ProcessStartInfo(Path.Combine(PathUtilities.GetVSIPDir(), "Cosmos.Debug.HostProcess.exe"));
if (StringComparer.InvariantCultureIgnoreCase.Equals(mDebugInfo["BuildTarget"], "qemu")) {
mTargetHost = TargetHost.QEMU;
LaunchQEMU(xGDBDebugStub);
} else if (StringComparer.InvariantCultureIgnoreCase.Equals(mDebugInfo["BuildTarget"], "VMWareWorkstation")) {
mTargetHost = TargetHost.VMWareWorkstation;
LaunchVMWareWorkstation(xGDBDebugStub);
} else {
throw new Exception("Invalid BuildTarget value: '" + mDebugInfo["BuildTarget"] + "'!");
}
mProcessStartInfo.UseShellExecute = false;
mProcessStartInfo.RedirectStandardInput = true;
mProcessStartInfo.RedirectStandardError = true;
mProcessStartInfo.RedirectStandardOutput = true;
mProcessStartInfo.CreateNoWindow = true;
IDictionary<uint, string> xAddressLabelMappings;
IDictionary<string, uint> xLabelAddressMappings;
Cosmos.Debug.Common.CDebugger.SourceInfo.ReadFromFile(Path.ChangeExtension(mISO, "cmap"), out xAddressLabelMappings, out xLabelAddressMappings);
if (xAddressLabelMappings.Count == 0)
{
throw new Exception("Debug data not found: LabelByAddressMapping");
}
//TODO: This next line takes a long time. See if we can speed it up.
var xSW = new Stopwatch();
xSW.Start();
mSourceMappings = Cosmos.Debug.Common.CDebugger.SourceInfo.GetSourceInfo(xAddressLabelMappings, xLabelAddressMappings, Path.ChangeExtension(mISO, ".cxdb"));
xSW.Stop();
Trace.WriteLine("GetSourceInfo took: " + xSW.Elapsed);
if (mSourceMappings.Count == 0) {
throw new Exception("Debug data not found: SourceMappings");
}
mReverseSourceMappings = new ReverseSourceInfos(mSourceMappings);
if (StringComparer.InvariantCultureIgnoreCase.Equals(mDebugInfo["BuildTarget"], "qemu")) {
mDbgConnector = new Cosmos.Debug.Common.CDebugger.DebugConnectorTCPServer();
} else if (StringComparer.InvariantCultureIgnoreCase.Equals(mDebugInfo["BuildTarget"], "vmwareworkstation")) {
mDbgConnector = new Cosmos.Debug.Common.CDebugger.DebugConnectorPipeServer();
} else {
throw new Exception("BuildTarget value not valid: '" + mDebugInfo["BuildTarget"] + "'!");
}
mDbgConnector.CmdTrace += new Action<Cosmos.Compiler.Debug.MsgType, uint>(DbgCmdTrace);
mDbgConnector.CmdText += new Action<string>(DbgCmdText);
mDbgConnector.CmdReady += new Action(DbgCmdReady);
mDbgConnector.ConnectionLost = new Action<Exception>(
delegate {
mEngine.Callback.OnProcessExit(0);
}
);
System.Threading.Thread.Sleep(250);
mProcess = Process.Start(mProcessStartInfo);
mProcess.EnableRaisingEvents = true;
mProcess.Exited += new EventHandler(mProcess_Exited);
// Sleep 250 and see if it exited too quickly. Why do we do this? We have .Exited hooked. Is this in case it happens between start and hook?
// if so, why not hook before start?
System.Threading.Thread.Sleep(250);
if (mProcess.HasExited) {
Trace.WriteLine("Error while running: " + mProcess.StandardError.ReadToEnd());
Trace.WriteLine(mProcess.StandardOutput.ReadToEnd());
Trace.WriteLine("ExitCode: " + mProcess.ExitCode);
throw new Exception("Error while starting application");
}
mCallback = aCallback;
mEngine = aEngine;
mThread = new AD7Thread(aEngine, this);
mCallback.OnThreadStart(mThread);
mPort = aPort;
}
// 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 DbgCmdReady() {
DebugMsg("RmtDbg: Ready");
// OK, now debugger is ready. Send it a list of breakpoints that were set before
// program run.
foreach (var xBP in mEngine.m_breakpointManager.mPendingBPs) {
foreach (var xBBP in xBP.mBoundBPs) {
DebugMsg("Setting BP @ " + xBBP.m_address.ToString("X8").ToUpper());
SetBreakpointAddress(xBBP.m_address);
}
}
}
public void SetBreakpointAddress(uint aAddress) {
mDbgConnector.SetBreakpointAddress(aAddress);
}
void DbgCmdText(string obj) {
mCallback.OnOutputString(obj + "\r\n");
}
internal AD7Thread Thread {
get {
return mThread;
}
}
void DbgCmdTrace(Cosmos.Compiler.Debug.MsgType arg1, uint arg2) {
switch (arg1) {
case Cosmos.Compiler.Debug.MsgType.BreakPoint: {
var xActualAddress = arg2 - 5; // - 5 to correct the addres:
DebugMsg("BP hit @ " + xActualAddress.ToString("X8").ToUpper());
// when doing a CALL, the return address is pushed, but that's the address of the next instruction, after CALL. call is 5 bytes (for now?)
var xActionPoints = new List<object>();
var xBoundBreakpoints = new List<IDebugBoundBreakpoint2>();
// Search the BPs and find the ones that match our address
foreach (var xBP in mEngine.m_breakpointManager.mPendingBPs) {
foreach (var xBBP in xBP.mBoundBPs) {
if (xBBP.m_address == xActualAddress) {
xBoundBreakpoints.Add(xBBP);
}
}
}
mCurrentAddress = xActualAddress;
//mCallback.onb
mCallback.OnBreakpoint(mThread, new ReadOnlyCollection<IDebugBoundBreakpoint2>(xBoundBreakpoints), xActualAddress);
//mEngine.Callback.OnBreakComplete(mThread, );
mEngine.AfterBreak = true;
//mEngine.Callback.OnBreak(mThread);
break;
}
default: {
DebugMsg("TraceReceived: " + arg1);
break;
}
}
}
#region IDebugProcess2 Members
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(uint Fields, PROCESS_INFO[] pProcessInfo)
{ throw new NotImplementedException();
}
public int GetName(uint gnType, out string pbstrName)
{
throw new NotImplementedException();
}
public int GetPhysicalProcessId(AD_PROCESS_ID[] pProcessId)
{
Trace.WriteLine(new StackTrace(false).GetFrame(0).GetMethod().GetFullName());
pProcessId[0].dwProcessId = (uint)mProcess.Id;
pProcessId[0].ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM;
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)
{
Trace.WriteLine(new StackTrace(false).GetFrame(0).GetMethod().GetFullName());
pguidProcessId = mID;
return VSConstants.S_OK;
}
public int GetServer(out IDebugCoreServer2 ppServer)
{
throw new NotImplementedException();
}
public int Terminate()
{
mProcess.Kill();
return VSConstants.S_OK;
}
#endregion
internal void ResumeFromLaunch()
{
// This unpauses our debug host
// We do this because VS requires a start, and then a resume after. So we have debughost which is a stub
// that allows VS to "see" that. Here we resume it.
mProcess.StandardInput.WriteLine();
if (mTargetHost == TargetHost.QEMU) {
// QEMU and Pipes - QEMU will stop and wait till we connect. It will not even show until we do.
// We have to do this after we release the debug host though.
mDbgConnector.WaitConnect();
}
}
void mProcess_Exited(object sender, EventArgs e) {
Trace.WriteLine("Error while running: " + mProcess.StandardError.ReadToEnd());
Trace.WriteLine(mProcess.StandardOutput.ReadToEnd());
//AD7ThreadDestroyEvent.Send(mEngine, mThread, (uint)mProcess.ExitCode);
//mCallback.OnProgramDestroy((uint)mProcess.ExitCode);
mCallback.OnProcessExit((uint)mProcess.ExitCode);
}
internal void Continue() {
mCurrentAddress = null;
mDbgConnector.SendCommand((byte)Command.Break);
}
internal void Step() {
mDbgConnector.SendCommand((byte)Command.Step);
}
}
}