Cosmos/source/Cosmos.VS.DebugEngine/AD7.Impl/AD7Process.cs
2017-03-19 09:29:46 -05:00

1234 lines
49 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Cosmos.Debug.DebugConnectors;
using Cosmos.Debug.Symbols;
using Cosmos.VS.DebugEngine.Engine.Impl;
using Cosmos.VS.DebugEngine.Utilities;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Debugger.Interop;
using Label = Cosmos.Debug.Symbols.Label;
using Cosmos.Build.Common;
using Cosmos.Debug.Common;
using Cosmos.Debug.Hosts;
using Cosmos.VS.DebugEngine.Properties;
namespace Cosmos.VS.DebugEngine.AD7.Impl
{
public partial class AD7Process : IDebugProcess2
{
public bool ASMSteppingMode = false;
public Guid ID = Guid.NewGuid();
protected EngineCallback mCallback;
public AD7Thread mThread;
protected AD7Engine mEngine;
public uint? mCurrentAddress = null;
public uint? mNextAddress1 = null;
public string mCurrentASMLine = null;
public string mNextASMLine1 = null;
protected readonly Dictionary<string, string> 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<uint, uint, int> ASMBPToStepTo = null;
//ASM Breakpoints stored as C# Address -> ASM Address, C# BP ID
//Allows quick look-up on INT3 occurring
private List<Tuple<uint, uint, int>> ASMBreakpoints = new List<Tuple<uint, uint, 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 mHost;
public string mISO;
public string mProjectFile;
protected void DbgCmdRegisters(byte[] aData)
{
mDebugDownPipe.SendCommand(Debugger2Windows.Registers, aData);
if (aData.Length < 40)
{
mCurrentAddress = null;
}
else
{
uint x32 = (uint)
(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);
uint xAddress = mDebugInfoDb.GetAddressOfLabel(xLabel);
mDbgConnector.SetAsmBreakpoint(xAddress);
mDbgConnector.Continue();
}
break;
case Windows2Debugger.ToggleAsmBreak2:
{
string xLabel = Encoding.UTF8.GetString(aData);
uint xAddress = mDebugInfoDb.GetAddressOfLabel(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.GetAddressOfLabel(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<uint, uint, int>> GetASMBreakpointInfoFromCSAddress(uint csAddress)
{
return ASMBreakpoints.Where(x => x.Item1 == csAddress).ToList();
}
private Tuple<uint, uint, int> GetASMBreakpointInfoFromASMAddress(uint asmAddress)
{
Tuple<uint, uint, int> result = null;
var posBPs = ASMBreakpoints.Where(x => x.Item2 == asmAddress);
if (posBPs.Any())
{
result = posBPs.First();
}
return result;
}
private void SetASMBreakpoint(uint aAddress)
{
if (GetASMBreakpointInfoFromASMAddress(aAddress) == null)
{
bool set = false;
for (int xID = 0; xID < BreakpointManager.MaxBP; xID++)
{
if (mEngine.BPMgr.mActiveBPs[xID] == null)
{
uint CSBPAddress = mDebugInfoDb.GetClosestCSharpBPAddress(aAddress);
ASMBreakpoints.Add(new Tuple<uint, uint, 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(uint 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[BuildPropertyNames.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[BuildPropertyNames.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[BuildPropertyNames.LaunchString];
OutputText("Starting debug connector.");
switch (xPortType)
{
case "pipe:":
mDbgConnector = new DebugConnectorPipeServer(xPortParam);
break;
#if SERIAL_PORT
case "serial:":
if (xLaunch == "IntelEdison")
{
mDbgConnector = new DebugConnectorEdison(xPortParam, Path.ChangeExtension(mDebugInfo["ISOFile"], ".bin"));
}
else
{
mDbgConnector = new DebugConnectorSerial(xPortParam);
}
break;
#endif
default:
throw new Exception("No debug connector found for port type '" + xPortType + "'");
}
mDbgConnector.SetConnectionHandler(DebugConnectorConnected);
mDbgConnector.CmdBreak += new Action<uint>(DbgCmdBreak);
mDbgConnector.CmdTrace += new Action<uint>(DbgCmdTrace);
mDbgConnector.CmdText += new Action<string>(DbgCmdText);
mDbgConnector.CmdSimpleNumber += new Action<uint>(DbgCmdSimpleNumber);
mDbgConnector.CmdKernelPanic += new Action<uint>(DbgCmdKernelPanic);
mDbgConnector.CmdSimpleLongNumber += new Action<ulong>(DbgCmdSimpleLongNumber);
mDbgConnector.CmdComplexNumber += new Action<float>(DbgCmdComplexNumber);
mDbgConnector.CmdComplexLongNumber += new Action<double>(DbgCmdComplexLongNumber);
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.CmdStackOverflowOccurred += DbgCmdStackOverflowOccurred;
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)
{
AD7Util.MessageBox(String.Format("Stack corruption occurred at address 0x{0:X8}! Halting now.", lastEIPAddress));
}
private void DbgCmdStackOverflowOccurred(uint lastEIPAddress)
{
AD7Util.MessageBox(String.Format("Stack overflow occurred at address 0x{0:X8}! Halting now.", lastEIPAddress));
}
private void DbgCmdNullReferenceOccurred(uint lastEIPAddress)
{
AD7Util.MessageBox(String.Format("NullReferenceException occurred at address 0x{0:X8}! Halting now.", lastEIPAddress));
}
private void DbgCmdMessageBox(string message)
{
AD7Util.MessageBox("Message from your Cosmos operating system:\r\n\r\n" + message);
}
internal AD7Process(Dictionary<string, string> aDebugInfo, EngineCallback aCallback, AD7Engine aEngine, IDebugPort2 aPort)
{
mCallback = aCallback;
mDebugInfo = aDebugInfo;
mLaunch = (LaunchType)Enum.Parse(typeof(LaunchType), aDebugInfo[BuildPropertyNames.LaunchString]);
if (mDebugDownPipe == null)
{
mDebugDownPipe = new PipeClient(Pipes.DownName);
mDebugUpPipe = new 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[BuildPropertyNames.EnableGDBString], "true", StringComparison.InvariantCultureIgnoreCase);
OutputText("GDB " + (xUseGDB ? "Enabled" : "Disabled") + ".");
//
var xGDBClient = false;
Boolean.TryParse(mDebugInfo[BuildPropertyNames.StartCosmosGDBString], out xGDBClient);
switch (mLaunch)
{
case LaunchType.VMware:
#region CheckIfHyperVServiceIsRunning
using (System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController("vmms"))
{
try
{
if (sc.Status == System.ServiceProcess.ServiceControllerStatus.Running)
{
AD7Util.MessageBox(
"The Hyper-V Virtual Machine Management Service will be stopped. This is needed to allow to run VMware. If you press \"No\" the debug will stop.",
"Question");
sc.Stop();
}
}
catch (InvalidOperationException)
{
// service not present
}
}
#endregion CheckIfHyperVServiceIsRunning
mHost = new VMware(mDebugInfo, xUseGDB);
break;
#if SERIAL_PORT
case LaunchType.Slave:
mHost = new Slave(mDebugInfo, xUseGDB);
break;
#endif
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(Resources.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 Bochs(mDebugInfo, xUseGDB, bochsConfigurationFile);
//((Host.Bochs)mHost).FixBochsConfiguration(new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("IsoFileName", mISO) });
break;
case LaunchType.IntelEdison:
mHost = new 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
{
AD7Util.MessageBox(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");
}
}
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 DbgCmdSimpleNumber(uint nr)
{
mCallback.OnOutputStringUser("0x" + nr.ToString("X8").ToUpper() + "\r\n");
}
void DbgCmdKernelPanic(uint nr)
{
AD7Util.MessageBox("Kernel panic: 0x" + nr.ToString());
}
void DbgCmdSimpleLongNumber(ulong nr)
{
mCallback.OnOutputStringUser("0x" + nr.ToString("X8").ToUpper() + "\r\n");
}
void DbgCmdComplexNumber(float nr)
{
mCallback.OnOutputStringUser(nr + "\r\n");
}
void DbgCmdComplexLongNumber(double nr)
{
mCallback.OnOutputStringUser(nr + "\r\n");
}
void DbgCmdText(string obj)
{
mCallback.OnOutputStringUser(obj + "\r\n");
}
internal AD7Thread Thread
{
get
{
return mThread;
}
}
void DbgCmdTrace(uint aAddress)
{
DebugMsg("TraceReceived: " + aAddress);
}
void DbgCmdBreak(uint 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!");
AD7Util.MessageBox("Failed to stop debugger! You need to install the VMWare VIX API!", "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!");
AD7Util.MessageBox(
"Failed to stop debugger! You probably need to install the VMWare VIX API!\r\n\r\nCheck Output window for more details.",
"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
uint 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
AD7Util.MessageBox("Step backwards is not supported.");
mCallback.OnStepComplete(); // Have to call this otherwise VS gets "stuck"
}
else
{
AD7Util.MessageBox("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<uint, string>> INT3sSet = new List<KeyValuePair<uint, 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<uint, 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<uint>();
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;
uint? 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();
uint 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);
}
}
}