using System; using System.Collections.Generic; using System.Text; using Microsoft.VisualStudio.Debugger.Interop; using System.Diagnostics; using System.Collections.ObjectModel; using Cosmos.Compiler.Debug; using System.Runtime.InteropServices; using System.Windows.Forms; using System.IO; namespace Cosmos.Debug.VSDebugEngine { public class EngineCallback //: ISampleEngineCallback { readonly IDebugEventCallback2 m_ad7Callback; readonly AD7Engine m_engine; public EngineCallback(AD7Engine engine, IDebugEventCallback2 ad7Callback) { m_ad7Callback = ad7Callback; m_engine = engine; } public void Send(IDebugEvent2 eventObject, string iidEvent, IDebugProgram2 program, IDebugThread2 thread) { uint attributes; Guid riidEvent = new Guid(iidEvent); EngineUtils.RequireOk(eventObject.GetAttributes(out attributes)); EngineUtils.RequireOk(m_ad7Callback.Event(m_engine, null, program, thread, eventObject, ref riidEvent, attributes)); } public void Send(IDebugEvent2 eventObject, string iidEvent, IDebugThread2 thread) { Send(eventObject, iidEvent, m_engine, thread); } public void OnError(int hrErr) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); // IDebugErrorEvent2 is used to report error messages to the user when something goes wrong in the debug engine. // The sample engine doesn't take advantage of this. } public void OnModuleLoad(AD7Module aModule) { // This will get called when the entrypoint breakpoint is fired because the engine sends a mod-load event // for the exe. //if (m_engine.DebuggedProcess != null) //{ // System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); //} AD7ModuleLoadEvent eventObject = new AD7ModuleLoadEvent(aModule, true /* this is a module load */); Send(eventObject, AD7ModuleLoadEvent.IID, null); } public void OnModuleUnload()//DebuggedModule debuggedModule) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); //AD7Module ad7Module = (AD7Module)debuggedModule.Client; //System.Diagnostics.Debug.Assert(ad7Module != null); //AD7ModuleLoadEvent eventObject = new AD7ModuleLoadEvent(ad7Module, false /* this is a module unload */); //Send(eventObject, AD7ModuleLoadEvent.IID, null); } // Call this one for internal Cosmos dev. // Can be turned off and should be turned off by default. Use an IFDEF or something. public void OnOutputString(string outputString) { if (false) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); var eventObject = new AD7OutputDebugStringEvent(outputString); Send(eventObject, AD7OutputDebugStringEvent.IID, null); } } // This is the user version, messages directly from Cosmos user code public void OnOutputStringUser(string outputString) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); var eventObject = new AD7OutputDebugStringEvent(outputString); Send(eventObject, AD7OutputDebugStringEvent.IID, null); } public void OnProcessExit(uint exitCode) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); AD7ProgramDestroyEvent eventObject = new AD7ProgramDestroyEvent(exitCode); Send(eventObject, AD7ProgramDestroyEvent.IID, null); } public void OnThreadExit()//DebuggedThread debuggedThread, uint exitCode) { //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); //AD7Thread ad7Thread = (AD7Thread)debuggedThread.Client; //System.Diagnostics.Debug.Assert(ad7Thread != null); //AD7ThreadDestroyEvent eventObject = new AD7ThreadDestroyEvent(exitCode); //Send(eventObject, AD7ThreadDestroyEvent.IID, ad7Thread); } public void OnThreadStart(AD7Thread debuggedThread) { // This will get called when the entrypoint breakpoint is fired because the engine sends a thread start event // for the main thread of the application. //if (m_engine.DebuggedProcess != null) //{ // System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); //} AD7ThreadCreateEvent eventObject = new AD7ThreadCreateEvent(); Send(eventObject, AD7ThreadCreateEvent.IID, debuggedThread); } public void OnBreak(AD7Thread aThread) { var mBreak = new AD7BreakEvent(); Send(mBreak, AD7BreakEvent.IID, aThread); } public void OnBreakpoint(AD7Thread thread, IList clients) { var boundBreakpoints = new IDebugBoundBreakpoint2[clients.Count]; int i = 0; foreach (var objCurrentBreakpoint in clients) { boundBreakpoints[i] = objCurrentBreakpoint; i++; } //// IDebugPendingBreakpoint2 ppend; IDebugBreakpointRequest2 req; BP_REQUEST_INFO[] reqinf = new BP_REQUEST_INFO[i]; ((IDebugBoundBreakpoint2)boundBreakpoints[0]).GetPendingBreakpoint(out ppend); ppend.GetBreakpointRequest(out req); req.GetRequestInfo(enum_BPREQI_FIELDS.BPREQI_BPLOCATION, reqinf); IDebugDocumentPosition2 docPosition = (IDebugDocumentPosition2)(Marshal.GetObjectForIUnknown(reqinf[0].bpLocation.unionmember2)); // Get the name of the document that the breakpoint was put in string documentName; EngineUtils.CheckOk(docPosition.GetFileName(out documentName)); // Get the location in the document that the breakpoint is in. TEXT_POSITION[] startPosition = new TEXT_POSITION[1]; TEXT_POSITION[] endPosition = new TEXT_POSITION[1]; EngineUtils.CheckOk(docPosition.GetRange(startPosition, endPosition)); //TODO: In future put enough info in cgdb so we can just look this up right away //NOTE: For BPs we already can go from c# line to EIP and reverse.. which I think requires // going through the IL labels.. if so I think this code can be shortened and I dont think we need // to go through the cs file? // Get C# lines String xFile; using (var xTR = new StreamReader(documentName)) { xFile = xTR.ReadToEnd(); } xFile = xFile.Replace('\r', ' '); string[] xFileLines = xFile.Split('\n'); string xMethod = xFileLines[startPosition[0].dwLine - 1]; string[] xMethodParts = xMethod.Split(' '); for (int j = 0; j < xMethodParts.Length; j++) { if (xMethodParts[j].Contains("()")) { xMethod = xMethodParts[j]; break; } } xMethod = "_" + xMethod; xMethod = xMethod.Replace("()", "__:"); // Get ASM lines int xStart = documentName.LastIndexOf('\\'); int xStop = documentName.LastIndexOf('.'); string xFileName = documentName.Substring(0, xStart); xFileName = Path.GetDirectoryName(documentName); xFileName = Path.Combine(xFileName, "bin", "Debug"); // TODO: Error checking for no return files. string[] xFiles = Directory.GetFiles(xFileName, "*.asm"); xFileName = Path.Combine(xFileName, xFiles[0]); using (var xTR = new StreamReader(xFileName)) { xFile = xTR.ReadToEnd(); } xFile = xFile.Replace('\r', ' '); xFile = xFile.Trim(); xFileLines = xFile.Split('\n'); int k = 0, l = 0; for (int j = 0; j < xFileLines.Length; j++) { if (xFileLines[j].Contains(xMethod)) { k = j; j++; } if ((k != 0) && (xFileLines[j].Contains(":"))) { l = j - 2; break; } } //MessageBox.Show(xFileLines[k]); //MessageBox.Show(xFileLines[l]); //MessageBox.Show(k.ToString()); //MessageBox.Show(l.ToString()); //MessageBox.Show("Name: " + documentName); //MessageBox.Show("Start Pos: " + startPosition[0].dwLine + " " + startPosition[0].dwColumn); //MessageBox.Show("End Pos: " + endPosition[0].dwLine + " " + endPosition[0].dwColumn); var xData = new StringBuilder(); for (int j = k; j < l; j++) { xData.AppendLine(xFileLines[j]); } DebugWindows.SendCommand(DwMsgType.AssemblySource, Encoding.ASCII.GetBytes(xData.ToString())); // An engine that supports more advanced breakpoint features such as hit counts, conditions and filters // should notify each bound breakpoint that it has been hit and evaluate conditions here. // The sample engine does not support these features. var boundBreakpointsEnum = new AD7BoundBreakpointsEnum(boundBreakpoints); var eventObject = new AD7BreakpointEvent(boundBreakpointsEnum); var ad7Thread = (AD7Thread)thread; Send(eventObject, AD7BreakpointEvent.IID, ad7Thread); } public void OnException()//DebuggedThread thread, uint code) { // Exception events are sent when an exception occurs in the debuggee that the debugger was not expecting. // The sample engine does not support these. throw new Exception("The method or operation is not implemented."); } public void OnStepComplete()//DebuggedThread thread) { AD7StepCompletedEvent.Send(m_engine); } public void OnAsyncBreakComplete(AD7Thread aThread) { // This will get called when the engine receives the breakpoint event that is created when the user // hits the pause button in vs. //System.Diagnostics.Debug.Assert(Worker.CurrentThreadId == m_engine.DebuggedProcess.PollThreadId); var xEvent = new AD7AsyncBreakCompleteEvent(); Send(xEvent, AD7AsyncBreakCompleteEvent.IID, aThread); //AD7Thread ad7Thread = (AD7Thread)thread.Client; //AD7AsyncBreakCompleteEvent eventObject = new AD7AsyncBreakCompleteEvent(); //Send(eventObject, AD7AsyncBreakCompleteEvent.IID, ad7Thread); } public void OnLoadComplete(AD7Thread aThread) { var xMsg = new AD7LoadCompleteEvent(); Send(xMsg, AD7LoadCompleteEvent.IID, aThread); //AD7Thread ad7Thread = (AD7Thread)thread.Client; //AD7LoadCompleteEvent eventObject = new AD7LoadCompleteEvent(); //Send(eventObject, AD7LoadCompleteEvent.IID, ad7Thread); } public void OnProgramDestroy(uint exitCode) { AD7ProgramDestroyEvent eventObject = new AD7ProgramDestroyEvent(exitCode); Send(eventObject, AD7ProgramDestroyEvent.IID, null); } // Engines notify the debugger about the results of a symbol serach by sending an instance // of IDebugSymbolSearchEvent2 public void OnSymbolSearch(AD7Module module, string status, enum_MODULE_INFO_FLAGS dwStatusFlags) { string statusString = (dwStatusFlags == enum_MODULE_INFO_FLAGS.MIF_SYMBOLS_LOADED ? "Symbols Loaded - " : "No symbols loaded") + status; AD7SymbolSearchEvent eventObject = new AD7SymbolSearchEvent(module, statusString, dwStatusFlags); Send(eventObject, AD7SymbolSearchEvent.IID, null); } // Engines notify the debugger that a breakpoint has bound through the breakpoint bound event. public void OnBreakpointBound(object objBoundBreakpoint, uint address) { AD7BoundBreakpoint boundBreakpoint = (AD7BoundBreakpoint)objBoundBreakpoint; IDebugPendingBreakpoint2 pendingBreakpoint; ((IDebugBoundBreakpoint2)boundBreakpoint).GetPendingBreakpoint(out pendingBreakpoint); AD7BreakpointBoundEvent eventObject = new AD7BreakpointBoundEvent((AD7PendingBreakpoint)pendingBreakpoint, boundBreakpoint); Send(eventObject, AD7BreakpointBoundEvent.IID, null); } } internal class AD7BreakEvent : AD7StoppingEvent, IDebugBreakEvent2 { public const string IID = "C7405D1D-E24B-44E0-B707-D8A5A4E1641B"; } }