diff --git a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs index a5b1c7f..4f6dde1 100644 --- a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs +++ b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs @@ -478,7 +478,10 @@ private void Dispose() // Unsubscribe from callbacks to avoid any further event dispatch _callbacks.OnAnyEvent -= QueueEvent; - _runtimeEventChannel.Writer.Complete(); + // TryComplete because this can run twice: Terminate disposes whether or not it succeeded, so a + // client that sends terminate and then disconnect would get a ChannelClosedException out of the + // second one + _runtimeEventChannel.Writer.TryComplete(); // ProcessRuntimeEventQueue is blocked on DapRequestAndRuntimeEventLock (which we hold) and would // never complete if we waited on it here — that is the deadlock. Read and discard remaining events ourselves, // then let the processor exit once the lock is released. diff --git a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs index d07d7f8..bb1f2fc 100644 --- a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs +++ b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs @@ -648,6 +648,15 @@ public void Terminate() { try { + // Terminate needs the process synchronized. On a running one it fails with + // CORDBG_E_PROCESS_NOT_SYNCHRONIZED and leaves the debuggee running. + if (_process.TryIsRunning(out var isRunning) is Cor.S_OK && isRunning) + { + var stopHResult = _process.TryStop(0); + if (stopHResult is not (Cor.S_OK or Cor.CORDBG_E_PROCESS_TERMINATED)) + _logger?.Invoke($"Error stopping process before terminating it: {stopHResult}"); + } + _process.Terminate(0); } catch (Exception ex)