Improve debuggee process pause routine.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 20 Jul 2022 12:55:33 +0000 (15:55 +0300)
committerAlexander Soldatov/Platform Lab /SRR/Staff Engineer/Samsung Electronics <soldatov.a@samsung.com>
Fri, 22 Jul 2022 14:25:43 +0000 (17:25 +0300)
Add thread ID support to VSCode protocol "pause" command.

src/debugger/managedcallback.cpp
src/debugger/managedcallback.h
src/debugger/manageddebugger.cpp
src/debugger/manageddebugger.h
src/interfaces/idebugger.h
src/protocols/cliprotocol.cpp
src/protocols/miprotocol.cpp
src/protocols/vscodeprotocol.cpp

index 4ff0b29d8200413065626825a04370ecd6805551..5e0511dc17c5aa537d3cdd1d8cd8e2ca3ab490c8 100644 (file)
@@ -26,6 +26,8 @@
 #include "interfaces/iprotocol.h"
 #include "utils/utf.h"
 
+#include <algorithm>
+
 namespace netcoredbg
 {
 
@@ -275,19 +277,42 @@ HRESULT ManagedCallback::Continue(ICorDebugProcess *pProcess)
     return S_OK;
 }
 
-HRESULT ManagedCallback::Pause(ICorDebugProcess *pProcess)
+// Caller should care about m_callbacksMutex.
+// Check stop status and stop, if need.
+// Return S_FALSE in case already was stopped, S_OK in case stopped by this call.
+static HRESULT InternalStop(ICorDebugProcess *pProcess, bool &stopEventInProcess)
+{
+    if (stopEventInProcess)
+        return S_FALSE; // Already stopped.
+
+    HRESULT Status;
+    IfFailRet(pProcess->Stop(0));
+    stopEventInProcess = true;
+    return S_OK;
+}
+
+// Analog of "pProcess->Stop(0)" call that also care about callbacks.
+HRESULT ManagedCallback::Stop(ICorDebugProcess *pProcess)
 {
     std::unique_lock<std::mutex> lock(m_callbacksMutex);
+    // DO NOT reset steppers here, this is "pProcess->Stop(0)" like call, that care about callbacks.
+    return InternalStop(pProcess, m_stopEventInProcess);
+}
 
-    if (m_stopEventInProcess)
-        return S_OK;
+// Stop process and set last stopped thread. If `lastStoppedThread` not passed value from protocol, find best thread.
+HRESULT ManagedCallback::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread)
+{
+    // Must be real thread ID or ThreadId::AllThreads.
+    if (!lastStoppedThread)
+        return E_INVALIDARG;
+
+    std::unique_lock<std::mutex> lock(m_callbacksMutex);
 
     // Note, in case Stop() failed, no stop event will be emitted, don't set m_stopEventInProcess to "true" in this case.
     HRESULT Status;
-    if (FAILED(Status = pProcess->Stop(0)))
-        return Status;
-
-    m_stopEventInProcess = true;
+    IfFailRet(InternalStop(pProcess, m_stopEventInProcess));
+    if (Status == S_FALSE) // Already stopped.
+        return S_OK;
 
     // Same logic as provide vsdbg in case of pause during stepping.
     m_debugger.m_uniqueSteppers->DisableAllSteppers(pProcess);
@@ -297,47 +322,64 @@ HRESULT ManagedCallback::Pause(ICorDebugProcess *pProcess)
     std::vector<Thread> threads;
     m_debugger.GetThreads(threads);
 
-    ThreadId lastStoppedId = m_debugger.GetLastStoppedThreadId();
-
-    // Reorder threads so that last stopped thread is checked first
-    for (size_t i = 0; i < threads.size(); ++i)
+    // In case `lastStoppedThread` provided, just check that we really have it.
+    if (lastStoppedThread != ThreadId::AllThreads)
     {
-        if (threads[i].id == lastStoppedId)
+        // In case VSCode protocol, user provide "pause" thread id.
+        if (std::find_if(threads.begin(), threads.end(), [&](Thread t){ return t.id == lastStoppedThread; }) != threads.end())
         {
-            std::swap(threads[0], threads[i]);
-            break;
+            // VSCode protocol event must provide thread only (VSCode count on this), even if this thread don't have user code.
+            m_debugger.SetLastStoppedThreadId(lastStoppedThread);
+            m_debugger.m_sharedProtocol->EmitStoppedEvent(StoppedEvent(StopPause, lastStoppedThread));
+            m_debugger.m_ioredirect.async_cancel();
+            return S_OK;
         }
     }
-
-    // Now get stack trace for each thread and find a frame with valid source location.
-    for (const Thread& thread : threads)
+    else
     {
-        int totalFrames = 0;
-        std::vector<StackFrame> stackFrames;
+        // MI and CLI protocols provide ThreadId::AllThreads as lastStoppedThread, this protocols require thread and frame with user code.
+        // Note, MIEngine (MI/GDB) require frame connected to user source or it will crash Visual Studio.
+
+        ThreadId lastStoppedId = m_debugger.GetLastStoppedThreadId();
 
-        if (FAILED(m_debugger.GetStackTrace(thread.id, FrameLevel(0), 0, stackFrames, totalFrames)))
-            continue;
+        // Reorder threads so that last stopped thread is checked first
+        for (size_t i = 0; i < threads.size(); ++i)
+        {
+            if (threads[i].id == lastStoppedId)
+            {
+                std::swap(threads[0], threads[i]);
+                break;
+            }
+        }
 
-        for (const StackFrame& stackFrame : stackFrames)
+        // Now get stack trace for each thread and find a frame with valid source location.
+        for (const Thread& thread : threads)
         {
-            if (stackFrame.source.IsNull())
+            int totalFrames = 0;
+            std::vector<StackFrame> stackFrames;
+
+            if (FAILED(m_debugger.GetStackTrace(thread.id, FrameLevel(0), 0, stackFrames, totalFrames)))
                 continue;
 
-            StoppedEvent event(StopPause, thread.id);
-            event.frame = stackFrame;
-            m_debugger.SetLastStoppedThreadId(thread.id);
-            m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
-            m_debugger.m_ioredirect.async_cancel();
-            return S_OK;
+            for (const StackFrame& stackFrame : stackFrames)
+            {
+                if (stackFrame.source.IsNull())
+                    continue;
+
+                StoppedEvent event(StopPause, thread.id);
+                event.frame = stackFrame;
+                m_debugger.SetLastStoppedThreadId(thread.id);
+                m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+                m_debugger.m_ioredirect.async_cancel();
+                return S_OK;
+            }
         }
     }
 
-    assert(threads.size() > 0);
-    // Event must provide thread (VSCode and MI/GDB protocols count on this), even if this thread don't have user code.
-    // Note, provide thread without user code also legit for this event.
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(StoppedEvent(StopPause, threads[0].id));
-    m_debugger.m_ioredirect.async_cancel();
-    return S_OK;
+    // Fatal error during stop, just fail Pause request and don't stop process.
+    m_stopEventInProcess = false;
+    IfFailRet(pProcess->Continue(0));
+    return E_FAIL;
 }
 
 ManagedCallback::~ManagedCallback()
index bfc45bd2e684e768393c1124611cc86e1a1c6c9f..e8147a23af4f579250de82e7bb528e7386400680 100644 (file)
@@ -87,10 +87,13 @@ public:
     ~ManagedCallback();
     ULONG GetRefCount();
 
-    // Called from ManagedDebugger by protocol request (Continue/StepCommand/Pause).
+    // Called from ManagedDebugger by protocol request (Continue/Pause).
     bool IsRunning();
     HRESULT Continue(ICorDebugProcess *pProcess);
-    HRESULT Pause(ICorDebugProcess *pProcess);
+    // Stop process and set last stopped thread. If `lastStoppedThread` not passed value from protocol, find best thread.
+    HRESULT Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread);
+    // Analog of "pProcess->Stop(0)" call that also care about callbacks.
+    HRESULT Stop(ICorDebugProcess *pProcess);
 
     // IUnknown
 
index 164b71c365b4bd21f4714e8fb23a646c6db31619..1d023468a9a7b404398fb7f6552bdd83a810c6ca 100644 (file)
@@ -363,7 +363,7 @@ HRESULT ManagedDebugger::Continue(ThreadId threadId)
     return Status;
 }
 
-HRESULT ManagedDebugger::Pause()
+HRESULT ManagedDebugger::Pause(ThreadId lastStoppedThread)
 {
     LogFuncEntry();
 
@@ -371,7 +371,7 @@ HRESULT ManagedDebugger::Pause()
     HRESULT Status;
     IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
 
-    return m_managedCallback->Pause(m_iCorProcess);
+    return m_managedCallback->Pause(m_iCorProcess, lastStoppedThread);
 }
 
 HRESULT ManagedDebugger::GetThreads(std::vector<Thread> &threads)
@@ -1326,16 +1326,15 @@ HRESULT ManagedDebugger::HotReloadApplyDeltas(const std::string &dllFileName, co
 
     // Deltas can be applied only on stopped debuggee process. For Hot Reload scenario we temporary stop it and continue after deltas applied.
     HRESULT Status;
-    BOOL procRunning = FALSE;
-    IfFailRet(m_iCorProcess->IsRunning(&procRunning));
-    if (procRunning == TRUE)
-        IfFailRet(m_iCorProcess->Stop(0));
+    IfFailRet(m_managedCallback->Stop(m_iCorProcess));
+    bool continueProcess = (Status == S_OK); // Was stopped by m_managedCallback->Stop() call.
+
 
     IfFailRet(ApplyMetadataAndILDeltas(m_sharedModules.get(), dllFileName, deltaMD, deltaIL));
     IfFailRet(ApplyPdbDeltaAndLineUpdates(dllFileName, deltaPDB, lineUpdates));
 
-    if (procRunning == TRUE)
-        IfFailRet(m_iCorProcess->Continue(0));
+    if (continueProcess)
+        IfFailRet(m_managedCallback->Continue(m_iCorProcess));
 
     return S_OK;
 }
index e9353c6a955990f6ec4167a1ef4b26ac04f0d151..60a8b60fd49a6beb7e99e51fd5b5c7e2a9523544 100644 (file)
@@ -147,7 +147,7 @@ public:
     ThreadId GetLastStoppedThreadId() override;
     void InvalidateLastStoppedThreadId();
     HRESULT Continue(ThreadId threadId) override;
-    HRESULT Pause() override;
+    HRESULT Pause(ThreadId lastStoppedThread) override;
     HRESULT GetThreads(std::vector<Thread> &threads) override;
     HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints) override;
     HRESULT SetFuncBreakpoints(const std::vector<FuncBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
index 5ae51e314f3603050fc547f965d960fffff03bff..515caafba52ebb8ac9e942ee015b2dec2ad7dfde 100644 (file)
@@ -83,7 +83,7 @@ public:
     virtual HRESULT Disconnect(DisconnectAction action = DisconnectDefault) = 0;
     virtual ThreadId GetLastStoppedThreadId() = 0;
     virtual HRESULT Continue(ThreadId threadId) = 0;
-    virtual HRESULT Pause() = 0;
+    virtual HRESULT Pause(ThreadId lastStoppedThread) = 0;
     virtual HRESULT GetThreads(std::vector<Thread> &threads) = 0;
     virtual HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
     virtual HRESULT SetFuncBreakpoints(const std::vector<FuncBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
index 2c03b05dd9db7ec867fa137826c21c540ef282d8..7c47ff3edc3aae238cbc70989d3e652fc05745b2 100644 (file)
@@ -1552,7 +1552,7 @@ HRESULT CLIProtocol::doCommand<CommandTag::Interrupt>(const std::vector<std::str
     }
 
     HRESULT Status;
-    IfFailRet(m_sharedDebugger->Pause());
+    IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
     output = "^stopped";
     return S_OK;
 }
@@ -2535,7 +2535,7 @@ void CLIProtocol::Pause()
     if (m_processStatus == Running)
     {
         lock.unlock();
-        m_sharedDebugger->Pause();
+        m_sharedDebugger->Pause(ThreadId::AllThreads);
     }
 }
 
index 5386c249dde046a0ec76423d2bbca42079541fa6..5245189e9f670f30d274c813c7cf7f28b6837f3c 100644 (file)
@@ -704,7 +704,7 @@ HRESULT MIProtocol::HandleCommand(const std::string& command, const std::vector<
     } },
     { "exec-interrupt", [this](const std::vector<std::string> &, std::string &output){
         HRESULT Status;
-        IfFailRet(m_sharedDebugger->Pause());
+        IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
         output = "^done";
         return S_OK;
     } },
index 277a9b82f7dc1de8d728126e3179a4ab3f926f36..4b8aa5b5566bd7be3588b1c071df268647997b0e 100644 (file)
@@ -635,8 +635,9 @@ static HRESULT HandleCommand(std::shared_ptr<IDebugger> &sharedDebugger, std::st
         return sharedDebugger->Continue(threadId);
     } },
     { "pause", [&](const json &arguments, json &body){
-        // Ignore `threadId` argument, since only pause for all threads are supported now.
-        return sharedDebugger->Pause();
+        ThreadId threadId{int(arguments.at("threadId"))};
+        body["threadId"] = int(threadId);
+        return sharedDebugger->Pause(threadId);
     } },
     { "next", [&](const json &arguments, json &body){
         return sharedDebugger->StepCommand(ThreadId{int(arguments.at("threadId"))}, IDebugger::StepType::STEP_OVER);