Cosmos/source2/Debug/Cosmos.Debug.VSDebugEngine/AD7.Impl/AD7Engine.cs
kudzu_cp 8dc36c13e7 Cleanup and fixes to debugger engine for breakpoints. All breakpoints should be set properly now before booting.
Still some other issues to solve with VS hanging now on some breakpoints.
2012-01-15 19:35:49 +00:00

544 lines
24 KiB
C#

using Cosmos.Debug.Common;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Debugger.Interop;
using Shell = Microsoft.VisualStudio.Shell;
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using System.Collections.Specialized;
using System.Windows.Forms;
namespace Cosmos.Debug.VSDebugEngine {
// AD7Engine is the primary entrypoint object for the the debugger.
//
// It implements:
//
// IDebugEngine2: This interface represents a debug engine (DE). It is used to manage various aspects of a debugging session,
// from creating breakpoints to setting and clearing exceptions.
//
// IDebugEngineLaunch2: Used by a debug engine (DE) to launch and terminate programs.
//
// IDebugProgram3: This interface represents a program that is running in a process. Since this engine only debugs one process at a time and each
// process only contains one program, it is implemented on the engine.
//
// IDebugEngineProgram2: This interface provides simultanious debugging of multiple threads in a debuggee.
[ComVisible(true)]
[Guid("8355452D-6D2F-41b0-89B8-BB2AA2529E94")]
public class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgram3, IDebugEngineProgram2 {
internal AD7Process mProcess;
internal IDebugProgram2 mProgram;
// A unique identifier for the program being debugged.
Guid mProgramID;
public const string ID = "FA1DA3A6-66FF-4c65-B077-E65F7164EF83";
internal AD7Module mModule;
private AD7Thread mThread;
private AD7ProgramNode mProgNode;
public bool AfterBreak = false;
public IList<IDebugBoundBreakpoint2> Breakpoints = null;
// This object facilitates calling from this thread into the worker thread of the engine. This is necessary because the Win32 debugging
// api requires thread affinity to several operations.
// This object manages breakpoints in the sample engine.
protected BreakpointManager mBPMgr;
public BreakpointManager BPMgr {
get { return mBPMgr; }
}
public AD7Engine() {
mBPMgr = new BreakpointManager(this);
}
// Used to send events to the debugger. Some examples of these events are thread create, exception thrown, module load.
EngineCallback mEngineCallback;
internal EngineCallback Callback {
get { return mEngineCallback; }
}
#region Startup Methods
// During startup these methods are called in this order:
// -LaunchSuspended
// -ResumeProcess
// -Attach - Triggered by Attach
int IDebugEngineLaunch2.LaunchSuspended(string aPszServer, IDebugPort2 aPort, string aDebugInfo
, string aArgs, string aDir, string aEnv, string aOptions, enum_LAUNCH_FLAGS aLaunchFlags
, uint aStdInputHandle, uint aStdOutputHandle, uint hStdError, IDebugEventCallback2 aAD7Callback
, out IDebugProcess2 aProcess) {
// Launches a process by means of the debug engine.
// Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger
// to the suspended program. However, there are circumstances in which the debug engine may need to launch a program
// (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language),
// in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method
// The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state.
aProcess = null;
try {
mEngineCallback = new EngineCallback(this, aAD7Callback);
var xDebugInfo = new NameValueCollection();
NameValueCollectionHelper.LoadFromString(xDebugInfo, aDebugInfo);
//TODO: In the future we might support command line args for kernel etc
//string xCmdLine = EngineUtils.BuildCommandLine(exe, args);
//var processLaunchInfo = new ProcessLaunchInfo(exe, xCmdLine, dir, env, options, launchFlags, hStdInput, hStdOutput, hStdError);
AD7EngineCreateEvent.Send(this);
var xProcess = new AD7Process(xDebugInfo, mEngineCallback, this, aPort);
aProcess = mProcess = xProcess;
mProgramID = xProcess.mID;
//AD7ThreadCreateEvent.Send(this, xProcess.Thread);
mModule = new AD7Module();
mProgNode = new AD7ProgramNode(EngineUtils.GetProcessId(xProcess));
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpProgramNodes, uint aCeltPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) {
// Attach the debug engine to a program.
//
// Attach can either be called to attach to a new process, or to complete an attach
// to a launched process.
// So could we simplify and move code from LaunchSuspended to here and maybe even
// eliminate the debughost? Although I supposed DebugHost has some other uses as well.
if (aCeltPrograms != 1) {
System.Diagnostics.Debug.Fail("Cosmos Debugger only supports one debug target at a time.");
throw new ArgumentException();
}
try {
int xProcessID = EngineUtils.GetProcessId(rgpPrograms[0]);
if (xProcessID == 0) {
// We only support system processes.
// What other kinds of processes are there?
return VSConstants.E_NOTIMPL;
}
EngineUtils.RequireOk(rgpPrograms[0].GetProgramId(out mProgramID));
mProgram = rgpPrograms[0];
AD7EngineCreateEvent.Send(this);
AD7ProgramCreateEvent.Send(this);
AD7ModuleLoadEvent.Send(this, mModule, true);
// Dummy main thread
// We dont support threads yet, but the debugger expects threads.
// So we create a dummy object to represente our only "thread".
mThread = new AD7Thread(this, mProcess);
AD7LoadCompleteEvent.Send(this, mThread);
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
int IDebugEngineLaunch2.ResumeProcess(IDebugProcess2 aProcess) {
// Resume a process launched by IDebugEngineLaunch2.LaunchSuspended
try {
int xProcessID = EngineUtils.GetProcessId(aProcess);
// Send a program node to the SDM. This will cause the SDM to turn around and call IDebugEngine2.Attach
// which will complete the hookup with AD7
var xProcess = aProcess as AD7Process;
if (xProcess == null) {
Trace.WriteLine("No AD7Process retrieved!");
return VSConstants.E_INVALIDARG;
}
IDebugPort2 xPort;
EngineUtils.RequireOk(aProcess.GetPort(out xPort));
var xDefPort = (IDebugDefaultPort2)xPort;
IDebugPortNotify2 xNotify;
EngineUtils.RequireOk(xDefPort.GetPortNotify(out xNotify));
// This triggers Attach
EngineUtils.RequireOk(xNotify.AddProgramNode(mProgNode));
Callback.OnModuleLoad(mModule);
Callback.OnSymbolSearch(mModule, xProcess.mISO.Replace("iso", "pdb"), enum_MODULE_INFO_FLAGS.MIF_SYMBOLS_LOADED);
// Important!
//
// This call triggers setting of breakpoints that exist before run.
// So it must be called before we resume the process.
// If not called VS will call it after our 3 startup events, but thats too late.
// This line was commented out in earlier Cosmos builds and caused problems with
// breakpoints and timing.
Callback.OnThreadStart(mThread);
// Not sure what this does exactly. It was commented out before
// but so was a lot of stuff we actually needed. If its uncommented it
// throws:
// "Operation is not valid due to the current state of the object."
//AD7EntrypointEvent.Send(this);
// Now finally release our process to go after breakpoints are set
mProcess.ResumeFromLaunch();
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
#endregion
#region Other implemented support methods
int IDebugEngine2.ContinueFromSynchronousEvent(IDebugEvent2 aEvent) {
// Called by the SDM to indicate that a synchronous debug event, previously sent by the DE to the SDM,
// was received and processed. The only event the engine sends in this fashion is Program Destroy.
// It responds to that event by shutting down the engine.
// Kudzu: I dont think we currently use this.
try {
if (aEvent is AD7ProgramDestroyEvent) {
mEngineCallback = null;
mProgramID = Guid.Empty;
mThread = null;
mProgNode = null;
} else {
System.Diagnostics.Debug.Fail("Unknown synchronious event");
}
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
int IDebugEngine2.CreatePendingBreakpoint(IDebugBreakpointRequest2 pBPRequest, out IDebugPendingBreakpoint2 ppPendingBP) {
// Creates a pending breakpoint in the engine. A pending breakpoint is contains all the information needed to bind a breakpoint to
// a location in the debuggee.
ppPendingBP = null;
try {
BPMgr.CreatePendingBreakpoint(pBPRequest, out ppPendingBP);
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
int IDebugEngine2.DestroyProgram(IDebugProgram2 pProgram) {
// Informs a DE that the program specified has been atypically terminated and that the DE should
// clean up all references to the program and send a program destroy event.
//
// Tell the SDM that the engine knows that the program is exiting, and that the
// engine will send a program destroy. We do this because the Win32 debug api will always
// tell us that the process exited, and otherwise we have a race condition.
return AD7_HRESULT.E_PROGRAM_DESTROY_PENDING;
}
int IDebugEngine2.GetEngineId(out Guid oGuidEngine) {
// Gets the GUID of the DebugEngine.
oGuidEngine = new Guid(ID);
return VSConstants.S_OK;
}
int IDebugEngineLaunch2.TerminateProcess(IDebugProcess2 aProcess) {
// This function is used to terminate a process that the SampleEngine launched
// The debugger will call IDebugEngineLaunch2::CanTerminateProcess before calling this method.
try {
// We only support one debugee, but if we support more in the future
// this can be use to identify which one this method applies to.
int xProcessID = EngineUtils.GetProcessId(aProcess);
mProcess.Terminate();
mEngineCallback.OnProcessExit(0);
mProgram = null;
} catch (Exception e) {
return EngineUtils.UnexpectedException(e);
}
return VSConstants.S_OK;
}
public int Continue(IDebugThread2 aThread) {
// Continue is called from the SDM when it wants execution to continue in the debugee
// but have stepping state remain. An example is when a tracepoint is executed,
// and the debugger does not want to actually enter break mode.
var xThread = (AD7Thread)aThread;
if (AfterBreak) {
//Callback.OnBreak(xThread);
}
return VSConstants.S_OK;
}
int IDebugEngine2.CauseBreak() {
// Requests that all programs being debugged by this DE stop execution the next time one of their threads attempts to run.
// This is normally called in response to the user clicking on the pause button in the debugger.
// When the break is complete, an AsyncBreakComplete event will be sent back to the debugger.
return ((IDebugProgram2)this).CauseBreak();
}
public int Detach() {
// Detach is called when debugging is stopped and the process was attached to (as opposed to launched)
// or when one of the Detach commands are executed in the UI.
BPMgr.ClearBoundBreakpoints();
return VSConstants.S_OK;
}
public int EnumModules(out IEnumDebugModules2 ppEnum) {
// EnumModules is called by the debugger when it needs to enumerate the modules in the program.
ppEnum = new AD7ModuleEnum(new[] { mModule });
return VSConstants.S_OK;
}
public int EnumThreads(out IEnumDebugThreads2 ppEnum) {
// EnumThreads is called by the debugger when it needs to enumerate the threads in the program.
ppEnum = new AD7ThreadEnum(new[] { mThread });
return VSConstants.S_OK;
}
public int GetEngineInfo(out string engineName, out Guid engineGuid) {
// Gets the name and identifier of the debug engine (DE) running this program.
engineName = ResourceStrings.EngineName;
engineGuid = new Guid(AD7Engine.ID);
return VSConstants.S_OK;
}
public int GetProgramId(out Guid aGuidProgramId) {
// Gets a GUID for this program. A debug engine (DE) must return the program identifier originally passed to the IDebugProgramNodeAttach2::OnAttach
// or IDebugEngine2::Attach methods. This allows identification of the program across debugger components.
aGuidProgramId = mProgramID;
return VSConstants.S_OK;
}
public int Step(IDebugThread2 pThread, enum_STEPKIND sk, enum_STEPUNIT Step) {
// This method is deprecated. Use the IDebugProcess3::Step method instead.
mProcess.Step((enum_STEPKIND)sk);
return VSConstants.S_OK;
}
public int ExecuteOnThread(IDebugThread2 pThread) {
// ExecuteOnThread is called when the SDM wants execution to continue and have
// stepping state cleared.
mProcess.Continue();
return VSConstants.S_OK;
}
#endregion
#region Unimplemented methods
// Gets the name of the program.
// The name returned by this method is always a friendly, user-displayable name that describes the program.
public int GetName(out string programName) {
// The Sample engine uses default transport and doesn't need to customize the name of the program,
// so return NULL.
programName = null;
return VSConstants.S_OK;
}
// This method gets the Edit and Continue (ENC) update for this program. A custom debug engine always returns E_NOTIMPL
public int GetENCUpdate(out object update) {
// The sample engine does not participate in managed edit & continue.
update = null;
return VSConstants.S_OK;
}
// Removes the list of exceptions the IDE has set for a particular run-time architecture or language.
// We dont support exceptions in the debuggee so this method is not actually implemented.
int IDebugEngine2.RemoveAllSetExceptions(ref Guid guidType) {
return VSConstants.S_OK;
}
// Removes the specified exception so it is no longer handled by the debug engine.
// The sample engine does not support exceptions in the debuggee so this method is not actually implemented.
int IDebugEngine2.RemoveSetException(EXCEPTION_INFO[] pException) {
// We stop on all exceptions.
return VSConstants.S_OK;
}
// Specifies how the DE should handle a given exception.
// We dont support exceptions in the debuggee so this method is not actually implemented.
int IDebugEngine2.SetException(EXCEPTION_INFO[] pException) {
return VSConstants.S_OK;
}
// Sets the locale of the DE.
// This method is called by the session debug manager (SDM) to propagate the locale settings of the IDE so that
// strings returned by the DE are properly localized. The sample engine is not localized so this is not implemented.
int IDebugEngine2.SetLocale(ushort wLangID) {
return VSConstants.S_OK;
}
// A metric is a registry value used to change a debug engine's behavior or to advertise supported functionality.
// This method can forward the call to the appropriate form of the Debugging SDK Helpers function, SetMetric.
int IDebugEngine2.SetMetric(string pszMetric, object varValue) {
// The sample engine does not need to understand any metric settings.
return VSConstants.S_OK;
}
// Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored
// This allows the debugger to tell the engine where that location is.
int IDebugEngine2.SetRegistryRoot(string pszRegistryRoot) {
// The sample engine does not read settings from the registry.
return VSConstants.S_OK;
}
public string GetAddressDescription(uint ip) {
// DebuggedModule module = m_debuggedProcess.ResolveAddress(ip);
return "";// EngineUtils.GetAddressDescription(module, ip);
}
// Determines if a debug engine (DE) can detach from the program.
public int CanDetach() {
// We always support detach
return VSConstants.S_OK;
}
// The debugger calls CauseBreak when the user clicks on the pause button in VS. The debugger should respond by entering
// breakmode.
public int CauseBreak() {
//m_pollThread.RunOperation(new Operation(delegate {
// //m_debuggedProcess.Break();
//}));
return VSConstants.S_OK;
}
// EnumCodePaths is used for the step-into specific feature -- right click on the current statment and decide which
// function to step into. This is not something that the SampleEngine supports.
public int EnumCodePaths(string hint, IDebugCodeContext2 start, IDebugStackFrame2 frame, int fSource, out IEnumCodePaths2 pathEnum, out IDebugCodeContext2 safetyContext) {
pathEnum = null;
safetyContext = null;
return VSConstants.E_NOTIMPL;
}
// The properties returned by this method are specific to the program. If the program needs to return more than one property,
// then the IDebugProperty2 object returned by this method is a container of additional properties and calling the
// IDebugProperty2::EnumChildren method returns a list of all properties.
// A program may expose any number and type of additional properties that can be described through the IDebugProperty2 interface.
// An IDE might display the additional program properties through a generic property browser user interface.
// The sample engine does not support this
public int GetDebugProperty(out IDebugProperty2 ppProperty) {
throw new Exception("The method or operation is not implemented.");
}
// The debugger calls this when it needs to obtain the IDebugDisassemblyStream2 for a particular code-context.
// The sample engine does not support dissassembly so it returns E_NOTIMPL
public int GetDisassemblyStream(enum_DISASSEMBLY_STREAM_SCOPE dwScope, IDebugCodeContext2 codeContext, out IDebugDisassemblyStream2 disassemblyStream) {
disassemblyStream = null;
return VSConstants.E_NOTIMPL;
}
// The memory bytes as represented by the IDebugMemoryBytes2 object is for the program's image in memory and not any memory
// that was allocated when the program was executed.
public int GetMemoryBytes(out IDebugMemoryBytes2 ppMemoryBytes) {
throw new Exception("The method or operation is not implemented.");
}
// Writes a dump to a file.
public int WriteDump(enum_DUMPTYPE DUMPTYPE, string pszDumpUrl) {
// The sample debugger does not support creating or reading mini-dumps.
return VSConstants.E_NOTIMPL;
}
// Stops all threads running in this program.
// This method is called when this program is being debugged in a multi-program environment. When a stopping event from some other program
// is received, this method is called on this program. The implementation of this method should be asynchronous;
// that is, not all threads should be required to be stopped before this method returns. The implementation of this method may be
// as simple as calling the IDebugProgram2::CauseBreak method on this program.
//
// The sample engine only supports debugging native applications and therefore only has one program per-process
public int Stop() {
throw new Exception("The method or operation is not implemented.");
}
// WatchForExpressionEvaluationOnThread is used to cooperate between two different engines debugging
// the same process. The sample engine doesn't cooperate with other engines, so it has nothing
// to do here.
public int WatchForExpressionEvaluationOnThread(IDebugProgram2 pOriginatingProgram, uint dwTid, uint dwEvalFlags, IDebugEventCallback2 pExprCallback, int fWatch) {
return VSConstants.S_OK;
}
// WatchForThreadStep is used to cooperate between two different engines debugging the same process.
// The sample engine doesn't cooperate with other engines, so it has nothing to do here.
public int WatchForThreadStep(IDebugProgram2 pOriginatingProgram, uint dwTid, int fWatch, uint dwFrame) {
return VSConstants.S_OK;
}
// Terminates the program.
public int Terminate() {
mProgram = null;
// Because the sample engine is a native debugger, it implements IDebugEngineLaunch2, and will terminate
// the process in IDebugEngineLaunch2.TerminateProcess
return VSConstants.S_OK;
}
// Enumerates the code contexts for a given position in a source file.
public int EnumCodeContexts(IDebugDocumentPosition2 pDocPos, out IEnumDebugCodeContexts2 ppEnum) {
throw new Exception("The method or operation is not implemented.");
}
// Determines if a process can be terminated.
int IDebugEngineLaunch2.CanTerminateProcess(IDebugProcess2 process) {
return VSConstants.S_OK;
//try {
// int processId = EngineUtils.GetProcessId(process);
// //if (processId == m_debuggedProcess.Id)
// {
// return VSConstants.S_OK;
// }
// //else
// {
// //return VSConstants.S_FALSE;
// }
//}
// //catch (ComponentException e)
// //{
// // return e.HResult;
// //}
//catch (Exception e) {
// return EngineUtils.UnexpectedException(e);
//}
}
#endregion
#region Deprecated interface methods
// These methods are not called by the Visual Studio debugger, so they don't need to be implemented
int IDebugEngine2.EnumPrograms(out IEnumDebugPrograms2 programs) {
System.Diagnostics.Debug.Fail("This function is not called by the debugger");
programs = null;
return VSConstants.E_NOTIMPL;
}
public int Attach(IDebugEventCallback2 pCallback) {
System.Diagnostics.Debug.Fail("This function is not called by the debugger");
return VSConstants.E_NOTIMPL;
}
public int GetProcess(out IDebugProcess2 process) {
System.Diagnostics.Debug.Fail("This function is not called by the debugger");
process = null;
return VSConstants.E_NOTIMPL;
}
public int Execute() {
System.Diagnostics.Debug.Fail("This function is not called by the debugger.");
return VSConstants.E_NOTIMPL;
}
#endregion
}
}