// 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. int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { Debug.Assert(_pollThread == null); Debug.Assert(_engineCallback == null); Debug.Assert(_debuggedProcess == null); Debug.Assert(_ad7ProgramId == Guid.Empty); // Check if the logger was enabled late. Logger.LoadMIDebugLogger(_configStore); process = null; _engineCallback = new EngineCallback(this, ad7Callback); Exception exception; try { // Note: LaunchOptions.GetInstance can be an expensive operation and may push a wait message loop LaunchOptions launchOptions = LaunchOptions.GetInstance(_configStore, exe, args, dir, options, _engineCallback, TargetEngine.Native, Logger); // We are being asked to debug a process when we currently aren't debugging anything _pollThread = new WorkerThread(Logger); var cancellationTokenSource = new CancellationTokenSource(); using (cancellationTokenSource) { _pollThread.RunOperation(ResourceStrings.InitializingDebugger, cancellationTokenSource, (HostWaitLoop waitLoop) => { try { _debuggedProcess = new DebuggedProcess(true, launchOptions, _engineCallback, _pollThread, _breakpointManager, this, _configStore); } finally { // If there is an exception from the DebuggeedProcess constructor, it is our responsibility to dispose the DeviceAppLauncher, // otherwise the DebuggedProcess object takes ownership. if (_debuggedProcess == null && launchOptions.DeviceAppLauncher != null) { launchOptions.DeviceAppLauncher.Dispose(); } } _pollThread.PostedOperationErrorEvent += _debuggedProcess.OnPostedOperationError; return _debuggedProcess.Initialize(waitLoop, cancellationTokenSource.Token); }); } EngineUtils.RequireOk(port.GetProcess(_debuggedProcess.Id, out process)); return Constants.S_OK; } catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { exception = e; // Return from the catch block so that we can let the exception unwind - the stack can get kind of big } // If we just return the exception as an HRESULT, we will loose our message, so we instead send up an error event, and then // return E_ABORT. Logger.Flush(); SendStartDebuggingError(exception); Dispose(); return Constants.E_ABORT; }
private void Dispose() { WorkerThread pollThread = _pollThread; DebuggedProcess debuggedProcess = _debuggedProcess; _engineCallback = null; _debuggedProcess = null; _pollThread = null; _ad7ProgramId = Guid.Empty; debuggedProcess?.Close(); pollThread?.Close(); }
public ExceptionManager(MICommandFactory commandFactory, WorkerThread worker, ISampleEngineCallback callback, /*OPTIONAL*/ string registryRoot) { Debug.Assert(commandFactory != null, "Missing commandFactory"); Debug.Assert(worker != null, "Missing worker"); Debug.Assert(callback != null, "Missing callback"); _commandFactory = commandFactory; _worker = worker; _callback = callback; _categoryMap = ReadDefaultSettings(registryRoot); }
// Attach the debug engine to a program. int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpProgramNodes, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) { Debug.Assert(_ad7ProgramId == Guid.Empty); if (celtPrograms != 1) { Debug.Fail("SampleEngine only expects to see one program in a process"); throw new ArgumentException(); } try { AD_PROCESS_ID processId = EngineUtils.GetProcessId(rgpPrograms[0]); EngineUtils.RequireOk(rgpPrograms[0].GetProgramId(out _ad7ProgramId)); // Attach can either be called to attach to a new process, or to complete an attach // to a launched process if (_pollThread == null) { // We are being asked to debug a process when we currently aren't debugging anything _pollThread = new WorkerThread(Logger); _engineCallback = new EngineCallback(this, ad7Callback); // Complete the win32 attach on the poll thread _pollThread.RunOperation(new Operation(delegate { throw new NotImplementedException(); })); _pollThread.PostedOperationErrorEvent += _debuggedProcess.OnPostedOperationError; } else { if (!EngineUtils.ProcIdEquals(processId, _debuggedProcess.Id)) { Debug.Fail("Asked to attach to a process while we are debugging"); return Constants.E_FAIL; } } AD7EngineCreateEvent.Send(this); AD7ProgramCreateEvent.Send(this); this.ProgramCreateEventSent = true; return Constants.S_OK; } catch (MIException e) { return e.HResult; } catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { return EngineUtils.UnexpectedException(e); } }
public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, string registryRoot) { uint processExitCode = 0; g_Process = this; _bStarted = false; _pendingMessages = new StringBuilder(400); _worker = worker; _launchOptions = launchOptions; _breakpointManager = bpman; Engine = engine; _libraryLoaded = new List<string>(); _loadOrder = 0; MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = MICommandFactory.SupportsStopOnDynamicLibLoad() ? new WaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; Natvis = new Natvis.Natvis(this); // we do NOT have real Win32 process IDs, so we use a guid AD_PROCESS_ID pid = new AD_PROCESS_ID(); pid.ProcessIdType = (int)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID; pid.guidProcessId = Guid.NewGuid(); this.Id = pid; SourceLineCache = new SourceLineCache(this); _callback = callback; _moduleList = new List<DebuggedModule>(); ThreadCache = new ThreadCache(callback, this); Disassembly = new Disassembly(this); ExceptionManager = new ExceptionManager(MICommandFactory, _worker, _callback, registryRoot); VariablesToDelete = new List<string>(); MessageEvent += delegate (object o, string message) { // We can get messages before we have started the process // but we can't send them on until it is if (_bStarted) { _callback.OnOutputString(message); } else { _pendingMessages.Append(message); } }; LibraryLoadEvent += delegate (object o, EventArgs args) { ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; string file = results.Results.TryFindString("host-name"); if (!string.IsNullOrEmpty(file) && MICommandFactory.SupportsStopOnDynamicLibLoad()) { _libraryLoaded.Add(file); if (_waitDialog != null) { _waitDialog.ShowWaitDialog(file); } } else if (this.MICommandFactory.Mode == MIMode.Clrdbg) { string id = results.Results.FindString("id"); ulong baseAddr = results.Results.FindAddr("base-address"); uint size = results.Results.FindUint("size"); bool symbolsLoaded = results.Results.FindInt("symbols-loaded") != 0; var module = new DebuggedModule(id, file, baseAddr, size, symbolsLoaded, string.Empty, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } else if (!string.IsNullOrEmpty(file)) { string addr = results.Results.TryFindString("loaded_addr"); if (string.IsNullOrEmpty(addr) || addr == "-") { return; // identifies the exe, not a real load } // generate module string id = results.Results.TryFindString("name"); bool symsLoaded = true; string symPath = null; if (results.Results.Contains("symbols-path")) { symPath = results.Results.FindString("symbols-path"); if (string.IsNullOrEmpty(symPath)) { symsLoaded = false; } } else { symPath = file; } ulong loadAddr = results.Results.FindAddr("loaded_addr"); uint size = results.Results.FindUint("size"); if (String.IsNullOrEmpty(id)) { id = file; } var module = FindModule(id); if (module == null) { module = new DebuggedModule(id, file, loadAddr, size, symsLoaded, symPath, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } } }; if (_launchOptions is LocalLaunchOptions) { this.Init(new MICore.LocalTransport(), _launchOptions); } else if (_launchOptions is PipeLaunchOptions) { this.Init(new MICore.PipeTransport(), _launchOptions); } else if (_launchOptions is TcpLaunchOptions) { this.Init(new MICore.TcpTransport(), _launchOptions); } else if (_launchOptions is SerialLaunchOptions) { string port = ((SerialLaunchOptions)_launchOptions).Port; this.Init(new MICore.SerialTransport(port), _launchOptions); } else { throw new ArgumentOutOfRangeException("LaunchInfo.options"); } MIDebugCommandDispatcher.AddProcess(this); // When the debuggee exits, we need to exit the debugger ProcessExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (results.Results.Contains("exit-code")) { processExitCode = results.Results.FindUint("exit-code"); } // quit MI Debugger _worker.PostOperation(CmdExitAsync); if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } }; // When the debugger exits, we tell AD7 we are done DebuggerExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // this is the last AD7 Event we can ever send // Also the transport is closed when this returns _callback.OnProcessExit(processExitCode); Dispose(); }; DebuggerAbortedEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // The MI debugger process unexpectedly exited. _worker.PostOperation(() => { // If the MI Debugger exits before we get a resume call, we have no way of sending program destroy. So just let start debugging fail. if (!_connected) { return; } _callback.OnError(MICoreResources.Error_MIDebuggerExited); _callback.OnProcessExit(uint.MaxValue); Dispose(); }); }; ModuleLoadEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported if (_libraryLoaded.Count != 0) { string moduleNames = string.Join(", ", _libraryLoaded); try { _libraryLoaded.Clear(); SourceLineCache.OnLibraryLoad(); await _breakpointManager.BindAsync(); await CheckModules(); _bLastModuleLoadFailed = false; } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_ExceptionProcessingModules, moduleNames, exceptionDescription); // to avoid spamming the user, if the last module failed, we send the next failure to the output windiw instead of a message box if (!_bLastModuleLoadFailed) { _callback.OnError(message); _bLastModuleLoadFailed = true; } else { _callback.OnOutputMessage(message, enum_MESSAGETYPE.MT_OUTPUTSTRING); } } } if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (MICommandFactory.SupportsStopOnDynamicLibLoad()) { CmdContinueAsync(); } }; // When we break we need to gather information BreakModeEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (!this._connected) { _initialBreakArgs = results; return; } try { await HandleBreakModeEvent(results); } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_FailedToEnterBreakState, exceptionDescription); _callback.OnError(message); Terminate(); } }; RunModeEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful if (!_bStarted) { _bStarted = true; // Send any strings we got before the process came up if (_pendingMessages.Length != 0) { try { _callback.OnOutputString(_pendingMessages.ToString()); } catch { // If something goes wrong sending the output, lets not crash VS } } _pendingMessages = null; } }; ErrorEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs result = (ResultEventArgs)args; _callback.OnError(result.Results.FindString("msg")); }; ThreadCreatedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted */false); }; ThreadExitedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted*/true); }; BreakChangeEvent += _breakpointManager.BreakpointModified; }
public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, HostConfigurationStore configStore, HostWaitLoop waitLoop = null) : base(launchOptions, engine.Logger) { uint processExitCode = 0; _pendingMessages = new StringBuilder(400); _worker = worker; _breakpointManager = bpman; Engine = engine; _libraryLoaded = new List<string>(); _loadOrder = 0; MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = (MICommandFactory.SupportsStopOnDynamicLibLoad() && launchOptions.WaitDynamicLibLoad) ? new HostWaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; Natvis = new Natvis.Natvis(this, launchOptions.ShowDisplayString); // we do NOT have real Win32 process IDs, so we use a guid AD_PROCESS_ID pid = new AD_PROCESS_ID(); pid.ProcessIdType = (int)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID; pid.guidProcessId = Guid.NewGuid(); this.Id = pid; SourceLineCache = new SourceLineCache(this); _callback = callback; _moduleList = new List<DebuggedModule>(); ThreadCache = new ThreadCache(callback, this); Disassembly = new Disassembly(this); ExceptionManager = new ExceptionManager(MICommandFactory, _worker, _callback, configStore); VariablesToDelete = new List<string>(); this.ActiveVariables = new List<IVariableInformation>(); _fileTimestampWarnings = new HashSet<Tuple<string, string>>(); OutputStringEvent += delegate (object o, string message) { // We can get messages before we have started the process // but we can't send them on until it is if (_connected) { _callback.OnOutputString(message); } else { _pendingMessages.Append(message); } }; LibraryLoadEvent += delegate (object o, EventArgs args) { ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; string file = results.Results.TryFindString("host-name"); if (!string.IsNullOrEmpty(file) && MICommandFactory.SupportsStopOnDynamicLibLoad()) { _libraryLoaded.Add(file); if (_waitDialog != null) { _waitDialog.ShowWaitDialog(file); } } else if (this.MICommandFactory.Mode == MIMode.Clrdbg) { string id = results.Results.FindString("id"); ulong baseAddr = results.Results.FindAddr("base-address"); uint size = results.Results.FindUint("size"); bool symbolsLoaded = results.Results.FindInt("symbols-loaded") != 0; var module = new DebuggedModule(id, file, baseAddr, size, symbolsLoaded, string.Empty, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } else if (!string.IsNullOrEmpty(file)) { string addr = results.Results.TryFindString("loaded_addr"); if (string.IsNullOrEmpty(addr) || addr == "-") { return; // identifies the exe, not a real load } // generate module string id = results.Results.TryFindString("name"); bool symsLoaded = true; string symPath = null; if (results.Results.Contains("symbols-path")) { symPath = results.Results.FindString("symbols-path"); if (string.IsNullOrEmpty(symPath)) { symsLoaded = false; } } else { symPath = file; } ulong loadAddr = results.Results.FindAddr("loaded_addr"); uint size = results.Results.FindUint("size"); if (String.IsNullOrEmpty(id)) { id = file; } var module = FindModule(id); if (module == null) { module = new DebuggedModule(id, file, loadAddr, size, symsLoaded, symPath, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } } }; if (_launchOptions is LocalLaunchOptions) { LocalLaunchOptions localLaunchOptions = (LocalLaunchOptions)_launchOptions; if (!localLaunchOptions.IsValidMiDebuggerPath()) { throw new Exception(MICoreResources.Error_InvalidMiDebuggerPath); } if (PlatformUtilities.IsOSX() && localLaunchOptions.DebuggerMIMode != MIMode.Clrdbg && localLaunchOptions.DebuggerMIMode != MIMode.Lldb && !UnixUtilities.IsBinarySigned(localLaunchOptions.MIDebuggerPath)) { string message = String.Format(CultureInfo.CurrentCulture, ResourceStrings.Warning_DarwinDebuggerUnsigned, localLaunchOptions.MIDebuggerPath); _callback.OnOutputMessage(new OutputMessage( message + Environment.NewLine, enum_MESSAGETYPE.MT_MESSAGEBOX, OutputMessage.Severity.Warning)); } ITransport localTransport = null; // For local Linux and OS X launch, use the local Unix transport which creates a new terminal and // uses fifos for debugger (e.g., gdb) communication. if (this.MICommandFactory.UseExternalConsoleForLocalLaunch(localLaunchOptions) && (PlatformUtilities.IsLinux() || (PlatformUtilities.IsOSX() && localLaunchOptions.DebuggerMIMode != MIMode.Lldb))) { localTransport = new LocalUnixTerminalTransport(); // Only need to clear terminal for Linux and OS X local launch _needTerminalReset = (localLaunchOptions.ProcessId == 0 && _launchOptions.DebuggerMIMode == MIMode.Gdb); } else { localTransport = new LocalTransport(); } if (localLaunchOptions.ShouldStartServer()) { this.Init( new MICore.ClientServerTransport( localTransport, new ServerTransport(killOnClose: true, filterStdout: localLaunchOptions.FilterStdout, filterStderr: localLaunchOptions.FilterStderr) ), _launchOptions); } else { this.Init(localTransport, _launchOptions); } // Only need to know the debugger pid on Linux and OS X local launch to detect whether // the debugger is closed. If the debugger is not running anymore, the response (^exit) // to the -gdb-exit command is faked to allow MIEngine to shut down. SetDebuggerPid(localTransport.DebuggerPid); } else if (_launchOptions is PipeLaunchOptions) { this.Init(new MICore.PipeTransport(), _launchOptions); } else if (_launchOptions is TcpLaunchOptions) { this.Init(new MICore.TcpTransport(), _launchOptions); } else if (_launchOptions is UnixShellPortLaunchOptions) { this.Init(new MICore.UnixShellPortTransport(), _launchOptions, waitLoop); } else { throw new ArgumentOutOfRangeException("LaunchInfo.options"); } MIDebugCommandDispatcher.AddProcess(this); // When the debuggee exits, we need to exit the debugger ProcessExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (results.Results.Contains("exit-code")) { // GDB sometimes returns exit codes, which don't fit into uint, like "030000000472". // And we can't throw from here, because it crashes VS. // Full exit code will still usually be reported in the Output window, // but here let's return "uint.MaxValue" just to indicate that something went wrong. if (!uint.TryParse(results.Results.FindString("exit-code"), out processExitCode)) { processExitCode = uint.MaxValue; } } // quit MI Debugger if (!this.IsClosed) { _worker.PostOperation(CmdExitAsync); } else { // If we are already closed, make sure that something sends program destroy _callback.OnProcessExit(processExitCode); } if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } }; // When the debugger exits, we tell AD7 we are done DebuggerExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // this is the last AD7 Event we can ever send // Also the transport is closed when this returns _callback.OnProcessExit(processExitCode); Dispose(); }; DebuggerAbortedEvent += delegate (object o, DebuggerAbortedEventArgs eventArgs) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // The MI debugger process unexpectedly exited. _worker.PostOperation(() => { _engineTelemetry.SendDebuggerAborted(MICommandFactory, GetLastSentCommandName(), eventArgs.ExitCode); // If the MI Debugger exits before we get a resume call, we have no way of sending program destroy. So just let start debugging fail. if (!_connected) { return; } _callback.OnError(string.Concat(eventArgs.Message, " ", ResourceStrings.DebuggingWillAbort)); _callback.OnProcessExit(uint.MaxValue); Dispose(); }); }; ModuleLoadEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported if (_needTerminalReset) { _needTerminalReset = false; // This is to work around a GDB bug of warning "Failed to set controlling terminal: Operation not permitted" // Reset debuggee terminal after the first module load. await ResetConsole(); } if (this.MICommandFactory.SupportsStopOnDynamicLibLoad() && !_launchOptions.WaitDynamicLibLoad) { await CmdAsync("-gdb-set stop-on-solib-events 0", ResultClass.None); } await this.EnsureModulesLoaded(); if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (MICommandFactory.SupportsStopOnDynamicLibLoad()) { // Do not continue if debugging core dump if (!this.IsCoreDump) { CmdContinueAsync(); } } }; // When we break we need to gather information BreakModeEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported StoppingEventArgs results = args as MICore.Debugger.StoppingEventArgs; if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (!this._connected) { _initialBreakArgs = results; return; } try { await HandleBreakModeEvent(results, results.AsyncRequest); } catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { if (this.IsStopDebuggingInProgress) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_FailedToEnterBreakState, exceptionDescription); _callback.OnError(message); Terminate(); } }; ErrorEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs result = (ResultEventArgs)args; _callback.OnError(result.Results.FindString("msg")); }; ThreadCreatedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadCreatedEvent(result.Results.FindInt("id"), result.Results.TryFindString("group-id")); _childProcessHandler?.ThreadCreatedEvent(result.Results); }; ThreadExitedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadExitedEvent(result.Results.FindInt("id")); }; ThreadGroupExitedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadGroupExitedEvent(result.Results.FindString("id")); }; MessageEvent += (object o, ResultEventArgs args) => { OutputMessage outputMessage = DecodeOutputEvent(args.Results); if (outputMessage != null) { _callback.OnOutputMessage(outputMessage); } }; TelemetryEvent += (object o, ResultEventArgs args) => { string eventName; KeyValuePair<string, object>[] properties; if (_engineTelemetry.DecodeTelemetryEvent(args.Results, out eventName, out properties)) { HostTelemetry.SendEvent(eventName, properties); } }; BreakChangeEvent += _breakpointManager.BreakpointModified; }
// 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. int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { Debug.Assert(_pollThread == null); Debug.Assert(_engineCallback == null); Debug.Assert(_debuggedProcess == null); Debug.Assert(_ad7ProgramId == Guid.Empty); process = null; _engineCallback = new EngineCallback(this, ad7Callback); Exception exception; try { // Note: LaunchOptions.GetInstance can be an expensive operation and may push a wait message loop LaunchOptions launchOptions = LaunchOptions.GetInstance(_registryRoot, exe, args, dir, options, _engineCallback); // We are being asked to debug a process when we currently aren't debugging anything _pollThread = new WorkerThread(); var cancellationTokenSource = new CancellationTokenSource(); using (cancellationTokenSource) { _pollThread.RunOperation(ResourceStrings.InitializingDebugger, cancellationTokenSource, (MICore.WaitLoop waitLoop) => { try { _debuggedProcess = new DebuggedProcess(true, launchOptions, _engineCallback, _pollThread, _breakpointManager, this); } finally { // If there is an exception from the DebuggeedProcess constructor, it is our responsibility to dispose the DeviceAppLauncher, // otherwise the DebuggedProcess object takes ownership. if (_debuggedProcess == null && launchOptions.DeviceAppLauncher != null) { launchOptions.DeviceAppLauncher.Dispose(); } } _pollThread.PostedOperationErrorEvent += _debuggedProcess.OnPostedOperationError; return(_debuggedProcess.Initialize(waitLoop, cancellationTokenSource.Token)); }); } EngineUtils.RequireOk(port.GetProcess(_debuggedProcess.Id, out process)); return(Constants.S_OK); } catch (Exception e) { exception = e; // Return from the catch block so that we can let the exception unwind - the stack can get kind of big } // If we just return the exception as an HRESULT, we will loose our message, so we instead send up an error event, and then // return E_ABORT. Logger.Flush(); SendStartDebuggingError(exception); Dispose(); return(Constants.E_ABORT); }
public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine) { uint processExitCode = 0; g_Process = this; _bStarted = false; _pendingMessages = new StringBuilder(400); _worker = worker; _launchOptions = launchOptions; _breakpointManager = bpman; Engine = engine; _libraryLoaded = new List <string>(); _loadOrder = 0; MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = MICommandFactory.SupportsStopOnDynamicLibLoad() ? new WaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; Natvis = new Natvis.Natvis(this); // we do NOT have real Win32 process IDs, so we use a guid AD_PROCESS_ID pid = new AD_PROCESS_ID(); pid.ProcessIdType = (int)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID; pid.guidProcessId = Guid.NewGuid(); this.Id = pid; SourceLineCache = new SourceLineCache(this); _callback = callback; _moduleList = new List <DebuggedModule>(); ThreadCache = new ThreadCache(callback, this); Disassembly = new Disassembly(this); VariablesToDelete = new List <string>(); MessageEvent += delegate(object o, string message) { // We can get messages before we have started the process // but we can't send them on until it is if (_bStarted) { _callback.OnOutputString(message); } else { _pendingMessages.Append(message); } }; LibraryLoadEvent += delegate(object o, EventArgs args) { ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; string file = results.Results.TryFindString("host-name"); if (!string.IsNullOrEmpty(file) && MICommandFactory.SupportsStopOnDynamicLibLoad()) { _libraryLoaded.Add(file); if (_waitDialog != null) { _waitDialog.ShowWaitDialog(file); } } else if (this.MICommandFactory.Mode == MIMode.Clrdbg) { string id = results.Results.FindString("id"); ulong baseAddr = results.Results.FindAddr("base-address"); uint size = results.Results.FindUint("size"); bool symbolsLoaded = results.Results.FindInt("symbols-loaded") != 0; var module = new DebuggedModule(id, file, baseAddr, size, symbolsLoaded, string.Empty, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } else if (!string.IsNullOrEmpty(file)) { string addr = results.Results.TryFindString("loaded_addr"); if (string.IsNullOrEmpty(addr) || addr == "-") { return; // identifies the exe, not a real load } // generate module string id = results.Results.TryFindString("name"); bool symsLoaded = true; string symPath = null; if (results.Results.Contains("symbols-path")) { symPath = results.Results.FindString("symbols-path"); if (string.IsNullOrEmpty(symPath)) { symsLoaded = false; } } else { symPath = file; } ulong loadAddr = results.Results.FindAddr("loaded_addr"); uint size = results.Results.FindUint("size"); if (String.IsNullOrEmpty(id)) { id = file; } var module = FindModule(id); if (module == null) { module = new DebuggedModule(id, file, loadAddr, size, symsLoaded, symPath, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } } }; if (_launchOptions is LocalLaunchOptions) { this.Init(new MICore.LocalTransport(), _launchOptions); } else if (_launchOptions is PipeLaunchOptions) { this.Init(new MICore.PipeTransport(), _launchOptions); } else if (_launchOptions is TcpLaunchOptions) { this.Init(new MICore.TcpTransport(), _launchOptions); } else if (_launchOptions is SerialLaunchOptions) { string port = ((SerialLaunchOptions)_launchOptions).Port; this.Init(new MICore.SerialTransport(port), _launchOptions); } else { throw new ArgumentOutOfRangeException("LaunchInfo.options"); } MIDebugCommandDispatcher.AddProcess(this); // When the debuggee exits, we need to exit the debugger ProcessExitEvent += delegate(object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (results.Results.Contains("exit-code")) { processExitCode = results.Results.FindUint("exit-code"); } // quit MI Debugger _worker.PostOperation(CmdExitAsync); if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } }; // When the debugger exits, we tell AD7 we are done DebuggerExitEvent += delegate(object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // this is the last AD7 Event we can ever send // Also the transport is closed when this returns _callback.OnProcessExit(processExitCode); Dispose(); }; DebuggerAbortedEvent += delegate(object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // The MI debugger process unexpectedly exited. _worker.PostOperation(() => { // If the MI Debugger exits before we get a resume call, we have no way of sending program destroy. So just let start debugging fail. if (!_connected) { return; } _callback.OnError(MICoreResources.Error_MIDebuggerExited); _callback.OnProcessExit(uint.MaxValue); Dispose(); }); }; ModuleLoadEvent += async delegate(object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported if (_libraryLoaded.Count != 0) { string moduleNames = string.Join(", ", _libraryLoaded); try { _libraryLoaded.Clear(); SourceLineCache.OnLibraryLoad(); await _breakpointManager.BindAsync(); await CheckModules(); _bLastModuleLoadFailed = false; } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_ExceptionProcessingModules, moduleNames, exceptionDescription); // to avoid spamming the user, if the last module failed, we send the next failure to the output windiw instead of a message box if (!_bLastModuleLoadFailed) { _callback.OnError(message); _bLastModuleLoadFailed = true; } else { _callback.OnOutputMessage(message, enum_MESSAGETYPE.MT_OUTPUTSTRING); } } } if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (MICommandFactory.SupportsStopOnDynamicLibLoad()) { CmdContinueAsync(); } }; // When we break we need to gather information BreakModeEvent += async delegate(object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (!this._connected) { _initialBreakArgs = results; return; } try { await HandleBreakModeEvent(results); } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_FailedToEnterBreakState, exceptionDescription); _callback.OnError(message); Terminate(); } }; RunModeEvent += delegate(object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful if (!_bStarted) { _bStarted = true; // Send any strings we got before the process came up if (_pendingMessages.Length != 0) { try { _callback.OnOutputString(_pendingMessages.ToString()); } catch { // If something goes wrong sending the output, lets not crash VS } } _pendingMessages = null; } }; ErrorEvent += delegate(object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs result = (ResultEventArgs)args; _callback.OnError(result.Results.FindString("msg")); }; ThreadCreatedEvent += delegate(object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted */ false); }; ThreadExitedEvent += delegate(object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted*/ true); }; BreakChangeEvent += _breakpointManager.BreakpointModified; }
public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, HostConfigurationStore configStore) : base(launchOptions) { uint processExitCode = 0; _pendingMessages = new StringBuilder(400); _worker = worker; _breakpointManager = bpman; Engine = engine; _libraryLoaded = new List<string>(); _loadOrder = 0; MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = MICommandFactory.SupportsStopOnDynamicLibLoad() ? new HostWaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; Natvis = new Natvis.Natvis(this, launchOptions.ShowDisplayString); // we do NOT have real Win32 process IDs, so we use a guid AD_PROCESS_ID pid = new AD_PROCESS_ID(); pid.ProcessIdType = (int)enum_AD_PROCESS_ID.AD_PROCESS_ID_GUID; pid.guidProcessId = Guid.NewGuid(); this.Id = pid; SourceLineCache = new SourceLineCache(this); _callback = callback; _moduleList = new List<DebuggedModule>(); ThreadCache = new ThreadCache(callback, this); Disassembly = new Disassembly(this); ExceptionManager = new ExceptionManager(MICommandFactory, _worker, _callback, configStore); VariablesToDelete = new List<string>(); OutputStringEvent += delegate (object o, string message) { // We can get messages before we have started the process // but we can't send them on until it is if (_connected) { _callback.OnOutputString(message); } else { _pendingMessages.Append(message); } }; LibraryLoadEvent += delegate (object o, EventArgs args) { ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; string file = results.Results.TryFindString("host-name"); if (!string.IsNullOrEmpty(file) && MICommandFactory.SupportsStopOnDynamicLibLoad()) { _libraryLoaded.Add(file); if (_waitDialog != null) { _waitDialog.ShowWaitDialog(file); } } else if (this.MICommandFactory.Mode == MIMode.Clrdbg) { string id = results.Results.FindString("id"); ulong baseAddr = results.Results.FindAddr("base-address"); uint size = results.Results.FindUint("size"); bool symbolsLoaded = results.Results.FindInt("symbols-loaded") != 0; var module = new DebuggedModule(id, file, baseAddr, size, symbolsLoaded, string.Empty, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } else if (!string.IsNullOrEmpty(file)) { string addr = results.Results.TryFindString("loaded_addr"); if (string.IsNullOrEmpty(addr) || addr == "-") { return; // identifies the exe, not a real load } // generate module string id = results.Results.TryFindString("name"); bool symsLoaded = true; string symPath = null; if (results.Results.Contains("symbols-path")) { symPath = results.Results.FindString("symbols-path"); if (string.IsNullOrEmpty(symPath)) { symsLoaded = false; } } else { symPath = file; } ulong loadAddr = results.Results.FindAddr("loaded_addr"); uint size = results.Results.FindUint("size"); if (String.IsNullOrEmpty(id)) { id = file; } var module = FindModule(id); if (module == null) { module = new DebuggedModule(id, file, loadAddr, size, symsLoaded, symPath, _loadOrder++); lock (_moduleList) { _moduleList.Add(module); } _callback.OnModuleLoad(module); } } }; if (_launchOptions is LocalLaunchOptions) { LocalLaunchOptions localLaunchOptions = (LocalLaunchOptions)_launchOptions; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && _launchOptions.DebuggerMIMode == MIMode.Gdb && String.IsNullOrEmpty(localLaunchOptions.MIDebuggerServerAddress) ) { // For local linux launch, use the local linux transport which creates a new terminal and uses fifos for gdb communication. // CONSIDER: add new flag and only do this if new terminal is true? Note that setting this to false on linux will cause a deadlock // during debuggee launch if (localLaunchOptions.ShouldStartServer()) { this.Init(new MICore.ClientServerTransport ( new LocalLinuxTransport(), new ServerTransport(killOnClose: true, filterStdout: localLaunchOptions.FilterStdout, filterStderr: localLaunchOptions.FilterStderr) ), _launchOptions ); } else { this.Init(new MICore.LocalLinuxTransport(), _launchOptions); } } else { if (localLaunchOptions.ShouldStartServer()) { this.Init(new MICore.ClientServerTransport ( new LocalTransport(), new ServerTransport(killOnClose: true, filterStdout: localLaunchOptions.FilterStdout, filterStderr: localLaunchOptions.FilterStderr) ), _launchOptions ); } else { this.Init(new MICore.LocalTransport(), _launchOptions); } } } else if (_launchOptions is PipeLaunchOptions) { this.Init(new MICore.PipeTransport(), _launchOptions); } else if (_launchOptions is TcpLaunchOptions) { this.Init(new MICore.TcpTransport(), _launchOptions); } else { throw new ArgumentOutOfRangeException("LaunchInfo.options"); } MIDebugCommandDispatcher.AddProcess(this); // When the debuggee exits, we need to exit the debugger ProcessExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (results.Results.Contains("exit-code")) { // GDB sometimes returns exit codes, which don't fit into uint, like "030000000472". // And we can't throw from here, because it crashes VS. // Full exit code will still usually be reported in the Output window, // but here let's return "uint.MaxValue" just to indicate that something went wrong. if (!uint.TryParse(results.Results.FindString("exit-code"), out processExitCode)) { processExitCode = uint.MaxValue; } } // quit MI Debugger _worker.PostOperation(CmdExitAsync); if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } }; // When the debugger exits, we tell AD7 we are done DebuggerExitEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // this is the last AD7 Event we can ever send // Also the transport is closed when this returns _callback.OnProcessExit(processExitCode); Dispose(); }; DebuggerAbortedEvent += delegate (object o, /*OPTIONAL*/ string debuggerExitCode) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful // The MI debugger process unexpectedly exited. _worker.PostOperation(() => { // If the MI Debugger exits before we get a resume call, we have no way of sending program destroy. So just let start debugging fail. if (!_connected) { return; } string message; if (string.IsNullOrEmpty(debuggerExitCode)) message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_MIDebuggerExited_UnknownCode, MICommandFactory.Name); else message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_MIDebuggerExited_WithCode, MICommandFactory.Name, debuggerExitCode); _callback.OnError(message); _callback.OnProcessExit(uint.MaxValue); Dispose(); }); }; ModuleLoadEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported if (_libraryLoaded.Count != 0) { string moduleNames = string.Join(", ", _libraryLoaded); try { _libraryLoaded.Clear(); SourceLineCache.OnLibraryLoad(); await _breakpointManager.BindAsync(); await CheckModules(); _bLastModuleLoadFailed = false; } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_ExceptionProcessingModules, moduleNames, exceptionDescription); // to avoid spamming the user, if the last module failed, we send the next failure to the output windiw instead of a message box if (!_bLastModuleLoadFailed) { _callback.OnError(message); _bLastModuleLoadFailed = true; } else { _callback.OnOutputMessage(new OutputMessage(message, enum_MESSAGETYPE.MT_OUTPUTSTRING, OutputMessage.Severity.Warning)); } } } if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (MICommandFactory.SupportsStopOnDynamicLibLoad()) { CmdContinueAsync(); } }; // When we break we need to gather information BreakModeEvent += async delegate (object o, EventArgs args) { // NOTE: This is an async void method, so make sure exceptions are caught and somehow reported ResultEventArgs results = args as MICore.Debugger.ResultEventArgs; if (_waitDialog != null) { _waitDialog.EndWaitDialog(); } if (!this._connected) { _initialBreakArgs = results; return; } try { await HandleBreakModeEvent(results); } catch (Exception e) { if (this.ProcessState == MICore.ProcessState.Exited) { return; // ignore exceptions after the process has exited } string exceptionDescription = EngineUtils.GetExceptionDescription(e); string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_FailedToEnterBreakState, exceptionDescription); _callback.OnError(message); Terminate(); } }; ErrorEvent += delegate (object o, EventArgs args) { // NOTE: Exceptions leaked from this method may cause VS to crash, be careful ResultEventArgs result = (ResultEventArgs)args; _callback.OnError(result.Results.FindString("msg")); }; ThreadCreatedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted */false); }; ThreadExitedEvent += delegate (object o, EventArgs args) { ResultEventArgs result = (ResultEventArgs)args; ThreadCache.ThreadEvent(result.Results.FindInt("id"), /*deleted*/true); }; MessageEvent += (object o, ResultEventArgs args) => { OutputMessage outputMessage = DecodeOutputEvent(args.Results); if (outputMessage != null) { _callback.OnOutputMessage(outputMessage); } }; TelemetryEvent += (object o, ResultEventArgs args) => { string eventName; KeyValuePair<string, object>[] properties; if (DecodeTelemetryEvent(args.Results, out eventName, out properties)) { HostTelemetry.SendEvent(eventName, properties); } }; BreakChangeEvent += _breakpointManager.BreakpointModified; }
public ExceptionManager(MICommandFactory commandFactory, WorkerThread worker, ISampleEngineCallback callback, /*OPTIONAL*/ HostConfigurationStore configStore) { Debug.Assert(commandFactory != null, "Missing commandFactory"); Debug.Assert(worker != null, "Missing worker"); Debug.Assert(callback != null, "Missing callback"); _commandFactory = commandFactory; _worker = worker; _callback = callback; _categoryMap = ReadDefaultSettings(configStore); }
private void Dispose() { WorkerThread pollThread = _pollThread; DebuggedProcess debuggedProcess = _debuggedProcess; _engineCallback = null; _debuggedProcess = null; _pollThread = null; _ad7ProgramId = Guid.Empty; if(debuggedProcess != null) debuggedProcess.Close(); if(pollThread != null) pollThread.Close(); }