diff --git a/src/MICore/CommandFactories/gdb.cs b/src/MICore/CommandFactories/gdb.cs index 3d769521a..d16ca3a59 100644 --- a/src/MICore/CommandFactories/gdb.cs +++ b/src/MICore/CommandFactories/gdb.cs @@ -207,6 +207,7 @@ public override async Task Terminate() // that isn't actually supported by gdb. await _debugger.CmdAsync("kill", ResultClass.None); } + private static string TypeBySize(uint size) { switch (size) diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs index 25c18d3f2..8e3dbe9d9 100755 --- a/src/MICore/Debugger.cs +++ b/src/MICore/Debugger.cs @@ -11,6 +11,7 @@ using System.Linq; using Microsoft.Win32.SafeHandles; using Microsoft.DebugEngineHost; +using System.Runtime.InteropServices; namespace MICore { @@ -49,6 +50,8 @@ public class Debugger : ITransportCallback public bool IsCygwin { get; protected set; } + public bool IsMinGW { get; protected set; } + public bool SendNewLineAfterCmd { get; protected set; } public virtual void FlushBreakStateData() @@ -233,7 +236,7 @@ public Task AddInternalBreakAction(Func func) _retryCount = 0; _waitingToStop = true; - // When using signals to stop the proces, do not kick off another break attempt. The debug break injection and + // When using signals to stop the process, do not kick off another break attempt. The debug break injection and // signal based models are reliable so no retries are needed. Cygwin can't currently async-break reliably, so // use retries there. if (!IsLocalGdb() && !this.IsCygwin) @@ -573,23 +576,18 @@ public Task CmdBreak(BreakRequest request) internal bool IsLocalGdb() { - if (this.MICommandFactory.Mode == MIMode.Gdb && + return (this.MICommandFactory.Mode == MIMode.Gdb && this._launchOptions is LocalLaunchOptions && - String.IsNullOrEmpty(((LocalLaunchOptions)this._launchOptions).MIDebuggerServerAddress) - ) - { - return true; - } - else - { - return false; - } + String.IsNullOrEmpty(((LocalLaunchOptions)this._launchOptions).MIDebuggerServerAddress)); + } private bool IsRemoteGdb() { return this.MICommandFactory.Mode == MIMode.Gdb && - this._launchOptions is PipeLaunchOptions; + (this._launchOptions is PipeLaunchOptions || + (this._launchOptions is LocalLaunchOptions + && !String.IsNullOrEmpty(((LocalLaunchOptions)this._launchOptions).MIDebuggerServerAddress))); } protected bool IsCoreDump @@ -609,9 +607,28 @@ public async Task CmdTerminate() if (!_terminating) { _terminating = true; - if (ProcessState == ProcessState.Running && this.MICommandFactory.Mode != MIMode.Clrdbg) - { - await AddInternalBreakAction(() => MICommandFactory.Terminate()); + if (ProcessState == ProcessState.Running && + this.MICommandFactory.Mode != MIMode.Clrdbg) + { + // MinGW and Cygwin on Windows don't support async break. Because of this, + // the normal path of sending an internal async break so we can exit doesn't work. + // Therefore, we will call TerminateProcess on the debuggee with the exit code of 0 + // to terminate debugging. + if (this.IsLocalGdb() && + (this.IsCygwin || this.IsMinGW) && + _debuggeePids.Count > 0) + { + if (TerminateAllPids()) + { + // OperationThread's _runningOpCompleteEvent is doing WaitOne(). Calling MICommandFactory.Terminate() will Set() it, unblocking the UI. + await MICommandFactory.Terminate(); + return new Results(ResultClass.done); + } + } + else + { + await AddInternalBreakAction(() => MICommandFactory.Terminate()); + } } else { @@ -622,6 +639,50 @@ public async Task CmdTerminate() return new Results(ResultClass.done); } + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hHandle); + + /// + /// Call PInvoke to terminate all debuggee PIDs. This is to solve MinGW/Cygwin issues in Windows and SHOULD NOT be used in other cases. + /// + /// True if any pids were terminated successfully + private bool TerminateAllPids() + { + var terminated = false; + foreach (var pid in _debuggeePids) + { + int debuggeePid = pid.Value; + IntPtr handle = IntPtr.Zero; + try + { + // 0x1 = Terminate + handle = OpenProcess(0x1, false, debuggeePid); + if (handle != IntPtr.Zero && TerminateProcess(handle, 0)) + { + terminated = true; + } + } + finally + { + if (handle != IntPtr.Zero) + { + bool close = CloseHandle(handle); + Debug.Assert(close, "Why did CloseHandle fail?"); + } + } + } + + return terminated; + } + + public async Task CmdDetach() { _detaching = true; diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index a6bdbda9c..7aa740ea1 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -790,6 +790,7 @@ private void CheckCygwin(List commands, LocalLaunchOptions localL } else { + this.IsMinGW = true; // Gdb on windows and not cygwin implies mingw _engineTelemetry.SendWindowsRuntimeEnvironment(EngineTelemetry.WindowsRuntimeEnvironment.MinGW); } diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs index 74a9510fd..cea81d92a 100644 --- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs @@ -217,7 +217,11 @@ internal async Task ThreadCreatedEvent(int id, string groupId) Results results = await _debugger.MICommandFactory.ThreadInfo(tid); if (results.ResultClass != ResultClass.done) { - Debug.Fail("Thread info not successful"); + // This can happen on some versions of gdb where thread-info is not supported while running, so only assert if we're also not running. + if (this._debugger.ProcessState != ProcessState.Running) + { + Debug.Fail("Thread info not successful"); + } } else {