/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet; using dnSpy.Contracts.Debugger.DotNet.Code; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.DotNet.Metadata.Internal; using dnSpy.Contracts.Debugger.DotNet.Mono; using dnSpy.Contracts.Debugger.DotNet.Steppers.Engine; using dnSpy.Contracts.Debugger.Engine; using dnSpy.Contracts.Debugger.Exceptions; using dnSpy.Contracts.Metadata; using dnSpy.Debugger.DotNet.Metadata; using dnSpy.Debugger.DotNet.Mono.CallStack; using dnSpy.Debugger.DotNet.Mono.Impl.Attach; using dnSpy.Debugger.DotNet.Mono.Impl.Evaluation; using dnSpy.Debugger.DotNet.Mono.Properties; using Mono.Debugger.Soft; namespace dnSpy.Debugger.DotNet.Mono.Impl { sealed partial class DbgEngineImpl : DbgEngine { static readonly TimeSpan defaultConnectionTimeout = TimeSpan.FromSeconds(10); static readonly TimeSpan maxConnectionTimeout = TimeSpan.FromMinutes(5); public override DbgStartKind StartKind => wasAttach ? DbgStartKind.Attach : DbgStartKind.Start; public override DbgEngineRuntimeInfo RuntimeInfo => runtimeInfo; public override string[] DebugTags => new[] { PredefinedDebugTags.DotNetDebugger }; public override string[] Debugging { get; } public override event EventHandler? Message; internal DbgObjectFactory ObjectFactory => objectFactory!; internal VirtualMachine MonoVirtualMachine => vm!; readonly object lockObj; readonly DebuggerThread debuggerThread; readonly DebuggerSettings debuggerSettings; readonly Lazy dbgDotNetCodeLocationFactory; readonly DbgEngineStepperFactory dbgEngineStepperFactory; readonly DbgManager dbgManager; readonly DbgModuleMemoryRefreshedNotifier2 dbgModuleMemoryRefreshedNotifier; DmdRuntime? dmdRuntime; readonly DmdDispatcherImpl dmdDispatcher; internal DbgRawMetadataService RawMetadataService { get; } readonly MonoDebugRuntimeKind monoDebugRuntimeKind; readonly DbgEngineRuntimeInfo runtimeInfo; readonly Dictionary toEngineAppDomain; readonly Dictionary toEngineModule; readonly Dictionary toEngineThread; readonly Dictionary> toAssemblyModules; readonly HashSet appDomainsThatHaveNotBeenInitializedYet; internal readonly StackFrameData stackFrameData; readonly List dotNetValuesToCloseOnContinue; readonly FuncEvalFactory funcEvalFactory; readonly List execOnPauseList; readonly Dictionary toStepper; readonly DotNetMonoRuntimeId monoRuntimeId; bool wasAttach; bool processWasRunningOnAttach; VirtualMachine? vm; int vmPid; int? vmDeathExitCode; volatile bool gotVMDisconnect; bool isUnhandledException; DbgObjectFactory? objectFactory; SafeHandle? hProcess_debuggee; volatile int suspendCount; readonly List pendingMessages; // The thrown exception. The mono debugger agent keeps it alive until Resume() is called, // but Unity uses a buggy debugger agent that doesn't keep it alive so it could get GC'd. ObjectMirror? thrownException; BreakOnEntryPointData? breakOnEntryPointData; ConsoleOutputReaderInfo? consoleStdOut; ConsoleOutputReaderInfo? consoleStdErr; sealed class BreakOnEntryPointData { public BreakpointEventRequest? Breakpoint; public string? Filename; } static DbgEngineImpl() => ThreadMirror.NativeTransitions = true; public DbgEngineImpl(DbgEngineImplDependencies deps, DbgManager dbgManager, MonoDebugRuntimeKind monoDebugRuntimeKind) { if (deps is null) throw new ArgumentNullException(nameof(deps)); lockObj = new object(); suspendCount = 0; pendingMessages = new List(); toEngineAppDomain = new Dictionary(); toEngineModule = new Dictionary(); toEngineThread = new Dictionary(); toAssemblyModules = new Dictionary>(); appDomainsThatHaveNotBeenInitializedYet = new HashSet(); stackFrameData = new StackFrameData(); dotNetValuesToCloseOnContinue = new List(); execOnPauseList = new List(); toStepper = new Dictionary(); debuggerSettings = deps.DebuggerSettings; dbgDotNetCodeLocationFactory = deps.DbgDotNetCodeLocationFactory; dbgEngineStepperFactory = deps.EngineStepperFactory; this.dbgManager = dbgManager ?? throw new ArgumentNullException(nameof(dbgManager)); dbgModuleMemoryRefreshedNotifier = deps.DbgModuleMemoryRefreshedNotifier; debuggerThread = new DebuggerThread("MonoDebug"); debuggerThread.CallDispatcherRun(); dmdDispatcher = new DmdDispatcherImpl(this); monoRuntimeId = new DotNetMonoRuntimeId(); RawMetadataService = deps.RawMetadataService; this.monoDebugRuntimeKind = monoDebugRuntimeKind; if (monoDebugRuntimeKind == MonoDebugRuntimeKind.Mono) { Debugging = new[] { "MonoCLR" }; runtimeInfo = new DbgEngineRuntimeInfo(PredefinedDbgRuntimeGuids.DotNetMono_Guid, PredefinedDbgRuntimeKindGuids.DotNet_Guid, "MonoCLR", monoRuntimeId, monoRuntimeTags); } else { Debug.Assert(monoDebugRuntimeKind == MonoDebugRuntimeKind.Unity); Debugging = new[] { "Unity" }; runtimeInfo = new DbgEngineRuntimeInfo(PredefinedDbgRuntimeGuids.DotNetUnity_Guid, PredefinedDbgRuntimeKindGuids.DotNet_Guid, "Unity", monoRuntimeId, unityRuntimeTags); } funcEvalFactory = new FuncEvalFactory(debuggerThread.GetDebugMessageDispatcher()); } static readonly ReadOnlyCollection monoRuntimeTags = new ReadOnlyCollection(new[] { PredefinedDotNetDbgRuntimeTags.DotNetBase, PredefinedDotNetDbgRuntimeTags.DotNetMono, }); static readonly ReadOnlyCollection unityRuntimeTags = new ReadOnlyCollection(new[] { PredefinedDotNetDbgRuntimeTags.DotNetBase, PredefinedDotNetDbgRuntimeTags.DotNetUnity, }); internal DebuggerThread DebuggerThread => debuggerThread; internal bool CheckMonoDebugThread() => debuggerThread.CheckAccess(); internal void VerifyMonoDebugThread() => debuggerThread.VerifyAccess(); internal T InvokeMonoDebugThread(Func callback) => debuggerThread.Invoke(callback); internal void MonoDebugThread(Action callback) => debuggerThread.BeginInvoke(callback); internal DbgRuntime DbgRuntime => objectFactory!.Runtime; internal DbgEngineMessageFlags GetMessageFlags(bool pause = false) { VerifyMonoDebugThread(); var flags = DbgEngineMessageFlags.None; if (pause) flags |= DbgEngineMessageFlags.Pause; if (IsEvaluating) flags |= DbgEngineMessageFlags.Continue; return flags; } bool HasConnected_MonoDebugThread { get { debuggerThread.VerifyAccess(); return vm is not null; } } abstract class PendingMessage { public abstract bool MustWaitForRun { get; } public abstract bool RequireResumeVM { get; } public abstract bool RaiseMessage(); } sealed class NormalPendingMessage : PendingMessage { readonly DbgEngineImpl engine; readonly DbgEngineMessage message; public override bool MustWaitForRun { get; } public override bool RequireResumeVM => true; public NormalPendingMessage(DbgEngineImpl engine, bool mustWaitForRun, DbgEngineMessage message) { this.engine = engine; MustWaitForRun = mustWaitForRun; this.message = message; } public override bool RaiseMessage() { engine.Message?.Invoke(engine, message); return true; } } sealed class DelegatePendingMessage : PendingMessage { readonly Action? actionRaiseMessage; readonly Func? funcRaiseMessage; public override bool MustWaitForRun { get; } public override bool RequireResumeVM { get; } public DelegatePendingMessage(bool mustWaitForRun, Action raiseMessage) { MustWaitForRun = mustWaitForRun; RequireResumeVM = true; actionRaiseMessage = raiseMessage; } public DelegatePendingMessage(bool mustWaitForRun, bool requireResumeVM, Func raiseMessage) { MustWaitForRun = mustWaitForRun; RequireResumeVM = requireResumeVM; funcRaiseMessage = raiseMessage; } public override bool RaiseMessage() { if (funcRaiseMessage is not null) return funcRaiseMessage(); actionRaiseMessage!(); return true; } } void SendMessage(DbgEngineMessage message, bool mustWaitForRun = false) => SendMessage(new NormalPendingMessage(this, mustWaitForRun, message)); void SendMessage(PendingMessage message) { debuggerThread.VerifyAccess(); pendingMessages.Add(message); SendNextMessage(); } uint runCounter; uint nextSendRunCounter; bool SendNextMessage() { debuggerThread.VerifyAccess(); if (gotVMDisconnect) return true; if (runCounter != nextSendRunCounter) return false; try { for (;;) { if (pendingMessages.Count == 0) { nextSendRunCounter = runCounter; return false; } var pendingMessage = pendingMessages[0]; pendingMessages.RemoveAt(0); bool raisedMessage = pendingMessage.RaiseMessage(); if (pendingMessage.MustWaitForRun) { if (raisedMessage) { nextSendRunCounter = runCounter + 1; return true; } else if (pendingMessage.RequireResumeVM) { if (!DecrementAndResume()) break; } } } } catch (VMDisconnectedException) { } catch { } return true; } public override void Start(DebugProgramOptions options) => MonoDebugThread(() => StartCore(options)); void StartCore(DebugProgramOptions options) { debuggerThread.VerifyAccess(); string? defaultCouldNotConnectMessage = null; try { string? connectionAddress; ushort connectionPort; TimeSpan connectionTimeout; int expectedPid; string? filename; if (options is MonoStartDebuggingOptions startMonoOptions) { connectionAddress = "127.0.0.1"; connectionPort = startMonoOptions.ConnectionPort; connectionTimeout = startMonoOptions.ConnectionTimeout; filename = startMonoOptions.Filename; if (string2.IsNullOrEmpty(filename)) throw new Exception("Missing filename"); Debug2.Assert(startMonoOptions.Filename is not null); if (connectionPort == 0) { int port = NetUtils.GetConnectionPort(); Debug.Assert(port >= 0); if (port < 0) throw new Exception("All ports are in use"); connectionPort = (ushort)port; } var monoExe = startMonoOptions.MonoExePath; if (string2.IsNullOrEmpty(monoExe)) monoExe = MonoExeFinder.Find(startMonoOptions.MonoExeOptions); if (!File.Exists(monoExe)) throw new StartException(string.Format(dnSpy_Debugger_DotNet_Mono_Resources.Error_CouldNotFindFile, MonoExeFinder.MONO_EXE)); Debug2.Assert(monoExe is not null); Debug.Assert(!connectionAddress.Contains(" ")); var psi = new ProcessStartInfo { FileName = monoExe, Arguments = $"--debug --debugger-agent=transport=dt_socket,server=y,address={connectionAddress}:{connectionPort} \"{startMonoOptions.Filename}\" {startMonoOptions.CommandLine}", WorkingDirectory = startMonoOptions.WorkingDirectory ?? string.Empty, UseShellExecute = false, }; if (debuggerSettings.RedirectGuiConsoleOutput && PortableExecutableFileHelpers.IsGuiApp(startMonoOptions.Filename)) { psi.RedirectStandardOutput = true; psi.RedirectStandardError = true; } var env = new Dictionary(); foreach (var kv in startMonoOptions.Environment.Environment) psi.Environment[kv.Key] = kv.Value; using (var process = Process.Start(psi)!) { expectedPid = process.Id; ReadConsoleOutput(psi, process); } if (startMonoOptions.BreakKind == PredefinedBreakKinds.EntryPoint) breakOnEntryPointData = new BreakOnEntryPointData { Filename = Path.GetFullPath(startMonoOptions.Filename) }; } else if (options is UnityStartDebuggingOptions startUnityOptions) { connectionAddress = "127.0.0.1"; connectionPort = startUnityOptions.ConnectionPort; connectionTimeout = startUnityOptions.ConnectionTimeout; filename = startUnityOptions.Filename; if (string2.IsNullOrEmpty(filename)) throw new Exception("Missing filename"); if (connectionPort == 0) { int port = NetUtils.GetConnectionPort(); Debug.Assert(port >= 0); if (port < 0) throw new Exception("All ports are in use"); connectionPort = (ushort)port; } var psi = new ProcessStartInfo { FileName = startUnityOptions.Filename!, Arguments = startUnityOptions.CommandLine ?? string.Empty, WorkingDirectory = startUnityOptions.WorkingDirectory ?? string.Empty, UseShellExecute = false, }; if (debuggerSettings.RedirectGuiConsoleOutput && PortableExecutableFileHelpers.IsGuiApp(startUnityOptions.Filename)) { psi.RedirectStandardOutput = true; psi.RedirectStandardError = true; } var env = new Dictionary(); foreach (var kv in startUnityOptions.Environment.Environment) psi.Environment[kv.Key] = kv.Value; // Which version is it? Who knows, set both env vars. const string ENV_VAR_NAME_V0 = "DNSPY_UNITY_DBG"; const string ENV_VAR_NAME_V1 = "DNSPY_UNITY_DBG2"; string envVarValue; Debug.Assert(!connectionAddress.Contains(" ")); // Unity 4.x - 2018.x+ (.NET 2.0-3.5 assemblies) envVarValue = $"--debugger-agent=transport=dt_socket,server=y,address={connectionAddress}:{connectionPort},defer=y"; if (!debuggerSettings.PreventManagedDebuggerDetection) envVarValue += ",no-hide-debugger"; psi.Environment[ENV_VAR_NAME_V0] = envVarValue; // Unity 5.5+ versions (.NET 4.x assemblies) envVarValue = $"--debugger-agent=transport=dt_socket,server=y,address={connectionAddress}:{connectionPort},suspend=n"; if (!debuggerSettings.PreventManagedDebuggerDetection) envVarValue += ",no-hide-debugger"; psi.Environment[ENV_VAR_NAME_V1] = envVarValue; using (var process = Process.Start(psi)!) { expectedPid = process.Id; ReadConsoleOutput(psi, process); } defaultCouldNotConnectMessage = GetCouldNotConnectErrorMessage(connectionAddress, connectionPort, filename) + "\r\n\r\n" + dnSpy_Debugger_DotNet_Mono_Resources.CouldNotConnectToUnityGame_MakeSureMonoDllFileIsPatched; } else if (options is MonoConnectStartDebuggingOptionsBase connectOptions && (connectOptions is MonoConnectStartDebuggingOptions || connectOptions is UnityConnectStartDebuggingOptions)) { connectionAddress = connectOptions.Address; if (string2.IsNullOrWhiteSpace(connectionAddress)) connectionAddress = "127.0.0.1"; connectionPort = connectOptions.Port; connectionTimeout = connectOptions.ConnectionTimeout; filename = null; expectedPid = -1; wasAttach = true; processWasRunningOnAttach = !connectOptions.ProcessIsSuspended; } else if (options is MonoAttachToProgramOptionsBase attachOptions) { connectionAddress = attachOptions.Address; if (string2.IsNullOrWhiteSpace(connectionAddress)) connectionAddress = "127.0.0.1"; connectionPort = attachOptions.Port; connectionTimeout = attachOptions.ConnectionTimeout; filename = null; expectedPid = -1; wasAttach = true; } else { // No need to localize it, should be unreachable throw new Exception("Invalid start options"); } monoRuntimeId.Address = connectionAddress; monoRuntimeId.Port = connectionPort; if (connectionTimeout == TimeSpan.Zero) connectionTimeout = defaultConnectionTimeout; if (connectionTimeout > maxConnectionTimeout) connectionTimeout = maxConnectionTimeout; if (!IPAddress.TryParse(connectionAddress, out var ipAddr)) { ipAddr = Dns.GetHostEntry(connectionAddress).AddressList.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork); if (ipAddr is null) throw new StartException("Invalid IP address" + ": " + connectionAddress); } var endPoint = new IPEndPoint(ipAddr, connectionPort); var startTime = DateTime.UtcNow; for (;;) { var elapsedTime = DateTime.UtcNow - startTime; if (elapsedTime >= connectionTimeout) throw new CouldNotConnectException(GetCouldNotConnectErrorMessage(connectionAddress, connectionPort, filename)); try { var cts = new CancellationTokenSource(connectionTimeout - elapsedTime); var asyncConn = VirtualMachineManager.ConnectAsync(endPoint, null, cts.Token); if (!asyncConn.Wait(connectionTimeout - elapsedTime)) throw new CouldNotConnectException(GetCouldNotConnectErrorMessage(connectionAddress, connectionPort, filename)); vm = asyncConn.Result; break; } catch (SocketException sex) when (sex.SocketErrorCode == SocketError.ConnectionRefused) { // Retry it in case it takes a while for mono.exe to initialize or if it hasn't started yet } Thread.Sleep(100); } var ep = (IPEndPoint)vm.EndPoint; var pid = NetUtils.GetProcessIdOfListener(ep.Address.MapToIPv4().GetAddressBytes(), (ushort)ep.Port); Debug.Assert(expectedPid == -1 || expectedPid == pid); if (pid is null) throw new StartException(dnSpy_Debugger_DotNet_Mono_Resources.Error_CouldNotFindDebuggedProcess); vmPid = pid.Value; hProcess_debuggee = NativeMethods.OpenProcess(NativeMethods.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)vmPid); var eventThread = new Thread(MonoEventThread); eventThread.IsBackground = true; eventThread.Name = "MonoDebugEvent"; eventThread.Start(); } catch (Exception ex) { try { vm?.Detach(); } catch { } vm = null!; string msg; if (ex is CouldNotConnectException) msg = defaultCouldNotConnectMessage ?? ex.Message; else if (ex is OperationCanceledException) msg = dnSpy_Debugger_DotNet_Mono_Resources.Error_CouldNotConnectToProcess; else if (ex is StartException) msg = ex.Message; else { msg = defaultCouldNotConnectMessage ?? dnSpy_Debugger_DotNet_Mono_Resources.Error_CouldNotConnectToProcess + "\r\n\r\n" + ex.Message; } SendMessage(new DbgMessageConnected(msg, GetMessageFlags())); return; } } ExceptionEventRequest? uncaughtRequest; ExceptionEventRequest? caughtRequest; MethodEntryEventRequest? methodEntryEventRequest; sealed class ConsoleOutputReaderInfo { readonly StreamReader streamReader; readonly char[] buffer; Task? task; const int bufferSize = 0x200; public ConsoleOutputReaderInfo(StreamReader streamReader) { this.streamReader = streamReader; buffer = new char[bufferSize]; } public Task Read() { if (task is null) task = streamReader.ReadAsync(buffer, 0, buffer.Length); return task; } public string? TryGetString() { var t = task; task = null; int length = t!.GetAwaiter().GetResult(); return length == 0 ? null : new string(buffer, 0, length); } public void Dispose() => streamReader.Dispose(); } void ReadConsoleOutput(ProcessStartInfo psi, Process process) { if (!psi.RedirectStandardOutput && !psi.RedirectStandardError) return; consoleStdOut = new ConsoleOutputReaderInfo(process.StandardOutput); consoleStdErr = new ConsoleOutputReaderInfo(process.StandardError); } async void ReadConsoleOutputAsync() { var waitTasks = new Task[2]; var consoleStdOut = this.consoleStdOut; var consoleStdErr = this.consoleStdErr; for (;;) { if (gotVMDisconnect) return; var outputTask = consoleStdOut!.Read(); var errorTask = consoleStdErr!.Read(); waitTasks[0] = outputTask; waitTasks[1] = errorTask; Debug.Assert(waitTasks.Length == 2); var task = await Task.WhenAny(waitTasks); if (gotVMDisconnect) return; ConsoleOutputReaderInfo pipe; if (task == outputTask) pipe = consoleStdOut; else if (task == errorTask) pipe = consoleStdErr; else throw new InvalidOperationException(); var text = pipe.TryGetString(); if (text is null) return; var source = task == outputTask ? AsyncProgramMessageSource.StandardOutput : AsyncProgramMessageSource.StandardError; SendMessage(new DbgMessageAsyncProgramMessage(source, text)); } } void MonoEventThread() { var vm = this.vm; Debug2.Assert(vm is not null); if (vm is null) throw new InvalidOperationException(); for (;;) { try { var eventSet = vm.GetNextEventSet(); bool start; lock (pendingEventSets) { start = pendingEventSets.Count == 0; pendingEventSets.Add(eventSet); } if (start) MonoDebugThread(() => OnDebuggerEvents()); foreach (var evt in eventSet.Events) { if (evt.EventType == EventType.VMDisconnect) return; } } catch (Exception ex) { Debug.Fail(ex.ToString()); dbgManager.ShowError("Sorry, I crashed, but don't blame me, I'm innocent\n\n" + ex.GetType().FullName + "\n\n" + ex.ToString()); try { vm.Detach(); } catch { } Message?.Invoke(this, new DbgMessageDisconnected(-1, DbgEngineMessageFlags.None)); return; } } } readonly List pendingEventSets = new List(); void IncrementSuspendCount() { debuggerThread.VerifyAccess(); suspendCount++; if (suspendCount == 1) { UpdateThreadProperties_MonoDebug(); InitializeObjectConstants_MonoDebug(); RunExecOnPauseDelegates_MonoDebug(); } } void DecrementSuspendCount() { debuggerThread.VerifyAccess(); Debug.Assert(suspendCount > 0); suspendCount--; } bool DecrementAndResume() { debuggerThread.VerifyAccess(); try { ResumeVirtualMachine(); DecrementSuspendCount(); return true; } catch (VMDisconnectedException) { } return false; } void ResumeVirtualMachine() { debuggerThread.VerifyAccess(); try { vm!.Resume(); } catch (VMNotSuspendedException) { } } bool IncrementAndSuspend() { debuggerThread.VerifyAccess(); try { vm!.Suspend(); IncrementSuspendCount(); return true; } catch (VMDisconnectedException) { } return false; } void EnableEvent(EventType evt, SuspendPolicy suspendPolicy) => vm!.EnableEvents(new[] { evt }, suspendPolicy); void InitializeVirtualMachine() { try { EnableEvent(EventType.AppDomainCreate, SuspendPolicy.All); EnableEvent(EventType.AppDomainUnload, SuspendPolicy.All); EnableEvent(EventType.AssemblyLoad, SuspendPolicy.All); EnableEvent(EventType.AssemblyUnload, SuspendPolicy.All); EnableEvent(EventType.ThreadStart, SuspendPolicy.All); EnableEvent(EventType.TypeLoad, SuspendPolicy.All); EnableEvent(EventType.ThreadDeath, SuspendPolicy.None); if (vm!.Version.AtLeast(2, 5)) { EnableEvent(EventType.UserLog, SuspendPolicy.All); if (!debuggerSettings.IgnoreBreakInstructions) EnableEvent(EventType.UserBreak, SuspendPolicy.All); } if (vm.Version.AtLeast(2, 1)) { uncaughtRequest = vm.CreateExceptionRequest(null, false, true); caughtRequest = vm.CreateExceptionRequest(null, true, false); } else caughtRequest = vm.CreateExceptionRequest(null, true, true); uncaughtRequest?.Enable(); caughtRequest?.Enable(); if (processWasRunningOnAttach) canInitializeObjectConstants = true; else { methodEntryEventRequest = vm.CreateMethodEntryRequest(); methodEntryEventRequest.Enable(); } } catch (VMDisconnectedException) { } } void OnDebuggerEvents() { for (;;) { EventSet? eventSet = null; EventSet[]? eventSets = null; lock (pendingEventSets) { if (pendingEventSets.Count == 0) return; if (pendingEventSets.Count == 1) eventSet = pendingEventSets[0]; else eventSets = pendingEventSets.ToArray(); pendingEventSets.Clear(); } if (eventSet is not null) OnDebuggerEvents(eventSet); else { foreach (var e in eventSets!) OnDebuggerEvents(e); } } } void OnDebuggerEvents(EventSet eventSet) { try { eventHandlerRecursionCounter++; OnDebuggerEventsCore(eventSet); } catch (SocketException) { } catch (ObjectDisposedException) { } catch (VMDisconnectedException) { } finally { eventHandlerRecursionCounter--; } } int eventHandlerRecursionCounter; SuspendPolicy GetSuspendPolicy(EventSet eventSet) { var spolicy = eventSet.SuspendPolicy; // If it's the old debugger agent used by Unity, we can trust it if (monoDebugRuntimeKind == MonoDebugRuntimeKind.Unity) return spolicy; // The latest one sends one value but then possibly changes it and uses that value to // decide if it should suspend the process... foreach (var e in eventSet.Events) { switch (e.EventType) { case EventType.VMStart: spolicy = processWasRunningOnAttach ? SuspendPolicy.None : SuspendPolicy.All; break; case EventType.VMDeath: spolicy = SuspendPolicy.None; break; case EventType.ThreadStart: case EventType.ThreadDeath: case EventType.AppDomainCreate: case EventType.AppDomainUnload: case EventType.MethodEntry: case EventType.MethodExit: case EventType.AssemblyLoad: case EventType.AssemblyUnload: case EventType.Breakpoint: case EventType.Step: case EventType.TypeLoad: case EventType.Exception: case EventType.KeepAlive: case EventType.UserBreak: case EventType.UserLog: case EventType.VMDisconnect: break; default: Debug.Fail($"Unknown event {e.EventType}"); break; } } return spolicy; } void OnDebuggerEventsCore(EventSet eventSet) { debuggerThread.VerifyAccess(); Debug.Assert(!gotVMDisconnect); if (gotVMDisconnect) return; bool wasRunning = suspendCount == 0; var spolicy = GetSuspendPolicy(eventSet); if (spolicy == SuspendPolicy.All) IncrementSuspendCount(); int exitCode; int suspCounter = 0; for (int i = 0; i < eventSet.Events.Length; i++) { var evt = eventSet.Events[i]; SuspendPolicy expectedSuspendPolicy; switch (evt.EventType) { case EventType.VMStart: expectedSuspendPolicy = SuspendPolicy.All; processWasRunningOnAttach = spolicy == SuspendPolicy.None; SendMessage(new DbgMessageConnected(vmPid, GetMessageFlags())); break; case EventType.VMDeath: expectedSuspendPolicy = SuspendPolicy.None; var vmde = (VMDeathEvent)evt; if (vmDeathExitCode is not null) break; if (vm!.Version.AtLeast(2, 27)) vmDeathExitCode = vmde.ExitCode; else if (TryGetProcessExitCode(out exitCode)) vmDeathExitCode = exitCode; else vmDeathExitCode = 0; break; case EventType.ThreadStart: expectedSuspendPolicy = SuspendPolicy.All; var tse = (ThreadStartEvent)evt; SendMessage(new DelegatePendingMessage(true, false, () => InitializeDomain(tse.Thread.Domain))); SendMessage(new DelegatePendingMessage(true, true, () => CreateThread(tse.Thread))); break; case EventType.ThreadDeath: expectedSuspendPolicy = SuspendPolicy.None; var tde = (ThreadDeathEvent)evt; SendMessage(new DelegatePendingMessage(false, () => DestroyThread(TryGetThreadMirror(tde)))); break; case EventType.AppDomainCreate: expectedSuspendPolicy = SuspendPolicy.All; var adce = (AppDomainCreateEvent)evt; SendMessage(new DelegatePendingMessage(true, true, () => CreateAppDomain(adce.Domain, isNewAppDomainEvent: true))); break; case EventType.AppDomainUnload: expectedSuspendPolicy = SuspendPolicy.All; var adue = (AppDomainUnloadEvent)evt; SendMessage(new DelegatePendingMessage(true, () => DestroyAppDomain(adue.Domain))); break; case EventType.MethodEntry: expectedSuspendPolicy = SuspendPolicy.None; Debug.Assert(evt.TryGetRequest() == methodEntryEventRequest); if (methodEntryEventRequest is not null && evt.TryGetRequest() == methodEntryEventRequest) { methodEntryEventRequest.Disable(); methodEntryEventRequest = null; // Func-eval doesn't work at first assembly load event for some reason. Should work now though. canInitializeObjectConstants = true; InitializeObjectConstants_MonoDebug(); } break; case EventType.AssemblyLoad: expectedSuspendPolicy = SuspendPolicy.All; var ale = (AssemblyLoadEvent)evt; SendMessage(new DelegatePendingMessage(true, false, () => InitializeDomain(ale.Assembly.Domain))); // The debugger agent doesn't support netmodules... SendMessage(new DelegatePendingMessage(true, true, () => CreateModule(ale.Assembly.ManifestModule))); break; case EventType.AssemblyUnload: expectedSuspendPolicy = SuspendPolicy.All; var aue = (AssemblyUnloadEvent)evt; var monoModule = TryGetModuleCore_NoCreate(aue.Assembly.ManifestModule); if (monoModule is null) expectedSuspendPolicy = SuspendPolicy.None; else { foreach (var module in GetAssemblyModules(monoModule)) { if (!TryGetModuleData(module, out var data)) continue; var tmp = data.MonoModule; SendMessage(new DelegatePendingMessage(true, () => DestroyModule(tmp))); } } break; case EventType.Breakpoint: expectedSuspendPolicy = SuspendPolicy.All; var be = (BreakpointEvent)evt; var bpReq = be.TryGetRequest() as BreakpointEventRequest; if (bpReq is not null) { if (breakOnEntryPointData?.Breakpoint == bpReq) { bpReq.Disable(); breakOnEntryPointData = null; SendMessage(new DbgMessageEntryPointBreak(TryGetThread(be.Thread), GetMessageFlags())); } else { if (!SendCodeBreakpointHitMessage_MonoDebug(bpReq, TryGetThread(be.Thread))) expectedSuspendPolicy = SuspendPolicy.None; } } else { // It's a removed BP. Repro: Step and stop on a BP, remove the BP, step again expectedSuspendPolicy = SuspendPolicy.None; } break; case EventType.Step: var se = (StepEvent)evt; if (OnStep(se.TryGetRequest() as StepEventRequest)) expectedSuspendPolicy = SuspendPolicy.All; else expectedSuspendPolicy = SuspendPolicy.None; break; case EventType.TypeLoad: expectedSuspendPolicy = SuspendPolicy.None; var tle = (TypeLoadEvent)evt; // Add it to the cache var reflectionAppDomain = TryGetEngineAppDomain(tle.Type.Assembly.Domain)?.AppDomain.GetReflectionAppDomain(); if (reflectionAppDomain is not null) { try { GetReflectionType(reflectionAppDomain, tle.Type, null); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { } } InitializeBreakpoints(tle.Type); break; case EventType.Exception: var ee = (ExceptionEvent)evt; // Unhandled exceptions in Unity seems to be ignored by Unity. Report them to the user but // don't set isUnhandledException to true since we won't be able to func-eval. if (ee.TryGetRequest() == uncaughtRequest && monoDebugRuntimeKind == MonoDebugRuntimeKind.Mono) isUnhandledException = true; if (IsEvaluating && !isUnhandledException) { expectedSuspendPolicy = SuspendPolicy.None; break; } expectedSuspendPolicy = SuspendPolicy.All; thrownException = ee.Exception; SendMessage(new DelegatePendingMessage(true, () => { var req = ee.TryGetRequest() as ExceptionEventRequest; DbgExceptionEventFlags exFlags; if (req == caughtRequest) exFlags = DbgExceptionEventFlags.FirstChance; else if (req == uncaughtRequest) exFlags = DbgExceptionEventFlags.SecondChance | DbgExceptionEventFlags.Unhandled; else { Debug.Fail("Unknown exception request"); exFlags = DbgExceptionEventFlags.FirstChance; } var exObj = ee.Exception; objectFactory!.CreateException(new DbgExceptionId(PredefinedExceptionCategories.DotNet, TryGetExceptionName(exObj) ?? "???"), exFlags, EvalReflectionUtils.TryGetExceptionMessage(exObj), TryGetThread(ee.Thread), TryGetModule(ee.Thread), GetMessageFlags()); })); break; case EventType.UserBreak: expectedSuspendPolicy = SuspendPolicy.All; var ube = (UserBreakEvent)evt; SendMessage(new DbgMessageBreak(TryGetThread(ube.Thread), GetMessageFlags())); break; case EventType.UserLog: expectedSuspendPolicy = SuspendPolicy.All; var ule = (UserLogEvent)evt; SendMessage(new NormalPendingMessage(this, true, new DbgMessageProgramMessage(ule.Message, TryGetThread(ule.Thread), GetMessageFlags()))); break; case EventType.VMDisconnect: expectedSuspendPolicy = SuspendPolicy.None; if (vmDeathExitCode is null && TryGetProcessExitCode(out exitCode)) vmDeathExitCode = exitCode; if (vmDeathExitCode is null) { vmDeathExitCode = -1; dbgManager.ShowError(dnSpy_Debugger_DotNet_Mono_Resources.Error_ConnectionWasUnexpectedlyClosed); } Message?.Invoke(this, new DbgMessageDisconnected(vmDeathExitCode.Value, GetMessageFlags())); gotVMDisconnect = true; consoleStdOut?.Dispose(); consoleStdErr?.Dispose(); consoleStdOut = null; consoleStdErr = null; break; default: expectedSuspendPolicy = SuspendPolicy.None; Debug.Fail($"Unknown event type: {evt.EventType}"); break; } // If it's the first iteration, don't suspend it if it must be suspended since // it was suspended by the debugger agent. int suspDir; if (expectedSuspendPolicy == SuspendPolicy.All && spolicy == SuspendPolicy.All) suspDir = i == 0 ? 0 : 1; else if (expectedSuspendPolicy == SuspendPolicy.All && spolicy == SuspendPolicy.None) suspDir = 1; else if (expectedSuspendPolicy == SuspendPolicy.None && spolicy == SuspendPolicy.All) suspDir = i == 0 ? -1 : 0; else if (expectedSuspendPolicy == SuspendPolicy.None && spolicy == SuspendPolicy.None) suspDir = 0; else { Debug.Fail("Shouldn't be here"); suspDir = 0; } suspCounter += suspDir; } if (suspCounter < 0) { Debug.Assert(suspCounter == -1); while (suspCounter++ < 0) { if (!DecrementAndResume()) break; } } else if (suspCounter > 0) { while (suspCounter-- > 0) { if (!IncrementAndSuspend()) break; } } if (wasRunning && pendingRunCore && eventHandlerRecursionCounter == 1) { pendingRunCore = false; RunCore(); } } bool pendingRunCore; ThreadMirror? TryGetThreadMirror(ThreadDeathEvent tde2) { try { return tde2.Thread; } catch (ObjectCollectedException) { Debug.Assert(!vm!.Version.AtLeast(2, 2)); return null; } } bool TryGetProcessExitCode(out int exitCode) { if (!hProcess_debuggee!.IsClosed && !hProcess_debuggee.IsInvalid) { if (NativeMethods.GetExitCodeProcess(hProcess_debuggee.DangerousGetHandle(), out exitCode)) return true; } exitCode = 0; return false; } DbgModule? TryGetModule(ThreadMirror thread) { var frames = thread.GetFrames(); if (frames.Length == 0) return null; return TryGetModule(frames[0].Method?.DeclaringType.Module); } string? TryGetExceptionName(ObjectMirror exObj) { var reflectionAppDomain = TryGetEngineAppDomain(exObj.Domain)?.AppDomain.GetReflectionAppDomain(); if (reflectionAppDomain is null) return exObj.Type.FullName; var type = GetReflectionType(reflectionAppDomain, exObj.Type, null); if (type.IsConstructedGenericType) type = type.GetGenericTypeDefinition(); return type.FullName; } DbgEngineAppDomain? TryGetEngineAppDomain(AppDomainMirror monoAppDomain) { if (monoAppDomain is null) return null; DbgEngineAppDomain? engineAppDomain; bool b; lock (lockObj) b = toEngineAppDomain.TryGetValue(monoAppDomain, out engineAppDomain); if (!b) { //TODO: This sometimes fails } return engineAppDomain; } int GetAppDomainId(AppDomainMirror monoAppDomain) { debuggerThread.VerifyAccess(); // We don't func-eval because of Unity func-eval crashes, just use an ID that's probably correct return nextAppDomainId++; } int nextAppDomainId = 1; bool InitializeDomain(AppDomainMirror monoAppDomain) { debuggerThread.VerifyAccess(); DbgEngineAppDomain? engineAppDomain; bool b; lock (lockObj) { if (!appDomainsThatHaveNotBeenInitializedYet.Remove(monoAppDomain)) return false; b = toEngineAppDomain.TryGetValue(monoAppDomain, out engineAppDomain); } Debug.Assert(b); if (b) engineAppDomain!.UpdateName(monoAppDomain.FriendlyName); return CreateModule(monoAppDomain.Corlib.ManifestModule); } bool CreateAppDomain(AppDomainMirror monoAppDomain, bool isNewAppDomainEvent) { debuggerThread.VerifyAccess(); lock (lockObj) { if (toEngineAppDomain.ContainsKey(monoAppDomain)) return false; } int appDomainId = GetAppDomainId(monoAppDomain); var appDomain = dmdRuntime!.CreateAppDomain(appDomainId); var internalAppDomain = new DbgMonoDebugInternalAppDomainImpl(appDomain, monoAppDomain); var appDomainName = monoAppDomain.FriendlyName; var engineAppDomain = objectFactory!.CreateAppDomain(internalAppDomain, appDomainName, appDomainId, GetMessageFlags(), data: null, onCreated: engineAppDomain2 => internalAppDomain.SetAppDomain(engineAppDomain2.AppDomain)); lock (lockObj) { if (isNewAppDomainEvent) appDomainsThatHaveNotBeenInitializedYet.Add(monoAppDomain); toEngineAppDomain.Add(monoAppDomain, engineAppDomain); } return true; } void DestroyAppDomain(AppDomainMirror monoAppDomain) { debuggerThread.VerifyAccess(); DbgEngineAppDomain? engineAppDomain; lock (lockObj) { if (toEngineAppDomain.TryGetValue(monoAppDomain, out engineAppDomain)) { appDomainsThatHaveNotBeenInitializedYet.Remove(monoAppDomain); toEngineAppDomain.Remove(monoAppDomain); var appDomain = engineAppDomain.AppDomain; dmdRuntime!.Remove(((DbgMonoDebugInternalAppDomainImpl)appDomain.InternalAppDomain).ReflectionAppDomain); foreach (var kv in toEngineThread.ToArray()) { if (kv.Value.Thread.AppDomain == appDomain) toEngineThread.Remove(kv.Key); } foreach (var kv in toEngineModule.ToArray()) { if (kv.Value.Module.AppDomain == appDomain) { toEngineModule.Remove(kv.Key); kv.Value.Remove(GetMessageFlags()); } } } } if (engineAppDomain is not null) engineAppDomain.Remove(GetMessageFlags()); } sealed class DbgModuleData { public DbgEngineImpl Engine { get; } public ModuleMirror MonoModule { get; } public ModuleId ModuleId { get; set; } public DbgModuleData(DbgEngineImpl engine, ModuleMirror monoModule) { Engine = engine; MonoModule = monoModule; } } internal ModuleId GetModuleId(DbgModule module) { if (TryGetModuleData(module, out var data)) return data.ModuleId; throw new InvalidOperationException(); } internal static ModuleId? TryGetModuleId(DbgModule module) { if (module.TryGetData(out DbgModuleData? data)) return data.ModuleId; return null; } bool TryGetModuleData(DbgModule module, [NotNullWhen(true)] out DbgModuleData? data) { if (module.TryGetData(out data) && data.Engine == this) return true; data = null; return false; } int moduleOrder; bool CreateModule(ModuleMirror monoModule) { debuggerThread.VerifyAccess(); if (TryGetModuleCore_NoCreate(monoModule) is not null) return false; var appDomain = TryGetEngineAppDomain(monoModule.Assembly.Domain)?.AppDomain; if (appDomain is null) return false; var moduleData = new DbgModuleData(this, monoModule); var engineModule = ModuleCreator.CreateModule(this, objectFactory!, appDomain, monoModule, ++moduleOrder, moduleData); moduleData.ModuleId = ModuleIdUtils.Create(engineModule.Module, monoModule); lock (lockObj) { if (!toAssemblyModules.TryGetValue(monoModule.Assembly, out var modules)) toAssemblyModules.Add(monoModule.Assembly, modules = new List()); modules.Add(monoModule); toEngineModule.Add(monoModule, engineModule); } if (breakOnEntryPointData is not null && breakOnEntryPointData.Breakpoint is null && StringComparer.OrdinalIgnoreCase.Equals(breakOnEntryPointData.Filename, engineModule.Module.Filename)) { try { CreateEntryPointBreakpoint(monoModule.Assembly.EntryPoint); } catch (Exception ex) { Debug.Fail(ex.ToString()); } } return true; } void CreateEntryPointBreakpoint(MethodMirror monoMethod) { if (monoMethod is null) return; breakOnEntryPointData!.Breakpoint = vm!.CreateBreakpointRequest(monoMethod, 0); breakOnEntryPointData.Breakpoint.Enable(); } void DestroyModule(ModuleMirror monoModule) { debuggerThread.VerifyAccess(); DbgEngineModule? engineModule; lock (lockObj) { if (toAssemblyModules.TryGetValue(monoModule.Assembly, out var modules)) { modules.Remove(monoModule); if (modules.Count == 0) toAssemblyModules.Remove(monoModule.Assembly); } if (toEngineModule.TryGetValue(monoModule, out engineModule)) { toEngineModule.Remove(monoModule); ((DbgMonoDebugInternalModuleImpl)engineModule.Module.InternalModule).Remove(); } } if (engineModule is not null) engineModule.Remove(GetMessageFlags()); } internal DbgModule? TryGetModule(ModuleMirror? monoModule) { if (monoModule is null) return null; var res = TryGetModuleCore_NoCreate(monoModule); if (res is not null) return res; DiscoverNewModules(monoModule); res = TryGetModuleCore_NoCreate(monoModule); Debug2.Assert(res is not null); return res; } DbgModule? TryGetModuleCore_NoCreate(ModuleMirror monoModule) { if (monoModule is null) return null; lock (lockObj) { if (toEngineModule.TryGetValue(monoModule, out var engineModule)) return engineModule.Module; } return null; } // The debugger agent doesn't send assembly load events for assemblies that have already been // loaded in some other AppDomain. This method discovers these assemblies. It should be called // when we've found a new module. void DiscoverNewModules(ModuleMirror monoModule) { debuggerThread.VerifyAccess(); if (monoModule is not null) { Debug.Assert(monoModule.Assembly.ManifestModule == monoModule); AddNewModule(monoModule); } KeyValuePair[] appDomains; lock (lockObj) appDomains = toEngineAppDomain.ToArray(); foreach (var kv in appDomains) { foreach (var monoAssembly in kv.Key.GetAssemblies()) AddNewModule(monoAssembly.ManifestModule); } } void AddNewModule(ModuleMirror monoModule) { debuggerThread.VerifyAccess(); if (TryGetModuleCore_NoCreate(monoModule) is not null) return; if (suspendCount == 0) { try { IncrementAndSuspend(); } catch (Exception ex) { Debug.Fail(ex.Message); SendMessage(new DbgMessageBreak(ex.Message, GetMessageFlags())); } Debug.Assert(pendingMessages.Count == 0); } CreateModule(monoModule); } internal bool TryGetMonoModule(DbgModule module, [NotNullWhen(true)] out ModuleMirror? monoModule) { if (module.TryGetData(out DbgModuleData? data) && data.Engine == this) { monoModule = data.MonoModule; return true; } monoModule = null; return false; } DbgThread? GetThreadPreferMain_MonoDebug() { debuggerThread.VerifyAccess(); DbgThread? firstThread = null; lock (lockObj) { foreach (var kv in toEngineThread) { var thread = TryGetThread(kv.Key); if (firstThread is null) firstThread = thread; if (thread?.IsMain == true) return thread; } } return firstThread; } internal DbgThread? TryGetThread(ThreadMirror thread) { if (thread is null) return null; DbgEngineThread? engineThread; lock (lockObj) toEngineThread.TryGetValue(thread, out engineThread); return engineThread?.Thread; } class StartException : Exception { public StartException(string message) : base(message) { } } sealed class CouldNotConnectException : StartException { public CouldNotConnectException(string message) : base(message) { } } static string GetCouldNotConnectErrorMessage(string address, ushort port, string? filenameOpt) { string extra = filenameOpt is null ? $" ({address}:{port})" : $" ({address}:{port} = {filenameOpt})"; return dnSpy_Debugger_DotNet_Mono_Resources.Error_CouldNotConnectToProcess + extra; } internal IDbgDotNetRuntime DotNetRuntime => internalRuntime!; DbgMonoDebugInternalRuntimeImpl? internalRuntime; public override DbgInternalRuntime CreateInternalRuntime(DbgRuntime runtime) { if (internalRuntime is not null) throw new InvalidOperationException(); dmdRuntime = DmdRuntimeFactory.CreateRuntime(new DmdEvaluatorImpl(this), runtime.Process.PointerSize == 4 ? DmdImageFileMachine.I386 : DmdImageFileMachine.AMD64); return internalRuntime = new DbgMonoDebugInternalRuntimeImpl(this, runtime, dmdRuntime, monoDebugRuntimeKind); } sealed class RuntimeData { public DbgEngineImpl Engine { get; } public RuntimeData(DbgEngineImpl engine) => Engine = engine; } internal static DbgEngineImpl? TryGetEngine(DbgRuntime runtime) { if (runtime.TryGetData(out RuntimeData? data)) return data.Engine; return null; } internal DbgModule[] GetAssemblyModules(DbgModule module) { if (!TryGetModuleData(module, out var data)) return Array.Empty(); lock (lockObj) { toAssemblyModules.TryGetValue(data.MonoModule.Assembly, out var modules); if (modules is null || modules.Count == 0) return Array.Empty(); var res = new List(modules.Count); foreach (var monoModule in modules) { if (toEngineModule.TryGetValue(monoModule, out var engineModule)) res.Add(engineModule.Module); } return res.ToArray(); } } public override void OnConnected(DbgObjectFactory objectFactory, DbgRuntime runtime) { Debug.Assert(objectFactory.Runtime == runtime); Debug.Assert(Array.IndexOf(objectFactory.Process.Runtimes, runtime) < 0); this.objectFactory = objectFactory; runtime.GetOrCreateData(() => new RuntimeData(this)); MonoDebugThread(() => OnConnected_MonoDebug()); } void OnConnected_MonoDebug() { debuggerThread.VerifyAccess(); try { if (gotVMDisconnect) return; if (consoleStdOut is not null) ReadConsoleOutputAsync(); Debug2.Assert(vm is not null); if (vm is not null) { InitializeVirtualMachine(); // Create the root AppDomain now since we want it to get id=1, which isn't guaranteed // if it's an attach and we wait for AppDomainCreate events. SendMessage(new DelegatePendingMessage(true, false, () => CreateAppDomain(vm.RootDomain, isNewAppDomainEvent: !processWasRunningOnAttach))); // We need to add all threads even if it's an attach. Unity notifies us of all threads, // except it sends the same thread N times in a row (N = number of threads). foreach (var monoThread in vm.GetThreads()) { SendMessage(new DelegatePendingMessage(true, false, () => CreateAppDomain(monoThread.Domain, isNewAppDomainEvent: !processWasRunningOnAttach))); SendMessage(new DelegatePendingMessage(true, false, () => CreateModule(monoThread.Domain.Corlib.ManifestModule))); SendMessage(new DelegatePendingMessage(true, false, () => CreateThread(monoThread))); } } } catch (VMDisconnectedException) { } } public override void Break() => MonoDebugThread(BreakCore); void BreakCore() { debuggerThread.VerifyAccess(); if (!HasConnected_MonoDebugThread) return; try { bool sendMsg = true; if (suspendCount == 0) sendMsg = IncrementAndSuspend(); if (sendMsg) SendMessage(new DbgMessageBreak(GetThreadPreferMain_MonoDebug(), GetMessageFlags())); } catch (Exception ex) { Debug.Fail(ex.Message); SendMessage(new DbgMessageBreak(ex.Message, GetMessageFlags())); } } public override void Run() => MonoDebugThread(RunCore); internal void RunCore() { debuggerThread.VerifyAccess(); if (!HasConnected_MonoDebugThread) return; try { continueCounter++; if (!IsEvaluating) CloseDotNetValues_MonoDebug(); if (runCounter != nextSendRunCounter) runCounter++; if (SendNextMessage()) return; if (IsEvaluating) { pendingRunCore = true; return; } ResumeCore(); } catch (VMDisconnectedException) { } catch (Exception ex) { Debug.Fail(ex.Message); dbgManager.ShowError(ex.Message); } } internal uint ContinueCounter => continueCounter; volatile uint continueCounter; internal bool IsPaused => suspendCount > 0; void ResumeCore() { debuggerThread.VerifyAccess(); while (suspendCount > 0) { thrownException = null; DecrementAndResume(); } } public override void Terminate() => MonoDebugThread(TerminateCore); void TerminateCore() { debuggerThread.VerifyAccess(); if (!HasConnected_MonoDebugThread) return; try { // If we got an unhandled exception, the next event is VMDisconnect so just Resume() it if (isUnhandledException) ResumeCore(); else vm!.Exit(0); } catch (VMDisconnectedException) { } catch (Exception ex) { Debug.Fail(ex.Message); dbgManager.ShowError(ex.Message); } } public override bool CanDetach => true; public override void Detach() => MonoDebugThread(DetachCore); void DetachCore() { debuggerThread.VerifyAccess(); if (!HasConnected_MonoDebugThread) return; try { vm!.Detach(); vmDeathExitCode = -1; } catch (VMDisconnectedException) { } catch (Exception ex) { Debug.Fail(ex.Message); dbgManager.ShowError(ex.Message); } } internal DbgDotNetValue? TryGetExceptionValue() { debuggerThread.VerifyAccess(); var exValue = thrownException; if (exValue is null) return null; var reflectionAppDomain = TryGetEngineAppDomain(exValue.Domain)?.AppDomain.GetReflectionAppDomain(); if (reflectionAppDomain is null) return null; var exceptionType = GetReflectionType(reflectionAppDomain, exValue.Type, null); var valueLocation = new NoValueLocation(exceptionType, exValue); return CreateDotNetValue_MonoDebug(valueLocation); } protected override void CloseCore(DbgDispatcher dispatcher) { debuggerThread.Terminate(); try { if (!gotVMDisconnect) vm?.Detach(); } catch { } try { vm?.ForceDisconnect(); } catch { } hProcess_debuggee?.Close(); foreach (var kv in toStepper) kv.Value.OnStep(new StepCompleteEventArgs(kv.Key, true, false)); toStepper.Clear(); } readonly struct TempBreakHelper : IDisposable { readonly DbgEngineImpl engine; readonly bool pausedIt; public TempBreakHelper(DbgEngineImpl engine) { this.engine = engine; bool pausedIt = engine.suspendCount == 0; if (pausedIt) engine.vm!.Suspend(); this.pausedIt = pausedIt; } public void Dispose() { if (pausedIt) engine.ResumeVirtualMachine(); } } TempBreakHelper TempBreak() => new TempBreakHelper(this); internal DmdType GetReflectionType(DmdAppDomain reflectionAppDomain, TypeMirror monoType, DmdType? couldBeRealTypeOpt) { // Older debugger agents (eg. the one used by Unity) can't return the generic arguments, so we // can't create the correct instantiated generic type. We can't create a generic DmdType from a // TypeMirror. If we cache the generic DmdType, we'll be able to look it up later when we get // a generic TypeMirror. if (couldBeRealTypeOpt is not null && !vm!.Version.AtLeast(2, 15)) { try { MonoDebugTypeCreator.GetType(this, couldBeRealTypeOpt, null); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { } } return new ReflectionTypeCreator(this, reflectionAppDomain).Create(monoType); } } }