in src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs [56:455]
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;
_deleteEntryPointBreakpoint = false;
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>();
DataBreakpointVariables = 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("id");
if (!string.IsNullOrEmpty(file) && MICommandFactory.SupportsStopOnDynamicLibLoad())
{
_libraryLoaded.Add(file);
if (_waitDialog != null)
{
_waitDialog.ShowWaitDialog(file);
}
}
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;
}
AddModule(id, file, loadAddr, size, symsLoaded, symPath);
}
};
if (_launchOptions is LocalLaunchOptions)
{
LocalLaunchOptions localLaunchOptions = (LocalLaunchOptions)_launchOptions;
if (!localLaunchOptions.IsValidMiDebuggerPath())
{
throw new Exception(MICoreResources.Error_InvalidMiDebuggerPath);
}
if (PlatformUtilities.IsOSX() &&
localLaunchOptions.DebuggerMIMode != MIMode.Lldb &&
!UnixUtilities.IsBinarySigned(localLaunchOptions.MIDebuggerPath, engine.Logger))
{
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;
// Attempt to support RunInTerminal first when it is a local launch and it is not debugging a coredump.
// Also since we use gdb-set new-console on in windows for external console, we don't need to RunInTerminal
if (HostRunInTerminal.IsRunInTerminalAvailable()
&& string.IsNullOrWhiteSpace(localLaunchOptions.MIDebuggerServerAddress)
&& string.IsNullOrWhiteSpace(localLaunchOptions.DebugServer)
&& IsCoreDump == false
&& (PlatformUtilities.IsWindows() ? !localLaunchOptions.UseExternalConsole : true)
&& !PlatformUtilities.IsOSX())
{
localTransport = new RunInTerminalTransport();
if (PlatformUtilities.IsLinux() || PlatformUtilities.IsOSX())
{
// Only need to clear terminal for Linux and OS X local launch
_needTerminalReset = (!localLaunchOptions.ProcessId.HasValue && _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.
// For RunInTransport, this needs to be updated via a callback.
if (localTransport is RunInTerminalTransport)
{
((RunInTerminalTransport)localTransport).RegisterDebuggerPidCallback(SetDebuggerPid);
}
else
{
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(nameof(launchOptions));
}
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;
// In lldb, the format is ^error,message=""
// In gdb/vsdbg it is ^error,msg=""
string message = result.Results.TryFindString("msg");
if (String.IsNullOrWhiteSpace(message))
{
message = result.Results.TryFindString("message");
}
// if the command was abort (usually because of breakpoints failing to bind) then gdb writes messages into the output
if (this.MICommandFactory.Mode == MIMode.Gdb && message == "Command aborted.")
{
message = MICoreResources.Error_CommandAborted;
if (ProcessState == ProcessState.Running)
{
// assume that it was a continue command that got aborted and return to stopped state:
// this occurs when using openocd to debug embedded devices and it runs out of hardware breakpoints.
int currentThread = MICommandFactory.CurrentThread;
if (currentThread == 0)
{
currentThread = 1; // default to main thread is current doesn't have a valid value for some reason
}
ScheduleStdOutProcessing(string.Format(CultureInfo.CurrentCulture, @"*stopped,reason=""exception-received"",signal-name=""SIGINT"",thread-id=""{1}"",exception=""{0}""", MICoreResources.Info_UnableToContinue, currentThread));
}
}
_callback.OnError(message);
};
ThreadCreatedEvent += async delegate (object o, EventArgs args)
{
try
{
ResultEventArgs result = (ResultEventArgs)args;
await ThreadCache.ThreadCreatedEvent(result.Results.FindInt("id"), result.Results.TryFindString("group-id"));
_childProcessHandler?.ThreadCreatedEvent(result.Results);
}
catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true))
{
// Avoid crashing VS
}
};
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"));
};
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 += async delegate (object o, EventArgs args)
{
try
{
await _breakpointManager.BreakpointModified(o, args);
}
catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true))
{ }
};
}