Cosmos/source/Cosmos.Debug.VSDebugEngine/AD7.Impl/AD7Process.cs
Geramy L. Loveless d6728ec902 bochs safe kill
we are now safely killing the bochs process and we release the use of
the cdb file via the OnShutDown event in the AD7Process.css
2015-05-26 11:17:13 -07:00

1169 lines
48 KiB
C#

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;
using Label = Cosmos.Debug.Common.Label;
namespace Cosmos.Debug.VSDebugEngine
{
public partial class AD7Process : IDebugProcess2
{
public bool ASMSteppingMode = false;
public Guid ID = Guid.NewGuid();
protected EngineCallback mCallback;
public AD7Thread mThread;
protected AD7Engine mEngine;
public UInt32? mCurrentAddress = null;
public UInt32? mNextAddress1 = null;
public string mCurrentASMLine = null;
public string mNextASMLine1 = 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;
private bool mASMSteppingOut = false;
private int mASMSteppingOut_NumEndMethodLabelsPassed = 0;
private Tuple<UInt32, UInt32, int> ASMBPToStepTo = null;
//ASM Breakpoints stored as C# Address -> ASM Address, C# BP ID
//Allows quick look-up on INT3 occurring
private List<Tuple<UInt32, UInt32, int>> ASMBreakpoints = new List<Tuple<UInt32, UInt32, int>>();
private ManualResetEvent ASMWindow_CurrentLineUpdated = new ManualResetEvent(false);
private ManualResetEvent ASMWindow_NextLine1Updated = new ManualResetEvent(false);
private ManualResetEvent ASMWindow_NextAddress1Updated = new ManualResetEvent(false);
private ManualResetEvent StackDataUpdated = new ManualResetEvent(false);
// 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);
if (aData.Length < 40)
{
mCurrentAddress = null;
}
else
{
UInt32 x32 = (UInt32)
(aData[39] << 24 |
aData[38] << 16 |
aData[37] << 8 |
aData[36]);
mCurrentAddress = x32;
//SendAssembly(true);
}
}
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(ushort aCmd, byte[] aData)
{
try
{
if (aCmd <= 127)
{
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();
}
break;
case Windows2Debugger.ToggleAsmBreak2:
{
string xLabel = Encoding.UTF8.GetString(aData);
UInt32 xAddress = mDebugInfoDb.AddressOfLabel(xLabel);
if (GetASMBreakpointInfoFromASMAddress(xAddress) == null)
{
SetASMBreakpoint(xAddress);
}
else
{
ClearASMBreakpoint(xAddress);
}
break;
}
case Windows2Debugger.ToggleStepMode:
ASMSteppingMode = !ASMSteppingMode;
break;
case Windows2Debugger.SetStepModeAssembler:
ASMSteppingMode = true;
break;
case Windows2Debugger.SetStepModeSource:
ASMSteppingMode = false;
break;
case Windows2Debugger.CurrentASMLine:
{
mCurrentASMLine = Encoding.UTF8.GetString(aData);
ASMWindow_CurrentLineUpdated.Set();
break;
}
case Windows2Debugger.NextASMLine1:
{
if (aData.Length == 0)
{
mNextASMLine1 = null;
mNextAddress1 = null;
}
else
{
mNextASMLine1 = Encoding.UTF8.GetString(aData);
ASMWindow_NextLine1Updated.Set();
}
break;
}
case Windows2Debugger.NextLabel1:
{
string nextLabel = Encoding.UTF8.GetString(aData);
mNextAddress1 = mDebugInfoDb.AddressOfLabel(nextLabel);
ASMWindow_NextAddress1Updated.Set();
break;
}
//cmd used from assembler window
case Windows2Debugger.Continue:
Step(enum_STEPKIND.STEP_OVER);
break;
//cmd used from assembler window
case Windows2Debugger.AsmStepInto:
Step(enum_STEPKIND.STEP_INTO);
break;
default:
throw new Exception(String.Format("Command value '{0}' not supported in method AD7Process.mDebugUpPipe_DataPacketReceived.", aCmd));
}
}
else
{
throw new NotImplementedException("Sending other channels not yet supported!");
}
}
catch(Exception ex)
{
//We cannot afford to silently break the pipe!
OutputText("AD7Process UpPipe receive error! " + ex.Message);
System.Diagnostics.Debug.WriteLine("AD7Process UpPipe receive error! " + ex.ToString());
}
}
private List<Tuple<UInt32, UInt32, int>> GetASMBreakpointInfoFromCSAddress(UInt32 csAddress)
{
return ASMBreakpoints.Where(x => x.Item1 == csAddress).ToList();
}
private Tuple<UInt32, UInt32, int> GetASMBreakpointInfoFromASMAddress(UInt32 asmAddress)
{
Tuple<UInt32, UInt32, int> result = null;
var posBPs = ASMBreakpoints.Where(x => x.Item2 == asmAddress);
if (posBPs.Count() > 0)
{
result = posBPs.First();
}
return result;
}
private void SetASMBreakpoint(UInt32 aAddress)
{
if (GetASMBreakpointInfoFromASMAddress(aAddress) == null)
{
bool set = false;
for (int xID = 0; xID < BreakpointManager.MaxBP; xID++)
{
if (mEngine.BPMgr.mActiveBPs[xID] == null)
{
UInt32 CSBPAddress = mDebugInfoDb.GetClosestCSharpBPAddress(aAddress);
ASMBreakpoints.Add(new Tuple<UInt32, UInt32, int>(CSBPAddress, aAddress, xID));
mEngine.BPMgr.mActiveBPs[xID] = new AD7BoundBreakpoint(CSBPAddress);
var label = mDebugInfoDb.GetLabels(CSBPAddress)[0];
INT3sSet.Add(new KeyValuePair<uint, string>(CSBPAddress, label));
mDbgConnector.SetBreakpoint(xID, CSBPAddress);
set = true;
break;
}
}
if (!set)
{
throw new Exception("Maximum number of active breakpoints exceeded (" + BreakpointManager.MaxBP + ").");
}
}
}
private void ClearASMBreakpoint(UInt32 aAddress)
{
var bp = GetASMBreakpointInfoFromASMAddress(aAddress);
if (bp != null)
{
var xID = bp.Item3;
int index = INT3sSet.FindIndex(x => x.Key == bp.Item1);
INT3sSet.RemoveAt(index);
mDbgConnector.DeleteBreakpoint(xID);
mEngine.BPMgr.mActiveBPs[xID] = null;
ASMBreakpoints.Remove(bp);
}
}
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];
// using (var xDebug = new StreamWriter(@"e:\debug.info", false))
// {
// foreach (var xItem in mDebugInfo.AllKeys)
// {
// xDebug.WriteLine("{0}: '{1}'", xItem, mDebugInfo[xItem]);
// }
// xDebug.Flush();
// }
if (String.IsNullOrWhiteSpace(xPort))
{
xPort = mDebugInfo[BuildProperties.CosmosDebugPortString];
}
var xParts = (null == xPort) ? null : xPort.Split(' ');
if ((null == xParts) || (2 > xParts.Length))
{
throw new Exception(string.Format("Unable to parse VS debug port: '{0}'", xPort));
//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();
var xLaunch = mDebugInfo[BuildProperties.LaunchString];
OutputText("Starting debug connector.");
switch (xPortType)
{
case "pipe:":
mDbgConnector = new Cosmos.Debug.Common.DebugConnectorPipeServer(xPortParam);
break;
case "serial:":
if (xLaunch == "IntelEdison")
{
mDbgConnector = new Cosmos.Debug.Common.DebugConnectorEdison(xPortParam, Path.ChangeExtension(mDebugInfo["ISOFile"], ".bin"));
}
else
{
mDbgConnector = new Cosmos.Debug.Common.DebugConnectorSerial(xPortParam);
}
break;
default:
throw new Exception("No debug connector found for port type '" + xPortType + "'");
}
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);
mDbgConnector.CmdStackCorruptionOccurred += DbgCmdStackCorruptionOccurred;
mDbgConnector.CmdNullReferenceOccurred += DbgCmdNullReferenceOccurred;
mDbgConnector.CmdMessageBox += DbgCmdMessageBox;
mDbgConnector.CmdChannel += DbgCmdChannel;
}
private void DbgCmdChannel(byte aChannel, byte aCommand, byte[] aData)
{
mDebugDownPipe.SendRawToChannel(aChannel, aCommand, aData);
}
private void DbgCmdStackCorruptionOccurred(uint lastEIPAddress)
{
MessageBox.Show(String.Format("Stack corruption occurred at address 0x{0:X8}! Halting now.", lastEIPAddress));
}
private void DbgCmdNullReferenceOccurred(uint lastEIPAddress)
{
MessageBox.Show(String.Format("NullReferenceException occurred at address 0x{0:X8}! Halting now.", lastEIPAddress));
}
private void DbgCmdMessageBox(string message)
{
MessageBox.Show("Message from your Cosmos operating system:\r\n\r\n" + message);
}
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 += mDebugUpPipe_DataPacketReceived;
mDebugUpPipe.Start();
}
else
{
mDebugUpPipe.CleanHandlers();
mDebugUpPipe.DataPacketReceived += mDebugUpPipe_DataPacketReceived;
}
// 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;
case LaunchType.IntelEdison:
mHost = new Host.IntelEdison(mDebugInfo, false);
break;
default:
throw new Exception("Invalid Launch value: '" + mLaunch + "'.");
}
mHost.OnShutDown += HostShutdown;
string xDbPath = Path.ChangeExtension(mISO, "cdb");
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)
{
var label = mDebugInfoDb.GetLabels(xBBP.mAddress)[0];
INT3sSet.Add(new KeyValuePair<uint, string>(xBBP.mAddress, label));
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());
if (mASMSteppingOut)
{
string[] currentASMLabels = mDebugInfoDb.GetLabels(aAddress);
foreach (string aLabel in currentASMLabels)
{
if (aLabel.Contains("END__OF__METHOD_EXCEPTION__2"))
{
mASMSteppingOut_NumEndMethodLabelsPassed++;
break;
}
}
if (mASMSteppingOut_NumEndMethodLabelsPassed >= 2)
{
mASMSteppingOut = false;
}
new System.Threading.Tasks.Task(() =>
{
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
}).Start();
}
else
{
bool fullUpdate = true;
var xActionPoints = new List<object>();
var xBoundBreakpoints = new List<IDebugBoundBreakpoint2>();
if (!mBreaking)
{
// 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;
mCurrentASMLine = null;
if (xBoundBreakpoints.Count == 0)
{
// If no matching breakpoints are found then its one of the following:
// - VS Break
// - Stepping operation
// - Asm break
//We _must_ respond to the VS commands via callback if VS is waiting on one so check this first...
if (mBreaking)
{
mCallback.OnBreak(mThread);
mBreaking = false;
}
else if (mStepping)
{
mCallback.OnStepComplete();
mStepping = false;
}
else
{
//Check if current address is the ASM BP we might be looking for
if (ASMBPToStepTo != null && ASMBPToStepTo.Item2 == aAddress)
{
//There is an ASM BP at this address so break
mCallback.OnBreak(mThread);
//Clear what we are stepping towards
ASMBPToStepTo = null;
}
else
{
fullUpdate = false;
//Check we aren't already stepping towards an ASM BP
if (ASMBPToStepTo == null)
{
//Check for future ASM breakpoints...
//Since we got this far, we know this must be an INT3 for a future ASM BP that has to be in current C# line.
//So get the ASM BP based off current address
var bp = GetASMBreakpointInfoFromCSAddress(aAddress).First();
//Set it as address we are looking for
ASMBPToStepTo = bp;
}
//Step towards the ASM BP(step-over since we don't want to go through calls or anything)
//We must check we haven't just stepped and address jumped wildely out of range (e.g. conditional jumps)
if (aAddress < ASMBPToStepTo.Item1 || aAddress > ASMBPToStepTo.Item2)
{
//If we have, just continue execution as this BP won't be hit.
mDbgConnector.Continue();
ASMBPToStepTo = null;
}
else
{
//We must do an update of ASM window so Step-Over can function properly
SendAssembly(true);
//Delay / wait for asm window to update
WaitForAssemblyUpdate();
//Do the step-over
ASMStepOver();
}
}
}
}
else
{
// Found a bound breakpoint
mCallback.OnBreakpoint(mThread, xBoundBreakpoints.AsReadOnly());
}
if (fullUpdate)
{
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.
new System.Threading.Tasks.Task(() =>
{
SendAssembly();
mDbgConnector.SendRegisters();
mDbgConnector.SendFrame();
mDbgConnector.SendStack();
}).Start();
}
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();
}
bool mBreaking = false;
public int CauseBreak()
{
mBreaking = true;
mDbgConnector.SendCmd(Vs2Ds.Break);
return VSConstants.S_OK;
}
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.");
try
{
mHost.Stop();
OutputText("Debugger terminated.");
return VSConstants.S_OK;
}
catch (ApplicationException)
{
OutputText("Failed to stop debugger!");
OutputText("\r\n");
OutputText("You need to install the VMWare VIX API!");
MessageBox.Show("Failed to stop debugger! You need to install the VMWare VIX API!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
OutputText("Failed to stop debugger! Exception message: " + ex.Message);
OutputText("\r\n");
OutputText("You probably need to install the VMWare VIX API!");
MessageBox.Show("Failed to stop debugger! You probably need to install the VMWare VIX API!\r\n\r\nCheck Output window for more details.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return VSConstants.E_FAIL;
}
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)
{
try
{
mCallback.OnProcessExit(0);
}
catch
{
// swallow exceptions here?
}
}
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
ClearINT3sOnCurrentMethod();
//Check for a future asm BP on current line
//If there is, don't do continue, do AsmStepOver
// The current address may or may not be a C# line due to asm stepping
//So get the C# INT3 address
UInt32 csAddress = mDebugInfoDb.GetClosestCSharpBPAddress(mCurrentAddress.Value);
//Get any Asm BPs for this address
var bps = GetASMBreakpointInfoFromCSAddress(csAddress).Where(x => x.Item2 > mCurrentAddress.Value).ToList();
//If there are any, do AsmStepOver on the next one after current address
if (bps.Count > 0)
{
var bp = bps.OrderBy(x => x.Item2).First();
ASMBPToStepTo = bp;
ASMStepOver();
mCurrentAddress = null;
mCurrentASMLine = null;
}
else
{
mCurrentAddress = null;
mCurrentASMLine = null;
mDbgConnector.Continue();
}
}
bool mStepping = false;
internal void Step(enum_STEPKIND aKind)
{
if (aKind == enum_STEPKIND.STEP_INTO)
{ // F11
mStepping = true;
if (ASMSteppingMode)
{
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
mDbgConnector.SendRegisters();
}
else
{
SetINT3sOnCurrentMethod();
mDbgConnector.SendCmd(Vs2Ds.StepInto);
}
}
else if (aKind == enum_STEPKIND.STEP_OVER)
{ // F10
mStepping = true;
if (ASMSteppingMode)
{
ASMStepOver();
}
else
{
SetINT3sOnCurrentMethod();
mDbgConnector.SendCmd(Vs2Ds.StepOver);
}
}
else if (aKind == enum_STEPKIND.STEP_OUT)
{ // Shift-F11
ClearINT3sOnCurrentMethod();
mStepping = true;
if (ASMSteppingMode)
{
mASMSteppingOut = true;
mASMSteppingOut_NumEndMethodLabelsPassed = 0;
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
//Set a condition to say we should be doing step out
//On break, check line just stepped.
//If current line is RET - do one more step then break.
//Else do another step
}
else
{
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"
}
}
internal void SetINT3sOnCurrentMethod()
{
//Set all the CS Tracepoints to INT3 but without setting them like BPs
//Don't bother setting existing CS BPs
ChangeINT3sOnCurrentMethod(false);
}
internal void ClearINT3sOnCurrentMethod()
{
//Clear all the CS Tracepoints to NOP but without treating them like BPs
//Don't clear existing/actual CS BPs
ChangeINT3sOnCurrentMethod(true);
}
public List<KeyValuePair<UInt32, string>> INT3sSet = new List<KeyValuePair<UInt32, string>>();
internal void ChangeINT3sOnCurrentMethod(bool clear)
{
if (mCurrentAddress.HasValue)
{
var currMethod = mDebugInfoDb.GetMethod(mCurrentAddress.Value);
//Clear out the full list so we don't accidentally accumulate INT3s all over the place
//Or set INT3s for all places in current method
var tpAdresses = clear ? new List<KeyValuePair<UInt32, string>>(INT3sSet.Count) : mDebugInfoDb.GetAllINT3AddressesForMethod(currMethod, true);
//If we just do a stright assigment then we get a collection modified exception in foreach loop below
if (clear)
{
tpAdresses.AddRange(INT3sSet);
}
var bps = mEngine.BPMgr.mPendingBPs.Select(x => x.mBoundBPs).ToList();
var bpAddressessUnified = new List<UInt32>();
foreach (var bp in bps)
{
bpAddressessUnified.AddRange(bp.Select(x => x != null ? x.mAddress : 0));
}
bpAddressessUnified.AddRange(mEngine.BPMgr.mActiveBPs.Select(x => x != null ? x.mAddress : 0));
foreach (var addressInfo in tpAdresses)
{
var address = addressInfo.Key;
//Don't set/clear actual BPs
if (!bpAddressessUnified.Contains(address))
{
int index = INT3sSet.FindIndex(x => x.Key == address);
bool set = index != -1;
if (clear && set)
{
//Clear the INT3
mDbgConnector.ClearINT3(address);
INT3sSet.RemoveAt(index);
}
else if (!clear && !set)
{
//Set the INT3
mDbgConnector.SetINT3(address);
INT3sSet.Add(addressInfo);
}
}
}
}
}
internal void ASMStepOver()
{
//ASM Step over : Detect calls and treat them specially.
//If current line has been stepped, get next line (since that is what we will step over)
//If current line hasn't been stepped, use current line
//If current line is CALL, set INT3 on next line and do continue
//Else do asm step-into
string currentASMLine = mCurrentASMLine;
if (string.IsNullOrEmpty(currentASMLine))
{
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
}
else
{
currentASMLine = currentASMLine.Trim();
string currentASMOp = currentASMLine.Split(' ')[0].ToUpper();
if (currentASMOp == "CALL")
{
//Get the line after the call
string nextASMLine = mNextASMLine1;
UInt32? nextAddress = mNextAddress1;
if (string.IsNullOrEmpty(nextASMLine) || !nextAddress.HasValue)
{
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
}
else
{
//Set the INT3 at next address
mDbgConnector.SetAsmBreakpoint(nextAddress.Value);
mDbgConnector.Continue();
}
}
else
{
mDbgConnector.SendCmd(Vs2Ds.AsmStepInto);
}
}
}
public void SendAssembly(bool noDisplay = false)
{
AD7Util.Log("SendAssembly");
ASMWindow_CurrentLineUpdated.Reset();
ASMWindow_NextAddress1Updated.Reset();
ASMWindow_NextLine1Updated.Reset();
UInt32 xAddress = mCurrentAddress.Value;
var xSourceInfos = mDebugInfoDb.GetSourceInfos(xAddress);
AD7Util.Log("SendAssembly - SourceInfos retrieved for address 0x{0}", xAddress.ToString("X8"));
if (xSourceInfos.Count > 0)
{
//We should be able to display the asesembler source for any address regardless of whether a C#
//line is associated with it.
//However, we do not store all labels in the debug database because that would make the compile
//time insane.
//So:
// - We take the current address amd find the method it is part of
// - We use the method header label as a start point and find all asm labels till the method footer label
// - We then find all the asm for these labels and display it.
Label[] xLabels = mDebugInfoDb.GetMethodLabels(xAddress);
AD7Util.Log("SendAssembly - MethodLabels retrieved");
// get the label of our current position, or the closest one before
var curPosLabel = xLabels.Where(i => i.Address <= xAddress).OrderByDescending(i => i.Address).FirstOrDefault();
// if curPosLabel is null, grab the first one.
if (curPosLabel == null)
{
curPosLabel = xLabels[0];
}
var curPosIndex = Array.IndexOf(xLabels, curPosLabel);
// we want 50 items before and after the current item, so 100 in total.
var itemsBefore = 10;
var itemsAfter = 10;
if (curPosIndex < itemsBefore)
{
// there are no 50 items before the current one, so adjust
itemsBefore = curPosIndex;
}
if ((curPosIndex + itemsAfter) >= xLabels.Length)
{
// there are no 50 items after the current one, so adjust
itemsAfter = xLabels.Length - curPosIndex;
}
var newArr = new Label[itemsBefore + itemsAfter];
for (int i = 0; i < newArr.Length; i++)
{
newArr[i] = xLabels[(curPosIndex - itemsBefore) + i];
}
xLabels = newArr;
//The ":" has to be added in because labels in asm code have it on the end - it's easier to add it here than
//strip them out of the read asm
var xLabelNames = xLabels.Select(x => x.Name + ":").ToList();
// Get assembly source
var xCode = AsmSource.GetSourceForLabels(Path.ChangeExtension(mISO, ".asm"), xLabelNames);
AD7Util.Log("SendAssembly - SourceForLabels retrieved");
// 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.
string xCurrentLabel = "";
var xCurrentLabels = mDebugInfoDb.GetLabels(xAddress);
if (xCurrentLabels.Length > 0)
{
xCurrentLabel = xCurrentLabels.OrderBy(q => q.Length).Last();
}
if (string.IsNullOrEmpty(xCurrentLabel))
{
xCurrentLabel = "NO_METHOD_LABEL_FOUND";
}
// Insert filter labels list as THIRD(!) line of our data stream
string filterLabelsList = "";
foreach (var addressInfo in INT3sSet)
{
//"We have to add the ".00:" because of how the ASM window works...
filterLabelsList += "|" + addressInfo.Value + ".00";
}
if (filterLabelsList.Length > 0)
{
filterLabelsList = filterLabelsList.Substring(1);
}
xCode.Insert(0, filterLabelsList + "\r\n");
// Insert parameters as SECOND(!) line of our data stream
xCode.Insert(0, (noDisplay ? "NoDisplay" : "") + "|" + (ASMSteppingMode ? "AsmStepMode" : "") + "\r\n");
// Insert current line's label as FIRST(!) line of our data stream
xCode.Insert(0, xCurrentLabel + "\r\n");
//THINK ABOUT THE ORDER that he above lines occur in and where they insert data into the stream - don't switch it!
AD7Util.Log("SendAssembly - Sending through pipe now");
mDebugDownPipe.SendCommand(Debugger2Windows.AssemblySource, Encoding.UTF8.GetBytes(xCode.ToString()));
AD7Util.Log("SendAssembly - Done");
}
}
public void WaitForAssemblyUpdate()
{
ASMWindow_CurrentLineUpdated.WaitOne(5000);
ASMWindow_NextAddress1Updated.WaitOne(5000);
ASMWindow_NextLine1Updated.WaitOne(5000);
}
//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);
}
}
}