Add native code execution stop for all managed threads.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 24 May 2023 10:16:54 +0000 (13:16 +0300)
committerGleb Balykov/Advanced System SW Lab /SRR/Staff Engineer/Samsung Electronics <g.balykov@samsung.com>
Wed, 13 Sep 2023 20:48:48 +0000 (05:48 +0900)
Other changes:
- Add events for native thread created and thread exited.
- Fix single step at step over breakpoint in order to prevent signals lost.
- Add `wait` wrapper (same idea as we provide wrapper for `waitpid`).
- Fix CLI protocol attach/interrupt commands output.
- Prevent eval code execution on stopped threads by interop debugger.
- Fix circular references in code.
- Add native threads support in `info threads` CLI command.
- Add raise(), kill() and sigsend() support in user code for SIGTRAP and SIGILL signals.
- Add `__builtin_debugtrap()` and `__builtin_trap()` support in user code.
- Fix hang at exit in case `ManagedCallback::ExitProcess()` never called (rarely happens in case debuggee was terminated by signal).
- Add CLI command `bt all`.
- Add native code execution stop for user-related native threads.

35 files changed:
docs/cli.md
src/debugger/breakpoint_interop_rendezvous.cpp
src/debugger/breakpoints.cpp
src/debugger/breakpoints.h
src/debugger/breakpoints_interop.cpp
src/debugger/breakpoints_interop.h
src/debugger/breakpoints_interop_line.cpp
src/debugger/callbacksqueue.cpp
src/debugger/callbacksqueue.h
src/debugger/evalstackmachine.cpp
src/debugger/evalstackmachine.h
src/debugger/evalwaiter.cpp
src/debugger/evalwaiter.h
src/debugger/interop_brk_helpers.cpp
src/debugger/interop_brk_helpers.h
src/debugger/interop_debugging.cpp
src/debugger/interop_debugging.h
src/debugger/interop_mem_helpers.cpp
src/debugger/interop_unwind.cpp
src/debugger/managedcallback.cpp
src/debugger/managedcallback.h
src/debugger/manageddebugger.cpp
src/debugger/manageddebugger.h
src/debugger/threads.cpp
src/debugger/threads.h
src/debugger/waitpid.cpp
src/interfaces/idebugger.h
src/interfaces/types.h
src/main.cpp
src/metadata/interop_libraries.cpp
src/metadata/interop_libraries.h
src/metadata/modules_app_update.cpp
src/protocols/cliprotocol.cpp
src/protocols/miprotocol.cpp
src/protocols/vscodeprotocol.cpp

index 59145be2bc276fb1aa1744525197e66aaadb63ee..2833aa441f0b7516b38d6fffd4fb417f2e7c0da6 100644 (file)
@@ -13,11 +13,11 @@ Note: to debug Release build of dlls with pdbs Just-My-Code should be disabled,
 ```
 command    alias  args   
 --------------------------
-backtrace  bt              Print backtrace info.
+backtrace  bt     [all]    Print backtrace info.
 break      b      <loc>    Set breakpoint at specified location, where the
-                           location might be filename.cs:line or function name.
+                           location might be source_file_name:line or function name.
                            Optional, module name also could be provided as part
-                           of location: module.dll!filename.cs:line
+                           of location: module.dll!source_file_name:line
 catch                      Set exception breakpoints.
 continue   c               Continue debugging after stop/pause.
 delete     clear  <num>    Delete breakpoint with specified number.
index 10d415a6e9361865b5951f156a7e8cb51e870921..d053a055ab0a34d29accc7d3f2ab6ab1d6246f93 100644 (file)
@@ -102,7 +102,10 @@ bool InteropRendezvousBreakpoint::IsRendezvousBreakpoint(std::uintptr_t brkAddr)
 // Must be called only in case all threads stopped and fixed (see InteropDebugger::StopAndDetach()).\r
 void InteropRendezvousBreakpoint::RemoveAtDetach(pid_t pid)\r
 {\r
-    m_sharedInteropBreakpoints->Remove(pid, m_brkAddr, [](){}, [](std::uintptr_t){});\r
+    if (pid != 0)\r
+    {\r
+        m_sharedInteropBreakpoints->Remove(pid, m_brkAddr, [](){}, [](std::uintptr_t){});\r
+    }\r
 \r
     m_rendezvousAddr = 0;\r
     m_rendezvousBrkState = 0;\r
index acc09fb5309f5c459f7f46a9d2b446374d658de7..fa58f390628eb3af5802e8cdbbcc1277358c1567 100644 (file)
@@ -328,9 +328,9 @@ bool Breakpoints::InteropStepPrevToBrk(pid_t pid, std::uintptr_t brkAddr)
     return m_sharedInteropBreakpoints->StepPrevToBrk(pid, brkAddr);
 }
 
-void Breakpoints::InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr)
+void Breakpoints::InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)
 {
-    m_sharedInteropBreakpoints->StepOverBrk(pid, brkAddr);
+    m_sharedInteropBreakpoints->StepOverBrk(pid, brkAddr, SingleStepOnBrk);
 }
 
 // Must be called only in case all threads stopped and fixed (see InteropDebugger::StopAndDetach()).
index 07371209f01a58008b50dd36e5aa70a550cfa4ca..fa4669f0e744ec892ee0d4217cb5d68fa8a4a14c 100644 (file)
@@ -101,7 +101,7 @@ public:
     // Note, this method will reset PC in case thread stop at breakpoint and alter `regs`.
     bool InteropStepPrevToBrk(pid_t pid, std::uintptr_t brkAddr);
     // Execute real breakpoint's code with single step.
-    void InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr);
+    void InteropStepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);
     // Remove all native breakpoints at interop detach.
     void InteropRemoveAllAtDetach(pid_t pid);
     // Resolve breakpoints for module.
index 6a4b61df65b8e5fd8ed7c1af1a595b09b3a6c9e0..ec8f17d7bf90d3e125e775ebe4b667fc495e2595 100644 (file)
@@ -127,7 +127,7 @@ bool InteropBreakpoints::IsBreakpoint(std::uintptr_t brkAddr)
     return m_currentBreakpointsInMemory.find(brkAddr) != m_currentBreakpointsInMemory.end();\r
 }\r
 \r
-void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr)\r
+void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)\r
 {\r
     std::lock_guard<std::recursive_mutex> lock(m_breakpointsMutex);\r
 \r
@@ -135,7 +135,7 @@ void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr)
     if (find == m_currentBreakpointsInMemory.end())\r
         return;\r
 \r
-    if (!InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData))\r
+    if (!InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData, SingleStepOnBrk))\r
         std::abort(); // Fatal error, we already logged all data about this error.\r
 }\r
 \r
index 49fc846982b1c64e6d1ba6ad768a77b58bf8ca71..713dfa22c69c6fb660a6c086ac8deed1943ef4fe 100644 (file)
@@ -28,7 +28,7 @@ public:
     // Remove all native breakpoints at interop detach.\r
     void RemoveAllAtDetach(pid_t pid);\r
     bool IsBreakpoint(std::uintptr_t brkAddr);\r
-    void StepOverBrk(pid_t pid, std::uintptr_t brkAddr);\r
+    void StepOverBrk(pid_t pid, std::uintptr_t brkAddr, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);\r
     // Return `false` in case no breakpoint with this PC was found (step is not possible).\r
     bool StepPrevToBrk(pid_t pid, std::uintptr_t brkAddr);\r
     // Remove all related to unloaded library breakpoints entries in data structures.\r
index 1ee143f1349a59b9c844980822f1310daadb0ad6..1a129ab01f50e73de98343295d1682d87bfb7900 100644 (file)
@@ -30,12 +30,15 @@ void InteropLineBreakpoints::RemoveAllAtDetach(pid_t pid)
 {\r
     m_breakpointsMutex.lock();\r
 \r
-    for (const auto &entry : m_lineResolvedBreakpoints)\r
+    if (pid != 0)\r
     {\r
-        for (const auto &bp : entry.second)\r
+        for (const auto &entry : m_lineResolvedBreakpoints)\r
         {\r
-            if (bp.m_enabled)\r
-                m_sharedInteropBreakpoints->Remove(pid, entry.first, [](){}, [](std::uintptr_t){});\r
+            for (const auto &bp : entry.second)\r
+            {\r
+                if (bp.m_enabled)\r
+                    m_sharedInteropBreakpoints->Remove(pid, entry.first, [](){}, [](std::uintptr_t){});\r
+            }\r
         }\r
     }\r
     m_lineResolvedBreakpoints.clear();\r
index fa9424ab71dd09da49d2e74e050618a9e60ce5a9..8c9247dc153f128b017cf3f9fcf112a8986d797d 100644 (file)
@@ -48,8 +48,12 @@ bool CallbacksQueue::CallbacksWorkerBreakpoint(ICorDebugAppDomain *pAppDomain, I
     if (SUCCEEDED(pThread->GetActiveFrame(&pFrame)) && pFrame != nullptr)
         m_debugger.GetFrameLocation(pFrame, threadId, FrameLevel(0), event.frame);
 
+#ifdef INTEROP_DEBUGGING
+    StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
     m_debugger.SetLastStoppedThread(pThread);
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+    m_debugger.pProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
 }
@@ -71,8 +75,12 @@ bool CallbacksQueue::CallbacksWorkerStepComplete(ICorDebugAppDomain *pAppDomain,
     StoppedEvent event(StopStep, threadId);
     event.frame = stackFrame;
 
+#ifdef INTEROP_DEBUGGING
+    StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
     m_debugger.SetLastStoppedThread(pThread);
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+    m_debugger.pProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
 }
@@ -96,9 +104,13 @@ bool CallbacksQueue::CallbacksWorkerBreak(ICorDebugAppDomain *pAppDomain, ICorDe
     if (SUCCEEDED(pThread->GetActiveFrame(&iCorFrame)) && iCorFrame != nullptr)
         m_debugger.GetFrameLocation(iCorFrame, threadId, FrameLevel(0), stackFrame);
 
+#ifdef INTEROP_DEBUGGING
+    StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
+
     StoppedEvent event(StopPause, threadId);
     event.frame = stackFrame;
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+    m_debugger.pProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
 }
@@ -121,9 +133,12 @@ bool CallbacksQueue::CallbacksWorkerException(ICorDebugAppDomain *pAppDomain, IC
     // Disable all steppers if we stop during step.
     m_debugger.m_uniqueSteppers->DisableAllSteppers(pAppDomain);
 
-    m_debugger.SetLastStoppedThread(pThread);
+#ifdef INTEROP_DEBUGGING
+    StopAllNativeThreads();
+#endif // INTEROP_DEBUGGING
 
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+    m_debugger.SetLastStoppedThread(pThread);
+    m_debugger.pProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
 }
@@ -170,6 +185,9 @@ void CallbacksQueue::CallbacksWorker()
         case CallbackQueueCall::InteropBreakpoint:
             m_stopEventInProcess = CallbacksWorkerInteropBreakpoint(c.pid, c.addr);
             break;
+        case CallbackQueueCall::InteropSignal:
+            m_stopEventInProcess = CallbacksWorkerInteropSignal(c.pid, c.addr, c.signal);
+            break;
 #endif // INTEROP_DEBUGGING
         default:
             // finish loop
@@ -187,7 +205,7 @@ void CallbacksQueue::CallbacksWorker()
         {
 #ifdef INTEROP_DEBUGGING
             if (m_debugger.m_interopDebugging)
-                m_debugger.m_uniqueInteropDebugger->ContinueAllThreadsWithEvents();
+                m_debugger.m_sharedInteropDebugger->ContinueAllThreadsWithEvents();
 
             if (iCorAppDomain) // last stop event was managed
             {
@@ -309,7 +327,7 @@ HRESULT CallbacksQueue::Continue(ICorDebugProcess *pProcess)
     {
 #ifdef INTEROP_DEBUGGING
         if (m_debugger.m_interopDebugging)
-            m_debugger.m_uniqueInteropDebugger->ContinueAllThreadsWithEvents();
+            m_debugger.m_sharedInteropDebugger->ContinueAllThreadsWithEvents();
 #endif // INTEROP_DEBUGGING
 
         return pProcess->Continue(0);
@@ -319,7 +337,7 @@ HRESULT CallbacksQueue::Continue(ICorDebugProcess *pProcess)
     return S_OK;
 }
 
-// Caller should care about m_callbacksMutex.
+// NOTE caller must 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)
@@ -342,7 +360,7 @@ HRESULT CallbacksQueue::Stop(ICorDebugProcess *pProcess)
 }
 
 // Stop process and set last stopped thread. If `lastStoppedThread` not passed value from protocol, find best thread.
-HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread)
+HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread, EventFormat eventFormat)
 {
     // Must be real thread ID or ThreadId::AllThreads.
     if (!lastStoppedThread)
@@ -356,6 +374,11 @@ HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedTh
     if (Status == S_FALSE) // Already stopped.
         return S_OK;
 
+#ifdef INTEROP_DEBUGGING
+    if (m_debugger.m_interopDebugging)
+        IfFailRet(m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(pProcess));
+#endif // INTEROP_DEBUGGING
+
     // Same logic as provide vsdbg in case of pause during stepping.
     m_debugger.m_uniqueSteppers->DisableAllSteppers(pProcess);
 
@@ -372,14 +395,30 @@ HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedTh
         {
             // 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.pProtocol->EmitStoppedEvent(StoppedEvent(StopPause, lastStoppedThread));
+            m_debugger.m_ioredirect.async_cancel();
+            return S_OK;
+        }
+    }
+    else if (eventFormat == EventFormat::CLI)
+    {
+        // CLI protocol provide ThreadId::AllThreads as lastStoppedThread, stop at main thread with real top frame in event.
+        m_debugger.SetLastStoppedThreadId(threads[0].id);
+
+        int totalFrames = 0;
+        std::vector<StackFrame> stackFrames;
+        if (SUCCEEDED(m_debugger.GetStackTrace(threads[0].id, FrameLevel(0), 1, stackFrames, totalFrames)))
+        {
+            StoppedEvent event(StopPause, threads[0].id);
+            event.frame = stackFrames[0];
+            m_debugger.pProtocol->EmitStoppedEvent(event);
             m_debugger.m_ioredirect.async_cancel();
             return S_OK;
         }
     }
     else
     {
-        // MI and CLI protocols provide ThreadId::AllThreads as lastStoppedThread, this protocols require thread and frame with user code.
+        // MI protocol 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();
@@ -411,7 +450,7 @@ HRESULT CallbacksQueue::Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedTh
                 StoppedEvent event(StopPause, thread.id);
                 event.frame = stackFrame;
                 m_debugger.SetLastStoppedThreadId(thread.id);
-                m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+                m_debugger.pProtocol->EmitStoppedEvent(event);
                 m_debugger.m_ioredirect.async_cancel();
                 return S_OK;
             }
@@ -462,13 +501,40 @@ bool CallbacksQueue::CallbacksWorkerInteropBreakpoint(pid_t pid, std::uintptr_t
 
     m_debugger.SetLastStoppedThreadId(ThreadId(pid));
 
-    if (FAILED(m_debugger.m_uniqueInteropDebugger->GetFrameForAddr(brkAddr, event.frame)))
+    if (FAILED(m_debugger.m_sharedInteropDebugger->GetFrameForAddr(brkAddr, event.frame)))
     {
         event.frame.source = event.breakpoint.source;
         event.frame.line = event.breakpoint.line;
     }
 
-    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
+    m_debugger.pProtocol->EmitStoppedEvent(event);
+    m_debugger.m_ioredirect.async_cancel();
+    return true;
+}
+
+bool CallbacksQueue::CallbacksWorkerInteropSignal(pid_t pid, std::uintptr_t breakAddr, const std::string &signal)
+{
+    ThreadId threadId(pid);
+    StoppedEvent event(StopPause, threadId);
+
+    // Disable all steppers if we stop at breakpoint during step.
+    m_debugger.m_debugProcessRWLock.reader.lock();
+    if (m_debugger.m_iCorProcess)
+    {
+        m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);
+    }
+    m_debugger.m_debugProcessRWLock.reader.unlock();
+
+    m_debugger.SetLastStoppedThreadId(ThreadId(pid));
+
+    if (FAILED(m_debugger.m_sharedInteropDebugger->GetFrameForAddr(breakAddr, event.frame)))
+    {
+        event.frame.source = event.breakpoint.source;
+        event.frame.line = event.breakpoint.line;
+    }
+
+    event.signal_name = signal;
+    m_debugger.pProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
 }
@@ -477,34 +543,49 @@ HRESULT CallbacksQueue::AddInteropCallbackToQueue(std::function<void()> callback
 {
     std::unique_lock<std::mutex> lock(m_callbacksMutex);
 
-    callback(); // Caller could add entries into m_callbacksQueue (this is why m_callbacksMutex cover this call).
+    callback(); // Caller should add entries into m_callbacksQueue (this is why m_callbacksMutex cover this call).
+    assert(!m_callbacksQueue.empty());
 
-    if (!m_callbacksQueue.empty())
+    // NOTE
+    // In case `m_stopEventInProcess` is `true`, process have "stopped" status for sure, but could already execute some eval (this is OK, do not stop managed part!).
+    // No need to check `IsEvalRunning()` here, since this code covered by `m_callbacksMutex` (that mean, breakpoint condition check with eval not running now for sure).
+    if (!m_stopEventInProcess)
     {
-        // NOTE
-        // In case `m_stopEventInProcess` is `true`, process have "stopped" status for sure, but could already execute some eval (this is OK, do not stop managed part!).
-        // No need to check `IsEvalRunning()` here, since this code covered by `m_callbacksMutex` (that mean, breakpoint condition check with eval not running now for sure).
-        if (!m_stopEventInProcess)
+        BOOL procRunning = FALSE;
+        m_debugger.m_debugProcessRWLock.reader.lock();
+        if (m_debugger.m_iCorProcess)
         {
-            BOOL procRunning = FALSE;
-            m_debugger.m_debugProcessRWLock.reader.lock();
-            if (m_debugger.m_iCorProcess && SUCCEEDED(m_debugger.m_iCorProcess->IsRunning(&procRunning)) && procRunning == TRUE)
-            {
+            if (SUCCEEDED(m_debugger.m_iCorProcess->IsRunning(&procRunning)) && procRunning == TRUE)
                 m_debugger.m_iCorProcess->Stop(0);
-            }
-            m_debugger.m_debugProcessRWLock.reader.unlock();
-        }
 
-        m_callbacksCV.notify_one(); // notify_one with lock
+            // Early stop of native code at native event, in case this will be not stop event - we will silently continue native code execution.
+            m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(m_debugger.m_iCorProcess);
+        }
+        m_debugger.m_debugProcessRWLock.reader.unlock();
     }
 
+    m_callbacksCV.notify_one(); // notify_one with lock
     return S_OK;
 }
 
 // NOTE caller must care about m_callbacksMutex.
-void CallbacksQueue::EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr)
+void CallbacksQueue::EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr, const std::string &signal)
+{
+    m_callbacksQueue.emplace_back(Call, pid, addr, signal);
+}
+
+// NOTE caller must care about m_callbacksMutex.
+void CallbacksQueue::StopAllNativeThreads()
 {
-    m_callbacksQueue.emplace_back(Call, pid, addr);
+    if (!m_debugger.m_interopDebugging || m_stopEventInProcess)
+        return;
+
+    m_debugger.m_debugProcessRWLock.reader.lock();
+    if (m_debugger.m_iCorProcess)
+    {
+        m_debugger.m_sharedInteropDebugger->StopAllNativeThreads(m_debugger.m_iCorProcess);
+    }
+    m_debugger.m_debugProcessRWLock.reader.unlock();
 }
 #endif // INTEROP_DEBUGGING
 
index 94f51959254413d2ca3a0fa3f0bde5d408c08cf8..f9cd667afd68fef4fbf3ef1ac9d63bbd8f8df372 100644 (file)
@@ -32,6 +32,7 @@ enum class CallbackQueueCall
     CreateProcess
 #ifdef INTEROP_DEBUGGING
     , InteropBreakpoint
+    , InteropSignal
 #endif // INTEROP_DEBUGGING
 };
 
@@ -39,7 +40,7 @@ class CallbacksQueue
 {
 public:
 
-    CallbacksQueue(ManagedDebugger &debugger) :
+    CallbacksQueue(ManagedDebuggerHelpers &debugger) :
         m_debugger(debugger), m_stopEventInProcess(false), m_callbacksWorker{&CallbacksQueue::CallbacksWorker, this} {}
     ~CallbacksQueue();
 
@@ -47,7 +48,7 @@ public:
     bool IsRunning();
     HRESULT Continue(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);
+    HRESULT Pause(ICorDebugProcess *pProcess, ThreadId lastStoppedThread, EventFormat eventFormat);
     // Analog of "pProcess->Stop(0)" call that also care about callbacks.
     HRESULT Stop(ICorDebugProcess *pProcess);
 
@@ -58,12 +59,12 @@ public:
                      CorDebugStepReason Reason, ExceptionCallbackType EventType, const std::string &ExcModule = std::string{});
 #ifdef INTEROP_DEBUGGING
     HRESULT AddInteropCallbackToQueue(std::function<void()> callback);
-    void EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr);
+    void EmplaceBackInterop(CallbackQueueCall Call, pid_t pid, std::uintptr_t addr, const std::string &signal);
 #endif // INTEROP_DEBUGGING
 
 private:
 
-    ManagedDebugger &m_debugger;
+    ManagedDebuggerHelpers &m_debugger;
 
     // NOTE we have one entry type for both (managed and interop) callbacks (stop events),
     //      since almost all the time we have CallbackQueue with 1 entry only, no reason complicate code.
@@ -98,13 +99,16 @@ private:
 #ifdef INTEROP_DEBUGGING
         pid_t pid = 0; // Initial value in order to suppress static analyzer warnings.
         std::uintptr_t addr = 0; // Initial value in order to suppress static analyzer warnings.
+        std::string signal;
 
         CallbackQueueEntry(CallbackQueueCall call,
                            pid_t pid_,
-                           std::uintptr_t addr_) :
+                           std::uintptr_t addr_,
+                           const std::string &signal_) :
             Call(call),
             pid(pid_),
-            addr(addr_)
+            addr(addr_),
+            signal(signal_)
         {}
 #endif // INTEROP_DEBUGGING
     };
@@ -125,6 +129,8 @@ private:
 
 #ifdef INTEROP_DEBUGGING
     bool CallbacksWorkerInteropBreakpoint(pid_t pid, std::uintptr_t brkAddr);
+    bool CallbacksWorkerInteropSignal(pid_t pid, std::uintptr_t breakAddr, const std::string &signal);
+    void StopAllNativeThreads();
 #endif // INTEROP_DEBUGGING
 
 };
index 1c4e4455a3800b68a9701b4b076b53db4f145e9f..7f2ce00904f28346048baf10763392917cdbb55c 100644 (file)
@@ -2039,6 +2039,8 @@ HRESULT EvalStackMachine::Run(ICorDebugThread *pThread, FrameLevel frameLevel, i
         case E_UNEXPECTED:
             output = "Evaluation timed out, but function evaluation can't be completed or aborted. Debuggee have inconsistent state now.";
             break;
+        case COR_E_THREADSTATE:
+            output = "Thread is in an invalid state for this operation.";
         default:
             break;
     }
index 30b5d20ae8d53c517fe896846ee2f2c687aa2981..6478dd2e5052884fdb6ca10d3a64c6c4b886a5c8 100644 (file)
@@ -108,6 +108,16 @@ public:
         m_evalData.pEvalWaiter = m_sharedEvalWaiter.get();\r
     }\r
 \r
+    void ResetEval()\r
+    {\r
+        m_sharedEvaluator.reset();\r
+        m_sharedEvalHelpers.reset();\r
+        m_sharedEvalWaiter.reset();\r
+        m_evalData.pEvaluator = nullptr;\r
+        m_evalData.pEvalHelpers = nullptr;\r
+        m_evalData.pEvalWaiter = nullptr;\r
+    }\r
+\r
     // Evaluate expression. Optional, return `editable` state and in case result is property - setter related information.\r
     HRESULT EvaluateExpression(ICorDebugThread *pThread, FrameLevel frameLevel, int evalFlags, const std::string &expression, ICorDebugValue **ppResultValue,\r
                                std::string &output, bool *editable = nullptr, std::unique_ptr<Evaluator::SetterData> *resultSetterData = nullptr);\r
index 7a04fcdb72d5d8381605980df32d2357fc697b55..8cc7ffaa387d78a9ca6298374af026c3d68bf82f 100644 (file)
@@ -4,6 +4,10 @@
 \r
 #include "debugger/evalwaiter.h"\r
 #include "utils/platform.h"\r
+#include "debugger/threads.h"\r
+#ifdef INTEROP_DEBUGGING\r
+#include "debugger/interop_debugging.h"\r
+#endif // INTEROP_DEBUGGING\r
 \r
 namespace netcoredbg\r
 {\r
@@ -49,6 +53,16 @@ DWORD EvalWaiter::GetEvalRunningThreadID()
 \r
     return !!m_evalResult ? m_evalResult->threadId : 0;\r
 }\r
+\r
+void EvalWaiter::SetInteropDebugger(std::shared_ptr<InteropDebugging::InteropDebugger> &sharedInteropDebugger)\r
+{\r
+    m_sharedInteropDebugger = sharedInteropDebugger;\r
+}\r
+\r
+void EvalWaiter::ResetInteropDebugger()\r
+{\r
+    m_sharedInteropDebugger.reset();\r
+}\r
 #endif // INTEROP_DEBUGGING\r
 \r
 void EvalWaiter::CancelEvalRunning()\r
@@ -131,6 +145,12 @@ HRESULT EvalWaiter::WaitEvalResult(ICorDebugThread *pThread,
     DWORD evalThreadId = 0;\r
     IfFailRet(pThread->GetID(&evalThreadId));\r
 \r
+#ifdef INTEROP_DEBUGGING\r
+    assert(!!m_sharedInteropDebugger);\r
+    if (m_sharedInteropDebugger->IsNativeThreadStopped((pid_t)evalThreadId))\r
+        return COR_E_THREADSTATE;\r
+#endif // INTEROP_DEBUGGING\r
+\r
     // Note, we need suspend during eval all managed threads, that not used for eval (delegates, reverse pinvokes, managed threads).\r
     auto ChangeThreadsState = [&](CorDebugThreadState state)\r
     {\r
index 2824519fec350379c33423f6fd79e1b87a64e5a5..7ec35bdb5ccf833d258036885cc449acae9e6379 100644 (file)
 namespace netcoredbg\r
 {\r
 \r
+class Threads;\r
+#ifdef INTEROP_DEBUGGING\r
+namespace InteropDebugging\r
+{\r
+class InteropDebugger;\r
+}\r
+#endif // INTEROP_DEBUGGING\r
+\r
+\r
 class EvalWaiter\r
 {\r
 public:\r
@@ -25,6 +34,8 @@ public:
     bool IsEvalRunning();\r
 #ifdef INTEROP_DEBUGGING\r
     DWORD GetEvalRunningThreadID();\r
+    void SetInteropDebugger(std::shared_ptr<InteropDebugging::InteropDebugger> &sharedInteropDebugger);\r
+    void ResetInteropDebugger();\r
 #endif // INTEROP_DEBUGGING\r
     void CancelEvalRunning();\r
     ICorDebugEval *FindEvalForThread(ICorDebugThread *pThread);\r
@@ -46,6 +57,10 @@ private:
     ToRelease<ICorDebugClass> m_iCorCrossThreadDependencyNotification;\r
     HRESULT SetEnableCustomNotification(ICorDebugProcess *pProcess, BOOL fEnable);\r
 \r
+#ifdef INTEROP_DEBUGGING\r
+    std::shared_ptr<InteropDebugging::InteropDebugger> m_sharedInteropDebugger;\r
+#endif // INTEROP_DEBUGGING\r
+\r
     struct evalResultData_t\r
     {\r
         ToRelease<ICorDebugValue> iCorEval;\r
index 5de940dcb0ee7e0b4f47d76b9a657db3db157083..7d40e4d0999dabfe092ccc0982d3071402db2e7c 100644 (file)
@@ -67,6 +67,23 @@ std::uintptr_t GetBrkAddrByPC(const user_regs_struct &regs)
 #endif
 }
 
+// Return break address by current PC.
+std::uintptr_t GetBreakAddrByPC(const user_regs_struct &regs)
+{
+#if DEBUGGER_UNIX_AMD64
+    return std::uintptr_t(regs.rip);
+#elif DEBUGGER_UNIX_X86
+    return std::uintptr_t(regs.eip);
+#elif DEBUGGER_UNIX_ARM64
+    return std::uintptr_t(regs.pc);
+#elif DEBUGGER_UNIX_ARM
+    const static int REG_PC = 15;
+    return std::uintptr_t(regs.uregs[REG_PC]);
+#else
+#error "Unsupported platform"
+#endif
+}
+
 #if DEBUGGER_UNIX_ARM
 static bool IsThumbOpcode32Bits(word_t data)
 {
@@ -124,7 +141,7 @@ word_t RestoredOpcode(word_t dataWithBrk, word_t restoreData)
 #endif
 }
 
-bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
+bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk)
 {
     // We have 2 cases here (at breakpoint stop):
     //   * x86/amd64 already changed PC (executed 0xCC code), so, SetPrevBrkPC() call will change PC in our stored registers
@@ -169,25 +186,8 @@ bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
         return false;
     }
 
-    // single step
-    if (async_ptrace(PTRACE_SINGLESTEP, pid, nullptr, nullptr) == -1)
-    {
-        LOGE("Ptrace singlestep error: %s\n", strerror(errno));
-        return false;
-    }
-
-    int wait_status;
-    if (GetWaitpid()(pid, &wait_status, 0) == -1)
-    {
-        LOGE("Waitpid error: %s\n", strerror(errno));
+    if (!SingleStepOnBrk(pid, addr))
         return false;
-    }
-
-    if (WSTOPSIG(wait_status) != SIGTRAP)
-    {
-        LOGE("Failed with single step, stop signal=%u", WSTOPSIG(wait_status));
-        return false;
-    }
 
     // setup bp again
     if (async_ptrace(PTRACE_POKEDATA, pid, (void*)addr, (void*)brkData) == -1)
index c144dec68562733491fe41e435c1e61cbe566715..33c8db4aa7814d047217fd7899ab65a8ee08e793 100644 (file)
@@ -6,6 +6,7 @@
 #ifdef INTEROP_DEBUGGING
 
 #include "debugger/interop_ptrace_helpers.h"
+#include <functional>
 
 namespace netcoredbg
 {
@@ -15,9 +16,10 @@ namespace InteropDebugging
     bool NeedSetPrevBrkPC(); // return true if at least one register should be changed
     void SetPrevBrkPC(user_regs_struct &regs);
     std::uintptr_t GetBrkAddrByPC(const user_regs_struct &regs);
+    std::uintptr_t GetBreakAddrByPC(const user_regs_struct &regs);
     word_t EncodeBrkOpcode(word_t data, bool thumbCode);
     word_t RestoredOpcode(word_t dataWithBrk, word_t restoreData);
-    bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData);
+    bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData, std::function<bool(pid_t, std::uintptr_t)> SingleStepOnBrk);
 
 } // namespace InteropDebugging
 } // namespace netcoredbg
index a11f533b9f7d2431eaf03e4b255ff6450c618989..7d5cd83e5dd9672528e3c93a6c6352f54de96636 100644 (file)
@@ -49,38 +49,154 @@ namespace
 } // unnamed namespace
 
 
-InteropDebugger::InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
-                                 std::shared_ptr<Breakpoints> &sharedBreakpoints,
-                                 std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
-    m_sharedProtocol(sharedProtocol),
+InteropDebuggerBase::InteropDebuggerBase(IProtocol *pProtocol_,
+                                         std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                                         std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+    pProtocol(pProtocol_),
     m_sharedBreakpoints(sharedBreakpoints),
     m_uniqueInteropLibraries(new InteropLibraries()),
     m_sharedEvalWaiter(sharedEvalWaiter)
 {}
 
+InteropDebuggerSignals::InteropDebuggerSignals(IProtocol *pProtocol_,
+                                               std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                                               std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+    InteropDebuggerBase(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
+InteropDebuggerHelpers::InteropDebuggerHelpers(IProtocol *pProtocol_,
+                                               std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                                               std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+    InteropDebuggerSignals(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
+InteropDebugger::InteropDebugger(IProtocol *pProtocol_,
+                                 std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                                 std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
+    InteropDebuggerHelpers(pProtocol_, sharedBreakpoints, sharedEvalWaiter)
+{}
+
 // NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::WaitThreadStop(pid_t stoppedPid)
+bool InteropDebuggerBase::SingleStepOnBrk(pid_t pid, std::uintptr_t addr)
 {
-    if (stoppedPid == g_waitForAllThreads)
+    // We may have situation, when we check thread status with internal structure, but thread already stopped at breakpoint
+    // and we send to this thread PTRACE_INTERRUPT (since internal structure was not updated yet at next waitpid() cycle).
+    // In this case, at ptrace(PTRACE_SINGLESTEP) thread will be stopped by PTRACE_INTERRUPT and ptrace(PTRACE_SINGLESTEP)
+    // call must be repeated in oreder to finally make single step.
+
+    // Another case is signal that landed on stopped thread. For example SIGKILL from user or SIGILL
+    // due to wrong instruction that was covered by breakpoint opcode and now executed on single step.
+
+    if (async_ptrace(PTRACE_SINGLESTEP, pid, nullptr, nullptr) == -1)
     {
-        if (std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end())
-            return;
+        LOGE("Ptrace singlestep error: %s\n", strerror(errno));
+        return false;
     }
     else
     {
-        if (m_TIDs[stoppedPid].stat != thread_stat_e::running)
-            return;
+        m_TIDs[pid].stat = thread_stat_e::running;
+        m_TIDs[pid].stop_signal = 0;
     }
 
+    WaitThreadStop(pid);
+
+    // Check that we still have this thread alive.
+    if (m_TIDs.find(pid) == m_TIDs.end())
+        return false;
+
+    if (m_TIDs[pid].stop_signal == SIGTRAP &&
+        m_TIDs[pid].event == 0) // not ptrace event (breakpoint, step)
+    {
+        siginfo_t ptrace_info;
+        if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+        {
+            LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+            return false;
+        }
+
+        switch (ptrace_info.si_code)
+        {
+        case SI_KERNEL:
+        case TRAP_BRKPT:
+            // Care about `__builtin_debugtrap()` in user code.
+            m_TIDs[pid].stat = thread_stat_e::stopped_signal_event_detected;
+            m_TIDs[pid].stop_event_data.signal = "SIGTRAP";
+            m_eventedThreads.emplace_back(pid);
+            return true;
+
+        case TRAP_TRACE: // single step
+            m_TIDs[pid].stop_signal = 0;
+            return true;
+        }
+    }
+    else if (m_TIDs[pid].stop_signal == SIGILL)
+    {
+        siginfo_t ptrace_info;
+        if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+        {
+            LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+            return false;
+        }
+
+        if (ptrace_info.si_code == TRAP_TRACE)
+        {
+            // Care about `__builtin_trap()` in user code.
+            m_TIDs[pid].stat = thread_stat_e::stopped_signal_event_detected;
+            m_TIDs[pid].stop_event_data.signal = "SIGILL";
+            m_eventedThreads.emplace_back(pid);
+            return true;
+        }
+    }
+
+    // Got some signal, that must be handled first.
+    // In this case we don't execute sigle step again, since this could be SIGILL for initial wrong opcode, but restore breakoint in memory.
+    // We will care about this signal and after that will step over breakpoint if this breakpoint happens again on this thread at next code
+    // execution continue (take into account, that breakpoint could be removed before next continue, for example).
+    m_TIDs[pid].addrStepOverBreakpointFailed = addr;
+    return true;
+}
+
+// NOTE caller must care about m_waitpidMutex.
+void InteropDebuggerBase::WaitThreadStop(pid_t stoppedPid, std::vector<pid_t> *stoppedTreads)
+{
+    auto AllRequestedThreadsNotRunning = [&]() -> bool
+    {
+        if (stoppedTreads != nullptr)
+        {
+            if (std::find_if(stoppedTreads->begin(), stoppedTreads->end(), [this](pid_t entry)
+                                                                            {// NOTE some threads could exit, don't create m_TIDs entry by m_TIDs[] request.
+                                                                                auto find = m_TIDs.find(entry);
+                                                                                return find == m_TIDs.end() ? false : find->second.stat == thread_stat_e::running;
+                                                                            }) == stoppedTreads->end())
+                return true;
+        }
+        else if (stoppedPid == g_waitForAllThreads)
+        {
+            if (std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end())
+                return true;
+        }
+        else
+        {
+            // NOTE thread could exit, don't create m_TIDs entry by m_TIDs[] request.
+            auto find = m_TIDs.find(stoppedPid);
+            if (find == m_TIDs.end() || find->second.stat != thread_stat_e::running)
+                return true;
+        }
+        return false;
+    };
+    if (AllRequestedThreadsNotRunning())
+        return;
+
     // At this point all threads must be stopped or interrupted, we need parse all signals now.
     pid_t pid = 0;
     int status = 0;
-    // Note, we ignore errors here and don't check is m_TGID exit or not, since in case m_TGID exited `waitpid` return error and break loop.
+    // Note, we ignore errors here and don't check is m_TGID exit or not, since in case m_TGID exited `AllRequestedThreadsNotRunning()` break loop.
     while ((pid = GetWaitpid()(-1, &status, __WALL)) > 0)
     {
         if (!WIFSTOPPED(status))
         {
             m_TIDs.erase(pid);
+            pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadExited, ThreadId(pid), true));
 
             // Tracee exited or was killed by signal.
             if (pid == m_TGID)
@@ -88,9 +204,10 @@ void InteropDebugger::WaitThreadStop(pid_t stoppedPid)
                 assert(m_TIDs.empty());
                 m_TGID = 0;
                 GetWaitpid().SetPidExitedStatus(pid, status);
+                m_NotifyLastThreadExited(status);
             }
 
-            if (stoppedPid == pid)
+            if (AllRequestedThreadsNotRunning())
                 break;
 
             continue;
@@ -114,22 +231,21 @@ void InteropDebugger::WaitThreadStop(pid_t stoppedPid)
                 stop_signal = 0;
         }
 
+        if (m_TIDs.find(pid) == m_TIDs.end())
+            pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadStarted, ThreadId(pid), true));
+
         m_TIDs[pid].stat = thread_stat_e::stopped; // if we here, this mean we get some stop signal for this thread
         m_TIDs[pid].stop_signal = stop_signal;
         m_TIDs[pid].event = (unsigned)status >> 16;
         m_changedThreads.emplace_back(pid);
 
-        if (stoppedPid == pid ||
-            (stoppedPid == g_waitForAllThreads &&
-             std::find_if(m_TIDs.begin(), m_TIDs.end(), [](std::pair<pid_t, thread_status_t> entry){return entry.second.stat == thread_stat_e::running;}) == m_TIDs.end()))
-        {
+        if (AllRequestedThreadsNotRunning())
             break;
-        }
     }
 }
 
 // NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::StopAndDetach(pid_t tgid)
+void InteropDebuggerHelpers::StopAndDetach(pid_t tgid)
 {
     WaitThreadStop(g_waitForAllThreads);
 
@@ -172,9 +288,9 @@ void InteropDebugger::StopAndDetach(pid_t tgid)
 }
 
 // NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::StopAllRunningThreads()
+static void StopAllRunningThreads(const std::unordered_map<pid_t, thread_status_t> &TIDs)
 {
-    for (const auto &tid : m_TIDs)
+    for (const auto &tid : TIDs)
     {
         if (tid.second.stat == thread_stat_e::running &&
             async_ptrace(PTRACE_INTERRUPT, tid.first, nullptr, nullptr) == -1)
@@ -185,9 +301,9 @@ void InteropDebugger::StopAllRunningThreads()
 }
 
 // NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::Detach(pid_t tgid)
+void InteropDebuggerHelpers::Detach(pid_t tgid)
 {
-    StopAllRunningThreads();
+    StopAllRunningThreads(m_TIDs);
     StopAndDetach(tgid);
 }
 
@@ -202,13 +318,13 @@ void InteropDebugger::Shutdown()
         if (m_waitpidThreadStatus == WaitpidThreadStatus::WORK)
         {
             m_waitpidNeedExit = true;
-            m_waitpidCV.notify_one(); // notify for exit from infinite loop (thread may stay and unlock mutex on wait() or usleep())
             m_waitpidCV.wait(lock); // wait for exit from infinite loop
         }
 
         m_waitpidWorker.join();
         Detach(m_TGID);
         m_TGID = 0;
+        m_NotifyLastThreadExited = std::function<void(int)>{};
         m_sharedCallbacksQueue = nullptr;
         GetWaitpid().SetInteropWaitpidMode(false);
         m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED_AND_JOINED;
@@ -220,7 +336,7 @@ void InteropDebugger::Shutdown()
     async_ptrace_shutdown();
 }
 
-static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, const pid_t pid, int &error_n)
+static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, const pid_t pid, bool attach, int &error_n, IProtocol *pProtocol)
 {
     char dirname[128];
     if (snprintf(dirname, sizeof dirname, "/proc/%d/task/", pid) >= (int)sizeof(dirname))
@@ -264,6 +380,8 @@ static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_stat
             closedir(dir);
             return E_FAIL;
         }
+
+        pProtocol->EmitThreadEvent(ThreadEvent(attach ? NativeThreadAttached : NativeThreadStarted, ThreadId(tid), true));
         TIDs[tid].stat = thread_stat_e::running; // seize - attach without stop
 
         if (async_ptrace(PTRACE_INTERRUPT, tid, nullptr, nullptr) == -1)
@@ -288,10 +406,8 @@ static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_stat
     return S_OK;
 }
 
-void InteropDebugger::LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr)
+void InteropDebuggerHelpers::LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr)
 {
-    // TODO setup related to this lib native breakpoints
-
     Module module;
     module.id = ""; // TODO add "The `id` field is an opaque identifier of the library"
     module.name = GetBasename(realLibName);
@@ -305,19 +421,19 @@ void InteropDebugger::LoadLib(pid_t pid, const std::string &libLoadName, const s
         std::vector<BreakpointEvent> events;
         m_sharedBreakpoints->InteropLoadModule(pid, startAddr, m_uniqueInteropLibraries.get(), events);
         for (const BreakpointEvent &event : events)
-            m_sharedProtocol->EmitBreakpointEvent(event);
+            pProtocol->EmitBreakpointEvent(event);
     }
 
-    m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
+    pProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
 }
 
-void InteropDebugger::UnloadLib(const std::string &realLibName)
+void InteropDebuggerHelpers::UnloadLib(const std::string &realLibName)
 {
     Module module;
     module.id = ""; // TODO add "The `id` field is an opaque identifier of the library"
     module.name = GetBasename(realLibName);
     module.path = realLibName;
-    m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleRemoved, module));
+    pProtocol->EmitModuleEvent(ModuleEvent(ModuleRemoved, module));
     std::uintptr_t startAddr = 0;
     std::uintptr_t endAddr = 0;
     if (m_uniqueInteropLibraries->RemoveLibrary(realLibName, startAddr, endAddr))
@@ -325,108 +441,240 @@ void InteropDebugger::UnloadLib(const std::string &realLibName)
         std::vector<BreakpointEvent> events;
         m_sharedBreakpoints->InteropUnloadModule(startAddr, endAddr, events);
         for (const BreakpointEvent &event : events)
-            m_sharedProtocol->EmitBreakpointEvent(event);
+            pProtocol->EmitBreakpointEvent(event);
     }
 }
 
-// NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::ParseThreadsChanges()
+static bool AddSignalEventForUserCode(pid_t pid, InteropLibraries *pInteropLibraries, const std::string &signal, thread_status_t &threadStatus)
 {
-    if (m_changedThreads.empty())
-        return;
+    // get registers (we need real breakpoint address for check)
+    user_regs_struct regs;
+    iovec iov;
+    iov.iov_base = &regs;
+    iov.iov_len = sizeof(user_regs_struct);
+    if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
+    {
+        LOGW("Ptrace getregset error: %s\n", strerror(errno));
+        return false;
+    }
 
-    for (auto it = m_changedThreads.begin(); it != m_changedThreads.end(); ++it)
+    // Should be user code only with debug info and ignore CoreCLR libs.
+    std::uintptr_t breakAddr = GetBreakAddrByPC(regs);
+    if (!pInteropLibraries->IsUserDebuggingCode(breakAddr))
+        return false;
+
+    // We need stop event and only at "continue" sent this signal to CoreCLR (let CoreCLR decide).
+    threadStatus.stat = thread_stat_e::stopped_signal_event_detected;
+    threadStatus.stop_event_data.addr = breakAddr;
+    threadStatus.stop_event_data.signal = signal;
+    return true;
+}
+
+static bool AddSignalEventForCallerInUserCode(pid_t pid, pid_t TGID, InteropLibraries *pInteropLibraries, const std::string &signal, thread_status_t &threadStatus)
+{
+    // Check, that debuggee send this to itself by PID.
+    siginfo_t siginfo;
+    memset(&siginfo, 0, sizeof(siginfo_t));
+    if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &siginfo) == -1)
     {
-        pid_t &pid = *it;
+        LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+        return false;
+    }
+    if (siginfo.si_pid != TGID)
+        return false;
+
+    // Should be user code only with debug info and ignore CoreCLR libs.
+    // Find second frame - raise()/kill() caller.
+    // NOTE in case we can't get second frame data we can't guarantee that stop event will not broke CoreCLR debug API.
+    int frameCount = 0;
+    bool isUserDebuggingCode = false;
+    std::uintptr_t breakAddr = 0;
+    ThreadStackUnwind(pid, nullptr, [&](std::uintptr_t addr)
+    {
+        frameCount++;
 
-        // TODO (CoreCLR have sigaction for this signals):
-        // SIGSTOP
-        // SIGILL
-        // SIGFPE
-        // SIGSEGV
-        // SIGBUS
-        // SIGABRT
-        // SIGINT - Note, in CLI set to SIG_IGN.
-        // SIGQUIT
-        // SIGTERM
+        // TODO care about case when one thread could send signal to another inside process:
+        // kill(getpid(), SIGTRAP);
+        // and that thread's second frame will be in user code it the same time.
+
+        // (?) check that top frame is user code / not CoreCLR (but skip frames with "system" libs like libc, libpthread, ...)
+
+        // (?) check lib + method name for first frame:
+        // raise(SIGTRAP)                                   -> libpthread-2.31.so` raise()
+        // kill(syscall(SYS_gettid), SIGTRAP)               -> libc-2.31.so` kill()
+        // tgkill(getpid(), syscall(SYS_gettid), SIGTRAP)   -> libc-2.31.so` tgkill()
+        // syscall(SYS_tkill, syscall(SYS_gettid), SIGTRAP) -> libc-2.31.so` syscall()
+
+        if (frameCount == 1)
+            breakAddr = addr;
+        else
+            isUserDebuggingCode = pInteropLibraries->IsUserDebuggingCode(addr);
+
+        return frameCount < 2;
+    });
+    if (!isUserDebuggingCode)
+        return false;
+
+    // We need stop event and only at "continue" sent this signal to CoreCLR (let CoreCLR decide).
+    threadStatus.stat = thread_stat_e::stopped_signal_event_detected;
+    threadStatus.stop_event_data.addr = breakAddr;
+    threadStatus.stop_event_data.signal = signal;
+    return true;
+}
 
-        if (m_TIDs[pid].stop_signal == SIGTRAP)
+void InteropDebuggerSignals::Parse_SIGILL(pid_t pid)
+{
+    siginfo_t ptrace_info;
+    if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+    {
+        LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+    }
+    else
+    {
+        switch (ptrace_info.si_code)
         {
-            switch (m_TIDs[pid].event)
-            {
-            case PTRACE_EVENT_EXEC:
-                if (pid != m_TGID)
-                {
-                    if (async_ptrace(PTRACE_DETACH, pid, nullptr, nullptr) == -1)
-                        LOGW("Ptrace detach at exec error: %s\n", strerror(errno));
-                    else
-                        m_TIDs.erase(pid);
+        case TRAP_TRACE:
+            // Care about `__builtin_trap()` in user code.
+            if (AddSignalEventForUserCode(pid, m_uniqueInteropLibraries.get(), "SIGILL", m_TIDs[pid]))
+                m_eventedThreads.emplace_back(pid);
+            break;
+        case SI_USER:
+        case SI_TKILL:
+            // Care about `raise()` and `kill()` for SIGILL in user code.
+            if (AddSignalEventForCallerInUserCode(pid, m_TGID, m_uniqueInteropLibraries.get(), "SIGILL", m_TIDs[pid]))
+                m_eventedThreads.emplace_back(pid);
+            break;
+        }
+    }
+}
 
-                    continue;
-                }
+void InteropDebuggerSignals::Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid)
+{
+    if (pid == m_TGID)
+    {
+        m_TIDs[pid].stop_signal = 0;
+        return;
+    }
 
-                m_TIDs[pid].stop_signal = 0;
-                break;
-            
-            case 0: // not ptrace-related event
+    if (async_ptrace(PTRACE_DETACH, pid, nullptr, nullptr) == -1)
+        LOGW("Ptrace detach at exec error: %s\n", strerror(errno));
+    else
+        m_TIDs.erase(pid);
+}
+
+void InteropDebuggerSignals::Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid)
+{
+    siginfo_t ptrace_info;
+    if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+    {
+        LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+    }
+    else
+    {
+        switch (ptrace_info.si_code)
+        {
+            case SI_KERNEL:
+            case TRAP_BRKPT:
+            {
+                // get registers (we need real breakpoint address for check)
+                user_regs_struct regs;
+                iovec iov;
+                iov.iov_base = &regs;
+                iov.iov_len = sizeof(user_regs_struct);
+                if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
+                    LOGW("Ptrace getregset error: %s\n", strerror(errno));
+
+                std::uintptr_t brkAddr = GetBrkAddrByPC(regs);
+
+                // Step over breakpoint (that previously was failed, since some signal at single step was received).
+                // Note, breakpoint with this address could be already deleted and we stop at another breakpoint, make sure addrStepOverBreakpointFailed is reset.
+                if (m_TIDs[pid].addrStepOverBreakpointFailed != 0)
                 {
-                    siginfo_t ptrace_info;
-                    if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &ptrace_info) == -1)
+                    // Reset must be before `InteropStepOverBrk()` call, since it could change it again.
+                    std::uintptr_t addrStepOverBreakpointFailed = m_TIDs[pid].addrStepOverBreakpointFailed;
+                    m_TIDs[pid].addrStepOverBreakpointFailed = 0;
+                    if (addrStepOverBreakpointFailed == brkAddr)
                     {
-                        LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+                        StopAllRunningThreads(m_TIDs);
+                        WaitThreadStop(g_waitForAllThreads);
+                        m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+                        break;
                     }
-                    else
+                }
+
+                if (m_sharedBreakpoints->IsInteropRendezvousBreakpoint(brkAddr))
+                {
+                    m_sharedBreakpoints->InteropChangeRendezvousState(m_TGID, pid);
+                    m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+                }
+                else if (m_sharedBreakpoints->IsInteropBreakpoint(brkAddr))
+                {
+                    // Ignore breakpoints during managed evaluation.
+                    if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
                     {
-                        switch (ptrace_info.si_code)
-                        {
-#ifdef SI_KERNEL
-                            case SI_KERNEL:
-#endif
-                            case SI_USER:
-                            case TRAP_BRKPT:
-                            {
-                                // get registers (we need real breakpoint address for check)
-                                user_regs_struct regs;
-                                iovec iov;
-                                iov.iov_base = &regs;
-                                iov.iov_len = sizeof(user_regs_struct);
-                                if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
-                                    LOGW("Ptrace getregset error: %s\n", strerror(errno));
-
-                                std::uintptr_t brkAddr = GetBrkAddrByPC(regs);
-
-                                if (m_sharedBreakpoints->IsInteropRendezvousBreakpoint(brkAddr))
-                                {
-                                    m_sharedBreakpoints->InteropChangeRendezvousState(m_TGID, pid);
-                                    m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
-                                    m_TIDs[pid].stop_signal = 0;
-                                }
-                                else if (m_sharedBreakpoints->IsInteropBreakpoint(brkAddr))
-                                {
-                                    // Ignore breakpoints during managed evaluation.
-                                    if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
-                                    {
-                                        StopAllRunningThreads();
-                                        WaitThreadStop(g_waitForAllThreads);
-                                        m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
-                                        m_TIDs[pid].stop_signal = 0;
-                                        break;
-                                    }
-
-                                    m_TIDs[pid].stop_signal = 0;
-                                    m_TIDs[pid].stat = thread_stat_e::stopped_breakpoint_event_detected;
-                                    m_TIDs[pid].stop_event_data.addr = brkAddr;
-                                    m_eventedThreads.emplace_back(pid);
-                                }
-                                break;
-                            }
-                            case TRAP_TRACE:
-                                // TODO check all native steppers
-                                // m_TIDs[pid].stop_signal = 0;
-                                break;
-                        }
+                        StopAllRunningThreads(m_TIDs);
+                        WaitThreadStop(g_waitForAllThreads);
+                        m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr, [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+                        break;
                     }
+
+                    m_TIDs[pid].stop_signal = 0;
+                    m_TIDs[pid].stat = thread_stat_e::stopped_breakpoint_event_detected;
+                    m_TIDs[pid].stop_event_data.addr = brkAddr;
+                    m_eventedThreads.emplace_back(pid);
                 }
+                else
+                {
+                    // Care about `__builtin_debugtrap()` in user code.
+                    if (AddSignalEventForUserCode(pid, m_uniqueInteropLibraries.get(), "SIGTRAP", m_TIDs[pid]))
+                        m_eventedThreads.emplace_back(pid);
+                }
+
+                break;
+            }
+            case SI_USER:
+            case SI_TKILL:
+                // Care about `raise()` and `kill()` for SIGTRAP in user code.
+                if (AddSignalEventForCallerInUserCode(pid, m_TGID, m_uniqueInteropLibraries.get(), "SIGTRAP", m_TIDs[pid]))
+                    m_eventedThreads.emplace_back(pid);
+                break;
+            case TRAP_TRACE:
+                // TODO check all native steppers
+                // m_TIDs[pid].stop_signal = 0;
+                // Reset m_TIDs[pid].addrStepOverBreakpointFailed
+                break;
+        }
+    }
+}
+
+// NOTE caller must care about m_waitpidMutex.
+void InteropDebuggerBase::ParseThreadsChanges()
+{
+    if (m_changedThreads.empty())
+        return;
+
+    static std::unordered_map<unsigned, std::function<void(pid_t pid)>> signalActions
+    {
+        { 0 , [&](pid_t pid) {
+            // From man 2 kill:
+            //    If sig is 0, then no signal is sent, but existence and permission
+            //    checks are still performed; this can be used to check for the
+            //    existence of a process ID or process group ID that the caller is
+            //    permitted to signal.
+            // Note, we  also use it as previous signal "reset" (signal, that was parsed by debugger and should not be send to debuggee).
+        } },
+        { SIGILL , [&](pid_t pid) {
+            Parse_SIGILL(pid);
+        } },
+        { SIGTRAP , [&](pid_t pid) {
+            switch (m_TIDs[pid].event)
+            {
+            case PTRACE_EVENT_EXEC:
+                Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid);
+                break;
+            
+            case 0: // not ptrace-related event
+                Parse_SIGTRAP__NOT_PTRACE_EVENT(pid);
                 break;
 
             //case PTRACE_EVENT_FORK:
@@ -440,13 +688,38 @@ void InteropDebugger::ParseThreadsChanges()
                 m_TIDs[pid].stop_signal = 0;
                 break;
             }
+        } }
+
+        // TODO (CoreCLR have sigaction for this signals):
+        // SIGSTOP
+        // SIGFPE
+        // SIGSEGV
+        // SIGBUS
+        // SIGABRT
+        // SIGINT - Note, in CLI set to SIG_IGN.
+        // SIGQUIT
+        // SIGTERM
+    };
+
+    for (auto it = m_changedThreads.begin(); it != m_changedThreads.end(); ++it)
+    {
+        pid_t &pid = *it;
+
+        if (m_TIDs[pid].stat != thread_stat_e::stopped)
+            continue;
+
+        auto find = signalActions.find(m_TIDs[pid].stop_signal);
+        if (find != signalActions.end())
+        {
+            find->second(pid);
         }
     }
 
     // NOTE we use second cycle, since during first (parsing) we may need stop all running threads (for example, in case user breakpoint during eval).
     for (const auto &pid : m_changedThreads)
     {
-        if (m_TIDs[pid].stat != thread_stat_e::stopped)
+        if (m_TIDs[pid].stat != thread_stat_e::stopped &&
+            m_TIDs[pid].stat != thread_stat_e::stopped_on_event_need_continue)
             continue;
 
         if (async_ptrace(PTRACE_CONT, pid, nullptr, (void*)((word_t)m_TIDs[pid].stop_signal)) == -1)
@@ -462,7 +735,7 @@ void InteropDebugger::ParseThreadsChanges()
 }
 
 // Separate thread for callbacks setup in order to make waitpid and CoreCLR debug API work in the same time.
-void InteropDebugger::CallbackEventWorker()
+void InteropDebuggerBase::CallbackEventWorker()
 {
     std::unique_lock<std::mutex> lock(m_callbackEventMutex);
     m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that thread init complete
@@ -474,6 +747,7 @@ void InteropDebugger::CallbackEventWorker()
         if (m_callbackEventNeedExit)
             break;
 
+        // m_sharedCallbacksQueue's wrapper that care about m_callbacksMutex lock before code execution in lambda.
         m_sharedCallbacksQueue->AddInteropCallbackToQueue([&]()
         {
             for (const auto &entry : m_callbackEvents)
@@ -481,15 +755,27 @@ void InteropDebugger::CallbackEventWorker()
                 switch (entry.stat)
                 {
                 case thread_stat_e::stopped_breakpoint_event_detected:
-                    m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, entry.pid, entry.stop_event_data.addr);
+                    m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, entry.pid, entry.stop_event_data.addr, "");
                     {
+                        // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+                        // Important! Lock sequence must be (1)m_callbacksMutex -> (2)m_waitpidMutex only!
                         std::lock_guard<std::mutex> lock(m_waitpidMutex);
                         auto find = m_TIDs.find(entry.pid);
                         if (find != m_TIDs.end())
                            find->second.stat = thread_stat_e::stopped_breakpoint_event_in_progress;
                     }
                     break;
-
+                case thread_stat_e::stopped_signal_event_detected:
+                    m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropSignal, entry.pid, entry.stop_event_data.addr, entry.stop_event_data.signal);
+                    {
+                        // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+                        // Important! Lock sequence must be (1)m_callbacksMutex -> (2)m_waitpidMutex only!
+                        std::lock_guard<std::mutex> lock(m_waitpidMutex);
+                        auto find = m_TIDs.find(entry.pid);
+                        if (find != m_TIDs.end())
+                           find->second.stat = thread_stat_e::stopped_signal_event_in_progress;
+                    }
+                    break;
                 default:
                     LOGW("This event type is not stop event: %d", entry.stat);
                     break;
@@ -503,10 +789,9 @@ void InteropDebugger::CallbackEventWorker()
     m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that execution exit from CallbackEventWorker()
 }
 
-void InteropDebugger::ParseThreadsEvents()
+// Caller must care about m_waitpidMutex.
+void InteropDebuggerBase::ParseThreadsEvents()
 {
-    std::lock_guard<std::mutex> lock(m_waitpidMutex);
-
     if (m_eventedThreads.empty())
         return;
 
@@ -514,6 +799,7 @@ void InteropDebugger::ParseThreadsEvents()
     //      could be stopped at CoreCLR's breakpoint and wait for waitpid, but we wait for managed process `Stop()` in the same time.
 
     // In case m_callbackEventMutex is locked, return to waitpid loop for next cycle.
+    // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only, but with `try_lock()` we fine here.
     if (!m_callbackEventMutex.try_lock())
         return;
 
@@ -524,7 +810,9 @@ void InteropDebugger::ParseThreadsEvents()
         case thread_stat_e::stopped_breakpoint_event_detected:
             m_callbackEvents.emplace_back(pid, m_TIDs[pid].stat, m_TIDs[pid].stop_event_data);
             break;
-
+        case thread_stat_e::stopped_signal_event_detected:
+            m_callbackEvents.emplace_back(pid, m_TIDs[pid].stat, m_TIDs[pid].stop_event_data);
+            break;
         default:
             LOGW("This event type is not stop event: %d", m_TIDs[pid].stat);
             break;
@@ -551,37 +839,228 @@ void InteropDebugger::ContinueAllThreadsWithEvents()
         {
         case thread_stat_e::stopped_breakpoint_event_in_progress:
             BrkStopAllThreads(allThreadsWereStopped);
-            m_sharedBreakpoints->InteropStepOverBrk(tid.first, tid.second.stop_event_data.addr);
+            m_sharedBreakpoints->InteropStepOverBrk(tid.first, tid.second.stop_event_data.addr, 
+                                                    [&](pid_t step_pid, std::uintptr_t step_addr) {return SingleStepOnBrk(step_pid, step_addr);});
+            break;
+        case thread_stat_e::stopped_signal_event_in_progress:
+            tid.second.stat = thread_stat_e::stopped_on_event_need_continue;
+            m_changedThreads.emplace_back(tid.first);
+            break;
+        case thread_stat_e::stopped_on_event_as_native_thread:
+            tid.second.stat = thread_stat_e::stopped;
+            tid.second.stop_signal = 0;
+            m_changedThreads.emplace_back(tid.first);
             break;
         default:
             continue;
         }
+    }
 
-        if (async_ptrace(PTRACE_CONT, tid.first, nullptr, nullptr) == -1)
-            LOGW("Ptrace cont error: %s", strerror(errno));
+    // Continue native code execution with care about stop events (CallbacksQueue).
+    // Ignore allThreadsWereStopped status here, since we could have different events not only breakpoints.
+    ParseThreadsChanges();
+}
+
+static void GetAllManagedThreads(ICorDebugProcess *pProcess, std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads)
+{
+    ToRelease<ICorDebugThreadEnum> iCorThreadEnum;
+    pProcess->EnumerateThreads(&iCorThreadEnum);
+    ULONG fetched = 0;
+    ToRelease<ICorDebugThread> iCorThread;
+    while (SUCCEEDED(iCorThreadEnum->Next(1, &iCorThread, &fetched)) && fetched == 1)
+    {
+        DWORD tid = 0;
+        if (SUCCEEDED(iCorThread->GetID(&tid)))
+            allManagedTreads[tid] = iCorThread.Detach();
+        else
+            iCorThread.Free();
+    }
+}
+
+static void StopAllManagedThreads(std::unordered_map<pid_t, thread_status_t> &TIDs, std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads,
+                                  std::vector<pid_t> &stoppedManagedTreads)
+{
+    for (const auto &managedThread : allManagedTreads)
+    {
+        if (TIDs[managedThread.first].stat != thread_stat_e::running)
+            continue;
+
+        if (async_ptrace(PTRACE_INTERRUPT, managedThread.first, nullptr, nullptr) == -1)
+            LOGW("Ptrace interrupt error: %s\n", strerror(errno));
         else
+            stoppedManagedTreads.emplace_back(managedThread.first);
+    }
+}
+
+static void AnalyzeAllManagedThreadsTopFrame(std::unordered_map<pid_t, thread_status_t> &TIDs,
+                                             std::map<pid_t, ToRelease<ICorDebugThread>> &allManagedTreads)
+{
+    for (const auto &managedThread : allManagedTreads)
+    {
+        // Note, we could already have some stop event here, that should be parsed separately.
+        if (TIDs[managedThread.first].stat != thread_stat_e::stopped)
+            continue;
+
+        HRESULT Status;
+        ToRelease<ICorDebugThread3> iCorThread3;
+        ToRelease<ICorDebugStackWalk> iCorStackWalk;
+        ToRelease<ICorDebugFrame> iCorFrame;
+        if (FAILED(managedThread.second->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &iCorThread3)) ||
+            FAILED(iCorThread3->CreateStackWalk(&iCorStackWalk)) ||
+            FAILED(Status = iCorStackWalk->GetFrame(&iCorFrame)))
+            continue;
+
+        if (Status == S_FALSE) // S_FALSE - The current frame is a native stack frame.
         {
-            tid.second.stat = thread_stat_e::running;
-            tid.second.stop_signal = 0;
+            TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+            continue;
+        }
+
+        // At this point (Status == S_OK).
+        // Accordingly to CoreCLR sources, S_OK could be with nulled iCorFrame, that must be skipped.
+        // Related to `FrameType::kExplicitFrame` in runtime (skipped frame function with no-frame transition represents)
+        if (iCorFrame == NULL)
+            continue;
+
+        // Managed frame.
+        ToRelease<ICorDebugFunction> iCorFunction;
+        if (SUCCEEDED(iCorFrame->GetFunction(&iCorFunction)))
+        {
+            // In case of optimized managed code, top frame could be native (optimized code could have inlined pinvoke).
+            // Note, breakpoint can't be set in optimized managed code and step can't stop here, since this code is not JMC for sure.
+            BOOL bJustMyCode;
+            ToRelease<ICorDebugFunction2> iCorFunction2;
+            if (SUCCEEDED(iCorFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID *) &iCorFunction2)) &&
+                SUCCEEDED(iCorFunction2->GetJMCStatus(&bJustMyCode)) &&
+                // Check for optimized code. In case of optimized code, JMC status can't be set to TRUE.
+                // https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/ee/debugger.cpp#L11257-L11260
+                SUCCEEDED(iCorFunction2->SetJMCStatus(TRUE)))
+            {
+                // Revert back JMC status if need.
+                if (bJustMyCode != TRUE)
+                    iCorFunction2->SetJMCStatus(bJustMyCode);
+
+                continue; // not optimized code for sure, don't stop thread.
+            }
+
+            // Prevent thread native stop in case thread stopped by managed exception.
+            ToRelease<ICorDebugThread> iCorThread;
+            ToRelease<ICorDebugValue> iCorExceptionValue;
+            if (SUCCEEDED(iCorThread3->QueryInterface(IID_ICorDebugThread, (LPVOID *) &iCorThread)) &&
+                SUCCEEDED(iCorThread->GetCurrentException(&iCorExceptionValue)) && iCorExceptionValue != nullptr)
+                continue;
+
+            TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+            continue;
         }
+
+        ToRelease<ICorDebugNativeFrame> iCorNativeFrame;
+        if (SUCCEEDED(iCorFrame->QueryInterface(IID_ICorDebugNativeFrame, (LPVOID*) &iCorNativeFrame)))
+            TIDs[managedThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+        //else
+        //    Some unknown frame, don't stop it during stop event.
     }
+}
 
-    // Continue threads execution with care about stop events (CallbacksQueue).
-    if (allThreadsWereStopped)
-        ParseThreadsChanges();
+HRESULT InteropDebugger::StopAllNativeThreads(ICorDebugProcess *pProcess)
+{
+    if (!pProcess)
+        return E_INVALIDARG;
+
+    std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+    if (m_TIDs.empty())
+        return S_OK;
+
+    std::map<pid_t, ToRelease<ICorDebugThread>> allManagedTreads;
+    GetAllManagedThreads(pProcess, allManagedTreads);
+
+    std::vector<pid_t> stoppedManagedTreads;
+    stoppedManagedTreads.reserve(allManagedTreads.size());
+    StopAllManagedThreads(m_TIDs, allManagedTreads, stoppedManagedTreads);
+    WaitThreadStop(g_waitForAllThreads, &stoppedManagedTreads);
+
+    AnalyzeAllManagedThreadsTopFrame(m_TIDs, allManagedTreads);
+    // At this point we have all managed threads stopped by `ptrace`:
+    //    thread_stat_e::stopped                           - execution will continue in ParseThreadsChanges() call in this method
+    //    thread_stat_e::stopped_on_event_as_native_thread - execution will continue only after stop event ends
+
+    // Stop all native treads and analyze them (we need stop all not CoreCLR related treads now).
+
+    StopAllRunningThreads(m_TIDs);
+    WaitThreadStop(g_waitForAllThreads);
+
+    static std::unordered_set<std::string> unwindStopFrames{
+        "libstdc++.so`execute_native_thread_routine()",
+        "libpthread.so`start_thread()",
+        "libc.so`__libc_start_main()",
+        "libc.so`clone()"
+    };
+
+    for (auto &nativeThread : m_TIDs)
+    {
+        // Skip managed threads, we analyzed all of them early.
+        if (allManagedTreads.find(nativeThread.first) != allManagedTreads.end())
+            continue;
+
+        // Note, we could already have some stop event here, that should be parsed separately.
+        if (m_TIDs[nativeThread.first].stat != thread_stat_e::stopped)
+            continue;
+
+        bool skipThread = false;
+        bool reachedStopFrames = false;
+        ThreadStackUnwind(nativeThread.first, nullptr, [&](std::uintptr_t addr)
+        {
+#if defined(DEBUGGER_UNIX_ARM)
+            addr = addr & ~((std::uintptr_t)1); // convert to proper (even) address (debug info use only even addresses)
+#endif
+
+            // Note, in this case we don't need info for address that is part of previous (already executed) code (we need only procedure name).
+            // This mean, no need address correction here for all frames.
+
+            std::string libLoadName;
+            std::string procName;
+            if (!m_uniqueInteropLibraries->FindDataForNotClrAddr(addr, libLoadName, procName))
+            {
+                skipThread = true;
+                return false;
+            }
+
+            // Case when lib name and/or procedure name can't be gathered, considered to be "user" code (frame should be skipped).
+            if (libLoadName.empty() || procName.empty())
+                return true;
+
+            // Note, in order to unwind successfully (and detect all user's code related threads), debug info for all user's code should be installed, plus,
+            // some arches could miss dynsyms for `start_thread()` or `clone()`, that mean debug info for libc/glibc and libstdc++ should be installed.
+            std::string unwindFrame = libLoadName + "`" + procName;
+            reachedStopFrames = unwindStopFrames.find(unwindFrame) != unwindStopFrames.end();
+            return !reachedStopFrames;
+        });
+
+        if (!skipThread && reachedStopFrames)
+            m_TIDs[nativeThread.first].stat = thread_stat_e::stopped_on_event_as_native_thread;
+    }
+
+    ParseThreadsChanges();
+    return S_OK;
 }
 
-void InteropDebugger::WaitpidWorker()
+void InteropDebuggerBase::InitWaitpidWorkerThread()
+{
+    m_waitpidWorker = std::thread(&InteropDebuggerBase::WaitpidWorker, this);
+}
+
+void InteropDebuggerBase::WaitpidWorker()
 {
     std::unique_lock<std::mutex> lockEvent(m_callbackEventMutex);
     m_callbackEventNeedExit = false;
-    m_callbackEventWorker = std::thread(&InteropDebugger::CallbackEventWorker, this);
+    m_callbackEventWorker = std::thread(&InteropDebuggerBase::CallbackEventWorker, this);
     m_callbackEventCV.wait(lockEvent); // wait for init complete from CallbackEventWorker()
     lockEvent.unlock();
 
-    std::unique_lock<std::mutex> lock(m_waitpidMutex);
+    std::unique_lock<std::mutex> lockWaitpid(m_waitpidMutex);
     m_waitpidCV.notify_one(); // notify Init(), that WaitpidWorker() thread init complete
-    m_waitpidCV.wait(lock); // wait for "respond" and mutex unlock from Init()
+    m_waitpidCV.wait(lockWaitpid); // wait for "respond" and mutex unlock from Init()
 
     pid_t pid = 0;
     int status = 0;
@@ -616,12 +1095,11 @@ void InteropDebugger::WaitpidWorker()
             }
 
             ParseThreadsChanges();
-
-            lock.unlock();
-            // NOTE mutexes lock sequence must be CallbacksQueue->InteropDebugger.
             ParseThreadsEvents();
+
+            lockWaitpid.unlock();
             usleep(10*1000); // sleep 10 ms before next waitpid call
-            lock.lock();
+            lockWaitpid.lock();
 
             if (m_waitpidNeedExit)
                 break;
@@ -632,6 +1110,7 @@ void InteropDebugger::WaitpidWorker()
         if (!WIFSTOPPED(status))
         {
             m_TIDs.erase(pid);
+            pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadExited, ThreadId(pid), true));
 
             // Tracee exited or was killed by signal.
             if (pid == m_TGID)
@@ -639,6 +1118,7 @@ void InteropDebugger::WaitpidWorker()
                 assert(m_TIDs.empty());
                 m_TGID = 0;
                 GetWaitpid().SetPidExitedStatus(pid, status);
+                m_NotifyLastThreadExited(status);
             }
 
             continue;
@@ -678,12 +1158,18 @@ void InteropDebugger::WaitpidWorker()
             }
         }
 
+        if (m_TIDs.find(pid) == m_TIDs.end())
+            pProtocol->EmitThreadEvent(ThreadEvent(NativeThreadStarted, ThreadId(pid), true));
+
         m_TIDs[pid].stat = thread_stat_e::stopped; // if we here, this mean we get some stop signal for this thread
         m_TIDs[pid].stop_signal = stop_signal;
         m_TIDs[pid].event = (unsigned)status >> 16;
         m_changedThreads.emplace_back(pid);
     }
 
+    m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED;
+    lockWaitpid.unlock(); // Important! Lock sequence must be (1)m_callbackEventMutex -> (2)m_waitpidMutex only!
+
     lockEvent.lock();
     m_callbackEventNeedExit = true;
     m_callbackEventCV.notify_one(); // notify CallbackEventWorker() for exit from infinite loop
@@ -691,10 +1177,9 @@ void InteropDebugger::WaitpidWorker()
     m_callbackEventWorker.join();
 
     m_waitpidCV.notify_one(); // notify Shutdown(), that execution exit from WaitpidWorker()
-    m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED;
 }
 
-HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, int &error_n)
+HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, bool attach, std::function<void(int)> NotifyLastThreadExited, int &error_n)
 {
     async_ptrace_init();
     GetWaitpid().SetInteropWaitpidMode(true);
@@ -712,7 +1197,7 @@ HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &shared
         return E_FAIL;
     };
 
-    if (FAILED(SeizeAndInterruptAllThreads(m_TIDs, pid, error_n)))
+    if (FAILED(SeizeAndInterruptAllThreads(m_TIDs, pid, attach, error_n, pProtocol)))
         return ExitWithError();
 
     WaitThreadStop(g_waitForAllThreads);
@@ -734,32 +1219,36 @@ HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &shared
     if (!m_sharedBreakpoints->InteropSetupRendezvousBrk(pid, loadLib, unloadLib, isThumbCode, error_n))
         return ExitWithError();
 
+    // At this point all threads are stopped, continue execution for all not event-related stopped threads.
+    ParseThreadsChanges();
+
     m_waitpidNeedExit = false;
-    m_waitpidWorker = std::thread(&InteropDebugger::WaitpidWorker, this);
+    InitWaitpidWorkerThread();
     m_waitpidCV.wait(lock); // wait for init complete from WaitpidWorker()
     m_waitpidThreadStatus = WaitpidThreadStatus::WORK;
     m_TGID = pid;
     m_sharedCallbacksQueue = sharedCallbacksQueue;
     m_waitpidCV.notify_one(); // notify WaitpidWorker() to start infinite loop
 
+    m_NotifyLastThreadExited = NotifyLastThreadExited;
     InitNativeFramesUnwind(this);
     return S_OK;
 }
 
 // In order to add or remove breakpoint we must stop all threads first.
-void InteropDebugger::BrkStopAllThreads(bool &allThreadsWereStopped)
+void InteropDebuggerHelpers::BrkStopAllThreads(bool &allThreadsWereStopped)
 {
     if (allThreadsWereStopped)
         return;
 
-    StopAllRunningThreads();
+    StopAllRunningThreads(m_TIDs);
     WaitThreadStop(g_waitForAllThreads);
     allThreadsWereStopped = true;
 }
 
 // In case we need remove breakpoint from address, we must care about all threads first, since some threads could break on this breakpoint already.
 // Note, at this point we don't need step over breakpoint, since we don't need "fix, step and restore" logic here.
-void InteropDebugger::BrkFixAllThreads(std::uintptr_t checkAddr)
+void InteropDebuggerHelpers::BrkFixAllThreads(std::uintptr_t checkAddr)
 {
     for (auto &entry : m_TIDs)
     {
@@ -1072,5 +1561,35 @@ HRESULT InteropDebugger::GetFrameForAddr(std::uintptr_t addr, StackFrame &frame)
     return S_OK;
 }
 
+bool InteropDebugger::IsNativeThreadStopped(pid_t pid)
+{
+    std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+    if (m_TIDs.empty())
+        return S_OK;
+
+    auto tid = m_TIDs.find(pid);
+    if (tid == m_TIDs.end())
+        return E_INVALIDARG;
+
+    assert(tid->second.stat != thread_stat_e::stopped);
+    return tid->second.stat != thread_stat_e::running;
+}
+
+void InteropDebugger::WalkAllThreads(std::function<void(pid_t, bool)> cb)
+{
+    std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+    if (m_TIDs.empty())
+        return;
+
+    std::map<pid_t, thread_status_t> orderedTIDs(m_TIDs.begin(), m_TIDs.end());
+
+    for (auto &tid : orderedTIDs)
+    {
+        cb(tid.first, tid.second.stat == thread_stat_e::running);
+    }
+}
+
 } // namespace InteropDebugging
 } // namespace netcoredbg
index 45fcba749b314d057920c8628fc2bf1834921311..b89c9422423373836d88c8fb3aa13e2feb2efffa 100644 (file)
@@ -33,15 +33,22 @@ class InteropLibraries;
 
 enum class thread_stat_e
 {
+    // Important! `stopped` state is temporary and must be parsed by `ParseThreadsChanges()` and not leave `m_waitpidMutex` locked scope.
+    // Before leave `m_waitpidMutex` locked scope this state should be reseted to `running` with execution continue or changed to event-related state.
     stopped,
+    stopped_on_event_as_native_thread,
     stopped_breakpoint_event_detected,
     stopped_breakpoint_event_in_progress,
+    stopped_signal_event_detected,
+    stopped_signal_event_in_progress,
+    stopped_on_event_need_continue,
     running
 };
 
 struct stop_event_data_t
 {
     std::uintptr_t addr = 0;
+    std::string signal;
 };
 
 struct thread_status_t
@@ -50,6 +57,10 @@ struct thread_status_t
     unsigned stop_signal = 0;
     unsigned event = 0;
 
+    // Address of breakpoint that single step at "step over breakpoint" was failed.
+    // Hold address of breakpoint that must be "step over" (skipped).
+    std::uintptr_t addrStepOverBreakpointFailed = 0;
+
     // Data, that should be stored in order to create stop event (CallbacksQueue) and/or continue thread execution.
     stop_event_data_t stop_event_data;
 };
@@ -67,32 +78,15 @@ struct callback_event_t
     {}
 };
 
-class InteropDebugger
+class InteropDebuggerBase
 {
-public:
-
-    InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
-                    std::shared_ptr<Breakpoints> &sharedBreakpoints,
-                    std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
-
-    // Initialize interop debugging, attach to process, detect loaded libs, setup native breakpoints, etc.
-    HRESULT Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, int &error_n);
-    // Shutdown interop debugging, remove all native breakpoints, detach from threads, etc.
-    void Shutdown();
-
-    // Called by CallbacksQueue for continue process (continue threads with processed stop events).
-    void ContinueAllThreadsWithEvents();
-    HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints);
-    HRESULT AllBreakpointsActivate(bool act);
-    HRESULT BreakpointActivate(uint32_t id, bool act);
+protected:
 
-    HRESULT GetFrameForAddr(std::uintptr_t addr, StackFrame &frame);
-    HRESULT UnwindNativeFrames(pid_t pid, bool firstFrame, std::uintptr_t endAddr, CONTEXT *pStartContext,
-                               std::function<HRESULT(NativeFrame &nativeFrame)> nativeFramesCallback);
-
-private:
+    InteropDebuggerBase(IProtocol *pProtocol_,
+                        std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                        std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
 
-    std::shared_ptr<IProtocol> m_sharedProtocol;
+    IProtocol *pProtocol;
     std::shared_ptr<Breakpoints> m_sharedBreakpoints;
     std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
     std::unique_ptr<InteropLibraries> m_uniqueInteropLibraries;
@@ -120,6 +114,7 @@ private:
     std::thread m_waitpidWorker;
     bool m_waitpidNeedExit = false;
     pid_t m_TGID = 0;
+    std::function<void(int)> m_NotifyLastThreadExited;
     std::unordered_map<pid_t, thread_status_t> m_TIDs;
     // We use std::list here, since we need container that not invalidate iterators at `emplace_back()` call.
     std::list<pid_t> m_changedThreads;
@@ -127,20 +122,75 @@ private:
     std::condition_variable m_waitpidCV;
     WaitpidThreadStatus m_waitpidThreadStatus = WaitpidThreadStatus::UNKNOWN;
 
+    void InitWaitpidWorkerThread();
     void WaitpidWorker();
 
-    void LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr);
-    void UnloadLib(const std::string &realLibName);
+    void WaitThreadStop(pid_t stoppedPid, std::vector<pid_t> *stopTreads = nullptr);
+    bool SingleStepOnBrk(pid_t pid, std::uintptr_t addr);
+    void ParseThreadsEvents();
+    void ParseThreadsChanges();
+    virtual void Parse_SIGILL(pid_t pid) = 0;
+    virtual void Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid) = 0;
+    virtual void Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid) = 0;
+};
+
+class InteropDebuggerSignals : protected InteropDebuggerBase
+{
+protected:
+
+    InteropDebuggerSignals(IProtocol *pProtocol_,
+                           std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                           std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
+
+    void Parse_SIGILL(pid_t pid) override;
+    void Parse_SIGTRAP__PTRACE_EVENT_EXEC(pid_t pid) override;
+    void Parse_SIGTRAP__NOT_PTRACE_EVENT(pid_t pid) override;
+};
+
+class InteropDebuggerHelpers : protected InteropDebuggerSignals
+{
+protected:
+
+    InteropDebuggerHelpers(IProtocol *pProtocol_,
+                           std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                           std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
 
-    void StopAllRunningThreads();
-    void WaitThreadStop(pid_t stoppedPid);
     void StopAndDetach(pid_t tgid);
     void Detach(pid_t tgid);
-    void ParseThreadsChanges();
-    void ParseThreadsEvents();
+
+    void LoadLib(pid_t pid, const std::string &libLoadName, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr);
+    void UnloadLib(const std::string &realLibName);
+
     void BrkStopAllThreads(bool &allThreadsWereStopped);
     void BrkFixAllThreads(std::uintptr_t checkAddr);
+};
+
+class InteropDebugger final : InteropDebuggerHelpers
+{
+public:
+
+    InteropDebugger(IProtocol *pProtocol_,
+                    std::shared_ptr<Breakpoints> &sharedBreakpoints,
+                    std::shared_ptr<EvalWaiter> &sharedEvalWaiter);
+
+    // Initialize interop debugging, attach to process, detect loaded libs, setup native breakpoints, etc.
+    HRESULT Init(pid_t pid, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue, bool attach, std::function<void(int)> NotifyLastThreadExited, int &error_n);
+    // Shutdown interop debugging, remove all native breakpoints, detach from threads, etc.
+    void Shutdown();
+
+    // Called by CallbacksQueue for continue process (continue threads with processed stop events).
+    void ContinueAllThreadsWithEvents();
+    HRESULT StopAllNativeThreads(ICorDebugProcess *pProcess);
+    HRESULT SetLineBreakpoints(const std::string& filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints);
+    HRESULT AllBreakpointsActivate(bool act);
+    HRESULT BreakpointActivate(uint32_t id, bool act);
+
+    HRESULT GetFrameForAddr(std::uintptr_t addr, StackFrame &frame);
+    HRESULT UnwindNativeFrames(pid_t pid, bool firstFrame, std::uintptr_t endAddr, CONTEXT *pStartContext,
+                               std::function<HRESULT(NativeFrame &nativeFrame)> nativeFramesCallback);
 
+    bool IsNativeThreadStopped(pid_t pid);
+    void WalkAllThreads(std::function<void(pid_t, bool)> cb);
 };
 
 } // namespace InteropDebugging
index 51e9d3b86a13c04cfdae130f7628e80fe821dd43..c1f3bc1f16f91c3209015307587b6015c84330ac 100644 (file)
@@ -108,10 +108,9 @@ static bool GetProcData(pid_t pid, std::string &execName, std::uintptr_t &startA
         return false;
     }
 
-    ssize_t read;
     char *line = nullptr;
     size_t lineLen = 0;
-    while ((read = getline(&line, &lineLen, mapsFile)) != -1)
+    while (getline(&line, &lineLen, mapsFile) != -1)
     {
         void *startAddress, *endAddress, *offset;
         int devHi, devLo, inode;
@@ -241,10 +240,9 @@ std::uintptr_t GetLibEndAddrAndRealName(pid_t TGID, pid_t pid, std::string &real
     std::uintptr_t endAddr = 0;
     assert(realLibName.empty());
 
-    ssize_t read;
     char *line = nullptr;
     size_t lineLen = 0;
-    while ((read = getline(&line, &lineLen, mapsFile)) != -1)
+    while (getline(&line, &lineLen, mapsFile) != -1)
     {
         void *startAddress, *endAddress, *offset;
         int devHi, devLo, inode;
index d531a8b411c603c4bd3c13bb8302c70751198d22..a3772e9d0753490cb4ae4b4a3af5a55a011df195 100644 (file)
@@ -305,6 +305,9 @@ void ThreadStackUnwind(pid_t pid, std::array<unw_word_t, UNW_REG_LAST + 1> *cont
                 break;
             }
 
+            if (pc == 0)
+                break;
+
 #if defined(UNW_TARGET_AARCH64)
             if (prev_pc == pc)
                 break;
index 6438b4f7b737f586ec8f2fd4750645e0569eb9aa..1f015338db16f3543f2f4dbd8f41a450872da360 100644 (file)
@@ -156,6 +156,44 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::CreateProcess(ICorDebugProcess *pProc
     // for global variables. coreclr_shutdown only should be called on process exit.
     Interop::Init(m_debugger.m_clrPath);
 
+#ifdef INTEROP_DEBUGGING
+    // Note, in case `attach` CoreCLR also call CreateProcess() that call this method.
+    int error_n = 0;
+    bool attach = m_debugger.m_startMethod == StartAttach;
+    auto NotifyLastThreadExited = [&](int status)
+    {
+        // In case debuggee process rude terminated by some signal, we may have situation when
+        // `ManagedCallback::ExitProcess()` will be newer called by dbgshim.
+        if (!WIFSIGNALED(status))
+            return;
+
+        // If we still `Attached` here, `ManagedCallback::ExitProcess()` was not called.
+        std::unique_lock<std::mutex> lockAttachedMutex(m_debugger.m_processAttachedMutex);
+        if (m_debugger.m_processAttachedState == ProcessAttachedState::Attached)
+            m_debugger.m_processAttachedCV.wait_for(lockAttachedMutex, std::chrono::milliseconds(3000));
+        if (m_debugger.m_processAttachedState == ProcessAttachedState::Unattached)
+            return;
+        lockAttachedMutex.unlock();
+
+        if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
+            LOGW("The target process exited while evaluating the function.");
+
+        m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(nullptr, nullptr);
+
+        m_debugger.pProtocol->EmitExitedEvent(ExitedEvent(GetWaitpid().GetExitCode()));
+        m_debugger.NotifyProcessExited();
+        m_debugger.pProtocol->EmitTerminatedEvent();
+        m_debugger.m_ioredirect.async_cancel();
+    };
+    if (m_debugger.m_interopDebugging &&
+        FAILED(m_debugger.m_sharedInteropDebugger->Init((pid_t)m_debugger.m_processId, m_sharedCallbacksQueue, attach, NotifyLastThreadExited, error_n)))
+    {
+        LOGE("Interop debugging disabled due to initialization fail: %s", strerror(error_n));
+        m_debugger.pProtocol->EmitInteropDebuggingErrorEvent(error_n);
+        m_debugger.m_interopDebugging = false;
+    }
+#endif // INTEROP_DEBUGGING
+
     // Important! Care about callback queue before NotifyProcessCreated() call.
     // In case of `attach`, NotifyProcessCreated() call will notify debugger that debuggee process attached and debugger
     // should stop debuggee process by dirrect `Pause()` call. From another side, callback queue have bunch of asynchronous
@@ -206,9 +244,9 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::ExitProcess(ICorDebugProcess *pProces
     }
 #endif // FEATURE_PAL
 
-    m_debugger.m_sharedProtocol->EmitExitedEvent(ExitedEvent(exitCode));
+    m_debugger.pProtocol->EmitExitedEvent(ExitedEvent(exitCode));
     m_debugger.NotifyProcessExited();
-    m_debugger.m_sharedProtocol->EmitTerminatedEvent();
+    m_debugger.pProtocol->EmitTerminatedEvent();
     m_debugger.m_ioredirect.async_cancel();
     return S_OK;
 }
@@ -223,7 +261,7 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::CreateThread(ICorDebugAppDomain *pApp
     ThreadId threadId(getThreadId(pThread));
     m_debugger.m_sharedThreads->Add(threadId);
 
-    m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadStarted, threadId));
+    m_debugger.pProtocol->EmitThreadEvent(ThreadEvent(ManagedThreadStarted, threadId, m_debugger.m_interopDebugging));
     return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
 }
 
@@ -240,7 +278,7 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::ExitThread(ICorDebugAppDomain *pAppDo
 
     m_debugger.m_sharedBreakpoints->ManagedCallbackExitThread(pThread);
 
-    m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadExited, threadId));
+    m_debugger.pProtocol->EmitThreadEvent(ThreadEvent(ManagedThreadExited, threadId, m_debugger.m_interopDebugging));
     return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
 }
 
@@ -252,15 +290,19 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::LoadModule(ICorDebugAppDomain *pAppDo
     std::string outputText;
     m_debugger.m_sharedModules->TryLoadModuleSymbols(pModule, module, m_debugger.IsJustMyCode(), m_debugger.IsHotReload(), outputText);
     if (!outputText.empty())
-        m_debugger.m_sharedProtocol->EmitOutputEvent(OutputStdErr, outputText);
-    m_debugger.m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
+    {
+        m_debugger.pProtocol->EmitOutputEvent(OutputStdErr, outputText);
+    }
+    m_debugger.pProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));
 
     if (module.symbolStatus == SymbolsLoaded)
     {
         std::vector<BreakpointEvent> events;
         m_debugger.m_sharedBreakpoints->ManagedCallbackLoadModule(pModule, events);
         for (const BreakpointEvent &event : events)
-            m_debugger.m_sharedProtocol->EmitBreakpointEvent(event);
+        {
+            m_debugger.pProtocol->EmitBreakpointEvent(event);
+        }
     }
     m_debugger.m_sharedBreakpoints->ManagedCallbackLoadModuleAll(pModule);
 
@@ -321,7 +363,7 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::LogMessage(ICorDebugAppDomain *pAppDo
         src = "Debugger.Log";
     }
 
-    m_debugger.m_sharedProtocol->EmitOutputEvent(OutputConsole, to_utf8(pMessage), src);
+    m_debugger.pProtocol->EmitOutputEvent(OutputConsole, to_utf8(pMessage), src);
     return m_sharedCallbacksQueue->ContinueAppDomain(pAppDomain);
 }
 
index 8a750ba46f6b9a45d1ba92488c82b2fdede1b8f5..4a94eeeb59605a9185a77b55237c037a22707352 100644 (file)
@@ -17,12 +17,12 @@ private:
 
     std::mutex m_refCountMutex;
     ULONG m_refCount;
-    ManagedDebugger &m_debugger;
+    ManagedDebuggerHelpers &m_debugger;
     std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
 
 public:
 
-    ManagedCallback(ManagedDebugger &debugger, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue) :
+    ManagedCallback(ManagedDebuggerHelpers &debugger, std::shared_ptr<CallbacksQueue> &sharedCallbacksQueue) :
         m_refCount(0), m_debugger(debugger), m_sharedCallbacksQueue(sharedCallbacksQueue){}
     ULONG GetRefCount();
 
index 77276d66577e75bc4b31b8273605a3cb5b270ede..09ab58445752e5dbe4ae12d276b3c7fecaf32c93 100644 (file)
@@ -55,6 +55,7 @@
 #ifdef INTEROP_DEBUGGING
 #include "elf++.h"
 #include "dwarf++.h"
+#include "debugger/sigaction.h"
 #endif // INTEROP_DEBUGGING
 
 #include "palclr.h"
@@ -106,71 +107,60 @@ namespace
 
         return 0;
     }
+}
 
-    // Caller must care about m_debugProcessRWLock.
-    HRESULT CheckDebugProcess(ICorDebugProcess *pProcess, std::mutex &processAttachedMutex, ProcessAttachedState processAttachedState)
-    {
-        if (!pProcess)
-            return E_FAIL;
-
-        // We might have case, when process was exited/detached, but m_iCorProcess still not free and hold invalid object.
-        // Note, we can't hold this lock, since this could deadlock execution at ICorDebugManagedCallback::ExitProcess call.
-        std::unique_lock<std::mutex> lockAttachedMutex(processAttachedMutex);
-        if (processAttachedState == ProcessAttachedState::Unattached)
-            return E_FAIL;
-        lockAttachedMutex.unlock();
+// Caller must care about m_debugProcessRWLock.
+HRESULT ManagedDebuggerBase::CheckDebugProcess()
+{
+    if (!m_iCorProcess)
+        return E_FAIL;
 
-        return S_OK;
-    }
+    // We might have case, when process was exited/detached, but m_iCorProcess still not free and hold invalid object.
+    // Note, we can't hold this lock, since this could deadlock execution at ICorDebugManagedCallback::ExitProcess call.
+    std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
+    if (m_processAttachedState == ProcessAttachedState::Unattached)
+        return E_FAIL;
+    lockAttachedMutex.unlock();
 
-    bool HaveDebugProcess(Utility::RWLock &debugProcessRWLock, ICorDebugProcess *pProcess, std::mutex &processAttachedMutex, ProcessAttachedState processAttachedState)
-    {
-        std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(debugProcessRWLock.reader);
-        return SUCCEEDED(CheckDebugProcess(pProcess, processAttachedMutex, processAttachedState));
-    }
+    return S_OK;
 }
 
-void ManagedDebugger::NotifyProcessCreated()
+bool ManagedDebuggerBase::HaveDebugProcess()
 {
-#ifdef INTEROP_DEBUGGING
-    // Note, in case `attach` CoreCLR also call CreateProcess() that call this method.
-    int error_n = 0;
-    if (m_interopDebugging && FAILED(m_uniqueInteropDebugger->Init((pid_t)m_processId, m_sharedCallbacksQueue, error_n)))
-    {
-        LOGE("Interop debugging disabled due to initialization fail: %s", strerror(error_n));
-        m_sharedProtocol->EmitInteropDebuggingErrorEvent(error_n);
-        m_interopDebugging = false;
-    }
-#endif // INTEROP_DEBUGGING
+    std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
+    return SUCCEEDED(CheckDebugProcess());
+}
 
+void ManagedDebuggerBase::NotifyProcessCreated()
+{
     std::unique_lock<std::mutex> lock(m_processAttachedMutex);
     m_processAttachedState = ProcessAttachedState::Attached;
     lock.unlock();
     m_processAttachedCV.notify_one();
 }
 
-void ManagedDebugger::NotifyProcessExited()
+void ManagedDebuggerBase::NotifyProcessExited()
 {
     std::unique_lock<std::mutex> lock(m_processAttachedMutex);
     m_processAttachedState = ProcessAttachedState::Unattached;
     lock.unlock();
-    m_processAttachedCV.notify_one();
+    m_processAttachedCV.notify_all();
 }
 
 // Caller must care about m_debugProcessRWLock.
-void ManagedDebugger::DisableAllBreakpointsAndSteppers()
+void ManagedDebuggerBase::DisableAllBreakpointsAndSteppers()
 {
     m_uniqueSteppers->DisableAllSteppers(m_iCorProcess); // Async stepper could have breakpoints active, disable them first.
     m_sharedBreakpoints->DeleteAllManaged();
     m_sharedBreakpoints->DisableAllManaged(m_iCorProcess); // Last one, disable all breakpoints on all domains, even if we don't hold them.
 }
 
-void ManagedDebugger::SetLastStoppedThread(ICorDebugThread *pThread)
+void ManagedDebuggerBase::SetLastStoppedThread(ICorDebugThread *pThread)
 {
     SetLastStoppedThreadId(getThreadId(pThread));
 }
 
-void ManagedDebugger::SetLastStoppedThreadId(ThreadId threadId)
+void ManagedDebuggerBase::SetLastStoppedThreadId(ThreadId threadId)
 {
     std::lock_guard<std::mutex> lock(m_lastStoppedMutex);
     m_lastStoppedThreadId = threadId;
@@ -180,7 +170,7 @@ void ManagedDebugger::SetLastStoppedThreadId(ThreadId threadId)
     m_sharedBreakpoints->SetLastStoppedIlOffset(m_iCorProcess, m_lastStoppedThreadId);
 }
 
-void ManagedDebugger::InvalidateLastStoppedThreadId()
+void ManagedDebuggerBase::InvalidateLastStoppedThreadId()
 {
     SetLastStoppedThreadId(ThreadId::AllThreads);
 }
@@ -193,12 +183,12 @@ ThreadId ManagedDebugger::GetLastStoppedThreadId()
     return m_lastStoppedThreadId;
 }
 
-ManagedDebugger::ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol) :
+ManagedDebuggerBase::ManagedDebuggerBase(IProtocol *pProtocol_) :
     m_processAttachedState(ProcessAttachedState::Unattached),
     m_lastStoppedThreadId(ThreadId::AllThreads),
     m_startMethod(StartNone),
     m_isConfigurationDone(false),
-    m_sharedProtocol(sharedProtocol),
+    pProtocol(pProtocol_),
     m_sharedThreads(new Threads),
     m_sharedModules(new Modules),
     m_sharedEvalWaiter(new EvalWaiter),
@@ -211,7 +201,7 @@ ManagedDebugger::ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol) :
     m_sharedCallbacksQueue(nullptr),
     m_uniqueManagedCallback(nullptr),
 #ifdef INTEROP_DEBUGGING
-    m_uniqueInteropDebugger(new InteropDebugging::InteropDebugger(m_sharedProtocol, m_sharedBreakpoints, m_sharedEvalWaiter)),
+    m_sharedInteropDebugger(new InteropDebugging::InteropDebugger(pProtocol, m_sharedBreakpoints, m_sharedEvalWaiter)),
 #endif // INTEROP_DEBUGGING
     m_justMyCode(true),
     m_stepFiltering(true),
@@ -226,10 +216,28 @@ ManagedDebugger::ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol) :
 {
     m_sharedEvalStackMachine->SetupEval(m_sharedEvaluator, m_sharedEvalHelpers, m_sharedEvalWaiter);
     m_sharedThreads->SetEvaluator(m_sharedEvaluator);
+#ifdef INTEROP_DEBUGGING
+    // Note, we don't care about m_interopDebugging here, since m_interopDebugging could be changed with env parsing before real start/attach.
+    m_sharedEvalWaiter->SetInteropDebugger(m_sharedInteropDebugger);
+#endif // INTEROP_DEBUGGING
 }
 
-ManagedDebugger::~ManagedDebugger()
+ManagedDebuggerHelpers::ManagedDebuggerHelpers(IProtocol *pProtocol_) :
+    ManagedDebuggerBase(pProtocol_)
+{}
+
+ManagedDebugger::ManagedDebugger(IProtocol *pProtocol_) :
+    ManagedDebuggerHelpers(pProtocol_)
+{}
+
+ManagedDebuggerBase::~ManagedDebuggerBase()
 {
+#ifdef INTEROP_DEBUGGING
+    // Note, we don't care about m_interopDebugging here, since m_interopDebugging could be changed with env parsing before real start/attach.
+    m_sharedEvalWaiter->ResetInteropDebugger();
+#endif // INTEROP_DEBUGGING
+    m_sharedThreads->ResetEvaluator();
+    m_sharedEvalStackMachine->ResetEval();
 }
 
 HRESULT ManagedDebugger::Initialize()
@@ -238,11 +246,11 @@ HRESULT ManagedDebugger::Initialize()
 
     // TODO: Report capabilities and check client support
     m_startMethod = StartNone;
-    m_sharedProtocol->EmitInitializedEvent();
+    pProtocol->EmitInitializedEvent();
     return S_OK;
 }
 
-HRESULT ManagedDebugger::RunIfReady()
+HRESULT ManagedDebuggerHelpers::RunIfReady()
 {
     FrameId::invalidate();
 
@@ -254,7 +262,7 @@ HRESULT ManagedDebugger::RunIfReady()
         case StartLaunch:
             return RunProcess(m_execPath, m_execArgs);
         case StartAttach:
-            return AttachToProcess(m_processId);
+            return AttachToProcess();
         default:
             return E_FAIL;
     }
@@ -332,14 +340,14 @@ HRESULT ManagedDebugger::Disconnect(DisconnectAction action)
 
 #ifdef INTEROP_DEBUGGING
     if (m_interopDebugging)
-        m_uniqueInteropDebugger->Shutdown();
+        m_sharedInteropDebugger->Shutdown();
 #endif
 
     if (!terminate)
     {
         HRESULT Status = DetachFromProcess();
         if (SUCCEEDED(Status))
-            m_sharedProtocol->EmitTerminatedEvent();
+            pProtocol->EmitTerminatedEvent();
 
         m_ioredirect.async_cancel();
         return Status;
@@ -354,7 +362,7 @@ HRESULT ManagedDebugger::StepCommand(ThreadId threadId, StepType stepType)
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     if (m_sharedEvalWaiter->IsEvalRunning())
     {
@@ -375,7 +383,7 @@ HRESULT ManagedDebugger::StepCommand(ThreadId threadId, StepType stepType)
 
     m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
     FrameId::invalidate(); // Clear all created during break frames.
-    m_sharedProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
+    pProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
 
     // Note, process continue must be after event emitted, since we could get new stop event from queue here.
     if (FAILED(Status = m_sharedCallbacksQueue->Continue(m_iCorProcess)))
@@ -390,7 +398,7 @@ HRESULT ManagedDebugger::Continue(ThreadId threadId)
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     if (m_sharedEvalWaiter->IsEvalRunning())
     {
@@ -407,7 +415,7 @@ HRESULT ManagedDebugger::Continue(ThreadId threadId)
 
     m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
     FrameId::invalidate(); // Clear all created during break frames.
-    m_sharedProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
+    pProtocol->EmitContinuedEvent(threadId); // VSCode protocol need thread ID.
 
     // Note, process continue must be after event emitted, since we could get new stop event from queue here.
     if (FAILED(Status = m_sharedCallbacksQueue->Continue(m_iCorProcess)))
@@ -416,33 +424,37 @@ HRESULT ManagedDebugger::Continue(ThreadId threadId)
     return Status;
 }
 
-HRESULT ManagedDebugger::Pause(ThreadId lastStoppedThread)
+HRESULT ManagedDebugger::Pause(ThreadId lastStoppedThread, EventFormat eventFormat)
 {
     LogFuncEntry();
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
-    return m_sharedCallbacksQueue->Pause(m_iCorProcess, lastStoppedThread);
+    return m_sharedCallbacksQueue->Pause(m_iCorProcess, lastStoppedThread, eventFormat);
 }
 
-HRESULT ManagedDebugger::GetThreads(std::vector<Thread> &threads)
+HRESULT ManagedDebugger::GetThreads(std::vector<Thread> &threads, bool withNativeThreads)
 {
     LogFuncEntry();
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
+#ifdef INTEROP_DEBUGGING
+    if (m_interopDebugging && withNativeThreads)
+        return m_sharedThreads->GetInteropThreadsWithState(m_iCorProcess, m_sharedInteropDebugger.get(), threads);
+#endif // INTEROP_DEBUGGING
     return m_sharedThreads->GetThreadsWithState(m_iCorProcess, threads);
 }
 
-VOID ManagedDebugger::StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr)
+VOID ManagedDebuggerHelpers::StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr)
 {
     ManagedDebugger *self = static_cast<ManagedDebugger*>(parameter);
 
-    self->Startup(pCordb, self->m_processId);
+    self->Startup(pCordb);
 
     if (self->m_unregisterToken)
     {
@@ -465,7 +477,7 @@ static bool AreAllHandlesValid(HANDLE *handleArray, DWORD arrayLength)
     return true;
 }
 
-static HRESULT InternalEnumerateCLRs(dbgshim_t &dbgshim, DWORD pid, HANDLE **ppHandleArray, LPWSTR **ppStringArray, DWORD *pdwArrayLength, int tryCount)
+static HRESULT EnumerateCLRs(dbgshim_t &dbgshim, DWORD pid, HANDLE **ppHandleArray, LPWSTR **ppStringArray, DWORD *pdwArrayLength, int tryCount)
 {
     int numTries = 0;
     HRESULT hr;
@@ -529,7 +541,7 @@ static std::string GetCLRPath(dbgshim_t &dbgshim, DWORD pid, int timeoutSec = 3)
     LPWSTR* pStringArray;
     DWORD dwArrayLength;
     const int tryCount = timeoutSec * 10; // 100ms interval between attempts
-    if (FAILED(InternalEnumerateCLRs(dbgshim, pid, &pHandleArray, &pStringArray, &dwArrayLength, tryCount)) || dwArrayLength == 0)
+    if (FAILED(EnumerateCLRs(dbgshim, pid, &pHandleArray, &pStringArray, &dwArrayLength, tryCount)) || dwArrayLength == 0)
         return std::string();
 
     std::string result = to_utf8(pStringArray[0]);
@@ -539,7 +551,7 @@ static std::string GetCLRPath(dbgshim_t &dbgshim, DWORD pid, int timeoutSec = 3)
     return result;
 }
 
-HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
+HRESULT ManagedDebuggerHelpers::Startup(IUnknown *punk)
 {
     HRESULT Status;
 
@@ -549,7 +561,7 @@ HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
     IfFailRet(iCorDebug->Initialize());
 
     if (m_clrPath.empty())
-        m_clrPath = GetCLRPath(m_dbgshim, pid);
+        m_clrPath = GetCLRPath(m_dbgshim, m_processId);
 
     m_sharedCallbacksQueue.reset(new CallbacksQueue(*this));
     m_uniqueManagedCallback.reset(new ManagedCallback(*this, m_sharedCallbacksQueue));
@@ -557,18 +569,18 @@ HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
     if (FAILED(Status))
     {
         iCorDebug->Terminate();
-        m_uniqueManagedCallback.reset(nullptr);
-        m_sharedCallbacksQueue = nullptr;
+        m_uniqueManagedCallback.reset();
+        m_sharedCallbacksQueue.reset();
         return Status;
     }
 
     ToRelease<ICorDebugProcess> iCorProcess;
-    Status = iCorDebug->DebugActiveProcess(pid, FALSE, &iCorProcess);
+    Status = iCorDebug->DebugActiveProcess(m_processId, FALSE, &iCorProcess);
     if (FAILED(Status))
     {
         iCorDebug->Terminate();
-        m_uniqueManagedCallback.reset(nullptr);
-        m_sharedCallbacksQueue = nullptr;
+        m_uniqueManagedCallback.reset();
+        m_sharedCallbacksQueue.reset();
         return Status;
     }
 
@@ -579,8 +591,6 @@ HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
 
     lockProcessRWLock.unlock();
 
-    m_processId = pid;
-
 #ifdef FEATURE_PAL
     GetWaitpid().SetupTrackingPID(m_processId);
 #endif // FEATURE_PAL
@@ -680,7 +690,7 @@ static void PrepareSystemEnvironmentArg(const std::map<std::string, std::string>
         outEnv.push_back('\0');
 }
 
-HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs)
+HRESULT ManagedDebuggerHelpers::RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs)
 {
     HRESULT Status;
 
@@ -697,9 +707,22 @@ HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vect
 
     HANDLE resumeHandle = 0; // Fake thread handle for the process resume
 
+#ifdef INTEROP_DEBUGGING
+    bool prevInteropDebuggingStatus = !!m_interopDebugging;
+#endif INTEROP_DEBUGGING
+
     std::vector<char> outEnv;
     PrepareSystemEnvironmentArg(m_env, outEnv, m_hotReload, m_interopDebugging);
 
+#ifdef INTEROP_DEBUGGING
+    if ((!!m_interopDebugging) != prevInteropDebuggingStatus)
+    {
+        // In case of interop debugging we depend on SIGCHLD set to SIG_DFL by init code.
+        // Note, debugger include corhost (CoreCLR) that could setup sigaction for SIGCHLD and ruin interop debugger work.
+        SetSigactionMode(!!m_interopDebugging);
+    }
+#endif INTEROP_DEBUGGING
+
     // cwd in launch.json set working directory for debugger https://code.visualstudio.com/docs/python/debugging#_cwd
     if (!m_cwd.empty())
     {
@@ -733,12 +756,12 @@ HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vect
     if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
         return E_FAIL;
 
-    m_sharedProtocol->EmitExecEvent(PID{m_processId}, fileExec);
+    pProtocol->EmitExecEvent(PID{m_processId}, fileExec);
 
     return S_OK;
 }
 
-HRESULT ManagedDebugger::CheckNoProcess()
+HRESULT ManagedDebuggerBase::CheckNoProcess()
 {
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
 
@@ -754,7 +777,7 @@ HRESULT ManagedDebugger::CheckNoProcess()
     return S_OK;
 }
 
-HRESULT ManagedDebugger::DetachFromProcess()
+HRESULT ManagedDebuggerHelpers::DetachFromProcess()
 {
     do {
         std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
@@ -782,7 +805,7 @@ HRESULT ManagedDebugger::DetachFromProcess()
     return S_OK;
 }
 
-HRESULT ManagedDebugger::TerminateProcess()
+HRESULT ManagedDebuggerHelpers::TerminateProcess()
 {
     do {
         std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
@@ -814,12 +837,12 @@ HRESULT ManagedDebugger::TerminateProcess()
     return S_OK;
 }
 
-void ManagedDebugger::Cleanup()
+void ManagedDebuggerBase::Cleanup()
 {
     m_sharedModules->CleanupAllModules();
     m_sharedEvalHelpers->Cleanup();
     m_sharedVariables->Clear(); // Important, must be sync with MIProtocol m_vars.clear()
-    m_sharedProtocol->Cleanup();
+    pProtocol->Cleanup();
 
     std::lock_guard<Utility::RWLock::Writer> guardProcessRWLock(m_debugProcessRWLock.writer);
 
@@ -842,20 +865,20 @@ void ManagedDebugger::Cleanup()
     m_sharedCallbacksQueue = nullptr;
 }
 
-HRESULT ManagedDebugger::AttachToProcess(DWORD pid)
+HRESULT ManagedDebuggerHelpers::AttachToProcess()
 {
     HRESULT Status;
 
     IfFailRet(CheckNoProcess());
 
-    m_clrPath = GetCLRPath(m_dbgshim, pid);
+    m_clrPath = GetCLRPath(m_dbgshim, m_processId);
     if (m_clrPath.empty())
         return E_INVALIDARG; // Unable to find libcoreclr.so
 
     WCHAR pBuffer[100];
     DWORD dwLength;
     IfFailRet(m_dbgshim.CreateVersionStringFromModule(
-        pid,
+        m_processId,
         reinterpret_cast<LPCWSTR>(to_utf16(m_clrPath).c_str()),
         pBuffer,
         _countof(pBuffer),
@@ -866,7 +889,7 @@ HRESULT ManagedDebugger::AttachToProcess(DWORD pid)
     IfFailRet(m_dbgshim.CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_4_0, pBuffer, &pCordb));
 
     m_unregisterToken = nullptr;
-    IfFailRet(Startup(pCordb, pid));
+    IfFailRet(Startup(pCordb));
 
     std::unique_lock<std::mutex> lockAttachedMutex(m_processAttachedMutex);
     if (!m_processAttachedCV.wait_for(lockAttachedMutex, startupWaitTimeout, [this]{return m_processAttachedState == ProcessAttachedState::Attached;}))
@@ -881,7 +904,7 @@ HRESULT ManagedDebugger::GetExceptionInfo(ThreadId threadId, ExceptionInfo &exce
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     ToRelease<ICorDebugThread> iCorThread;
     IfFailRet(m_iCorProcess->GetThread(int(threadId), &iCorThread));
@@ -898,7 +921,7 @@ HRESULT ManagedDebugger::UpdateLineBreakpoint(int id, int linenum, Breakpoint &b
 {
     LogFuncEntry();
 
-    bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+    bool haveProcess = HaveDebugProcess();
     return m_sharedBreakpoints->UpdateLineBreakpoint(haveProcess, id, linenum, breakpoint);
 }
 
@@ -935,12 +958,12 @@ HRESULT ManagedDebugger::SetLineBreakpoints(const std::string& filename,
     LogFuncEntry();
 
 #ifdef INTEROP_DEBUGGING
-    // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing
+    // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
     if (isNativeSource(filename))
-        return m_uniqueInteropDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints);
+        return m_sharedInteropDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints);
 #endif // INTEROP_DEBUGGING
 
-    bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+    bool haveProcess = HaveDebugProcess();
     return m_sharedBreakpoints->SetLineBreakpoints(haveProcess, filename, lineBreakpoints, breakpoints);
 }
 
@@ -948,7 +971,7 @@ HRESULT ManagedDebugger::SetFuncBreakpoints(const std::vector<FuncBreakpoint> &f
 {
     LogFuncEntry();
 
-    bool haveProcess = HaveDebugProcess(m_debugProcessRWLock, m_iCorProcess, m_processAttachedMutex, m_processAttachedState);
+    bool haveProcess = HaveDebugProcess();
     return m_sharedBreakpoints->SetFuncBreakpoints(haveProcess, funcBreakpoints, breakpoints);
 }
 
@@ -958,7 +981,8 @@ HRESULT ManagedDebugger::BreakpointActivate(int id, bool act)
         return S_OK;
 
 #ifdef INTEROP_DEBUGGING
-    return m_uniqueInteropDebugger->BreakpointActivate(id, act);
+    // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
+    return m_sharedInteropDebugger->BreakpointActivate(id, act);
 #else
     return E_FAIL;
 #endif // INTEROP_DEBUGGING
@@ -969,14 +993,15 @@ HRESULT ManagedDebugger::AllBreakpointsActivate(bool act)
     HRESULT Status1 = m_sharedBreakpoints->AllBreakpointsActivate(act);
 
 #ifdef INTEROP_DEBUGGING
-    HRESULT Status2 = m_uniqueInteropDebugger->AllBreakpointsActivate(act);
+    // Note, we don't care about m_interopDebugging here, since breakpoint setup could be start before m_interopDebugging changes with env parsing.
+    HRESULT Status2 = m_sharedInteropDebugger->AllBreakpointsActivate(act);
     return FAILED(Status1) ? Status1 : Status2;
 #else
     return Status1;
 #endif // INTEROP_DEBUGGING
 }
 
-static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModules, bool hotReload, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller)
+HRESULT ManagedDebuggerBase::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller)
 {
     HRESULT Status;
 
@@ -992,7 +1017,7 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
 
     ULONG32 methodVersion = 1;
     ULONG32 currentVersion = 1;
-    if (hotReload)
+    if (m_hotReload)
     {
         // In case current (top) code version is 1, executed in this frame method version can't be not 1.
         if (SUCCEEDED(pFunc->GetCurrentVersionNumber(&currentVersion)) && currentVersion != 1)
@@ -1025,7 +1050,7 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
 
     ULONG32 ilOffset;
     Modules::SequencePoint sp;
-    if (SUCCEEDED(pModules->GetFrameILAndSequencePoint(pFrame, ilOffset, sp)))
+    if (SUCCEEDED(m_sharedModules->GetFrameILAndSequencePoint(pFrame, ilOffset, sp)))
     {
         stackFrame.source = Source(sp.document);
         stackFrame.line = sp.startLine;
@@ -1061,11 +1086,6 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
     return S_OK;
 }
 
-HRESULT ManagedDebugger::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame)
-{
-    return InternalGetFrameLocation(pFrame, m_sharedModules.get(), m_hotReload, threadId, level, stackFrame, false);
-}
-
 static std::string GetModuleNameForFrame(ICorDebugFrame *pFrame)
 {
     ToRelease<ICorDebugFunction> pFunc;
@@ -1084,8 +1104,8 @@ static std::string GetModuleNameForFrame(ICorDebugFrame *pFrame)
     return GetBasename(to_utf8(name));
 }
 
-static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, bool interopDebugging, ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame,
-                                     unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
+HRESULT ManagedDebuggerBase::GetManagedStackTrace(ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame, unsigned maxFrames,
+                                                  std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
 {
     LogFuncEntry();
 
@@ -1102,7 +1122,7 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, bool int
 
 #ifdef INTEROP_DEBUGGING
     // In case debug session without interop, we merge "[CoreCLR Native Frame]" and "user's native frame" into "[Native Frames]".
-    const std::string FrameCLRNativeText = interopDebugging ? "[CoreCLR Native Frame]" : "[Native Frames]";
+    const std::string FrameCLRNativeText = m_interopDebugging ? "[CoreCLR Native Frame]" : "[Native Frames]";
 #else
     // CoreCLR native frame + at least one user's native frame (note, `FrameNative` case should never happen for not interop build)
     static const std::string FrameCLRNativeText = "[Native Frames]";
@@ -1161,7 +1181,7 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, bool int
             case FrameCLRManaged:
                 {
                     StackFrame stackFrame;
-                    InternalGetFrameLocation(pFrame, pModules, hotReload, threadId, FrameLevel{currentFrame}, stackFrame, hotReloadAwareCaller);
+                    GetFrameLocation(pFrame, threadId, FrameLevel{currentFrame}, stackFrame, hotReloadAwareCaller);
                     stackFrames.push_back(stackFrame);
                     stackFrames.back().addr = addr;
                     stackFrames.back().unknownFrameAddr = !addr; // Could be 0 here only in case some CoreCLR registers context issue.
@@ -1180,15 +1200,14 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, bool int
 }
 
 #ifdef INTEROP_DEBUGGING
-static HRESULT InternalGetNativeStackTrace(InteropDebugging::InteropDebugger *pInteropDebugger, ThreadId threadId, FrameLevel startFrame,
-                                           unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
+HRESULT ManagedDebuggerBase::GetNativeStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
 {
     LogFuncEntry();
 
     HRESULT Status;
     int currentFrame = -1;
 
-    Status = pInteropDebugger->UnwindNativeFrames(int(threadId), true, 0, nullptr, [&](NativeFrame &nativeFrame)
+    Status = m_sharedInteropDebugger->UnwindNativeFrames(int(threadId), true, 0, nullptr, [&](NativeFrame &nativeFrame)
     {
         currentFrame++;
 
@@ -1224,16 +1243,16 @@ HRESULT ManagedDebugger::GetStackTrace(ThreadId threadId, FrameLevel startFrame,
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     ToRelease<ICorDebugThread> pThread;
     if (SUCCEEDED(Status = m_iCorProcess->GetThread(int(threadId), &pThread)))
-        return InternalGetStackTrace(m_sharedModules.get(), m_hotReload, m_interopDebugging, pThread, threadId, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
+        return GetManagedStackTrace(pThread, threadId, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
 
 #ifdef INTEROP_DEBUGGING
     // E_INVALIDARG for ICorDebugProcess::GetThread() mean thread is not managed (can't found ICorDebugThread object that represents the thread)
     if (m_interopDebugging && Status == E_INVALIDARG)
-        return InternalGetNativeStackTrace(m_uniqueInteropDebugger.get(), threadId, startFrame, maxFrames, stackFrames, totalFrames);
+        return GetNativeStackTrace(threadId, startFrame, maxFrames, stackFrames, totalFrames);
 #endif // INTEROP_DEBUGGING
 
     return Status;
@@ -1257,7 +1276,7 @@ HRESULT ManagedDebugger::GetVariables(
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     return m_sharedVariables->GetVariables(m_iCorProcess, variablesReference, filter, start, count, variables);
 }
@@ -1268,7 +1287,7 @@ HRESULT ManagedDebugger::GetScopes(FrameId frameId, std::vector<Scope> &scopes)
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     return m_sharedVariables->GetScopes(m_iCorProcess, frameId, scopes);
 }
@@ -1279,7 +1298,7 @@ HRESULT ManagedDebugger::Evaluate(FrameId frameId, const std::string &expression
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     return m_sharedVariables->Evaluate(m_iCorProcess, frameId, expression, variable, output);
 }
@@ -1297,7 +1316,7 @@ HRESULT ManagedDebugger::SetVariable(const std::string &name, const std::string
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     return m_sharedVariables->SetVariable(m_iCorProcess, name, value, ref, output);
 }
@@ -1308,7 +1327,7 @@ HRESULT ManagedDebugger::SetExpression(FrameId frameId, const std::string &expre
 
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     return m_sharedVariables->SetExpression(m_iCorProcess, frameId, expression, evalFlags, value, output);
 }
@@ -1326,14 +1345,18 @@ void ManagedDebugger::FindFunctions(string_view pattern, unsigned limit, SearchC
     m_sharedModules->FindFunctions(pattern, limit, cb);
 }
 
-static void InternalFindVariables(ICorDebugProcess *pProcess, Variables *pVariables, ThreadId thread, FrameLevel framelevel,
-                                  string_view pattern, unsigned limit, ManagedDebugger::SearchCallback cb)
+void ManagedDebugger::FindVariables(ThreadId thread, FrameLevel framelevel, string_view pattern, unsigned limit, SearchCallback cb)
 {
     LogFuncEntry();
+
+    std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
+    if (FAILED(CheckDebugProcess()))
+        return;
+
     StackFrame frame{thread, framelevel, ""};
     std::vector<Scope> scopes;
     std::vector<Variable> variables;
-    HRESULT status = pVariables->GetScopes(pProcess, frame.id, scopes);
+    HRESULT status = m_sharedVariables->GetScopes(m_iCorProcess, frame.id, scopes);
     if (FAILED(status))
     {
         LOGW("GetScopes failed: %s", errormessage(status));
@@ -1346,7 +1369,7 @@ static void InternalFindVariables(ICorDebugProcess *pProcess, Variables *pVariab
         return;
     }
 
-    status = pVariables->GetVariables(pProcess, scopes[0].variablesReference, VariablesNamed, 0, 0, variables);
+    status = m_sharedVariables->GetVariables(m_iCorProcess, scopes[0].variablesReference, VariablesNamed, 0, 0, variables);
     if (FAILED(status))
     {
         LOGW("GetVariables failed: %s", errormessage(status));
@@ -1369,21 +1392,10 @@ static void InternalFindVariables(ICorDebugProcess *pProcess, Variables *pVariab
     }
 }
 
-void ManagedDebugger::FindVariables(ThreadId thread, FrameLevel framelevel, string_view pattern, unsigned limit, SearchCallback cb)
-{
-    LogFuncEntry();
-
-    std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
-    if (FAILED(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState)))
-        return;
-
-    InternalFindVariables(m_iCorProcess, m_sharedVariables.get(), thread, framelevel, pattern, limit, cb);
-}
-
 
-void ManagedDebugger::InputCallback(IORedirectHelper::StreamType type, span<char> text)
+void ManagedDebuggerBase::InputCallback(IORedirectHelper::StreamType type, span<char> text)
 {
-    m_sharedProtocol->EmitOutputEvent(type == IOSystem::Stderr ? OutputStdErr : OutputStdOut, {text.begin(), text.size()});
+    pProtocol->EmitOutputEvent(type == IOSystem::Stderr ? OutputStdErr : OutputStdOut, {text.begin(), text.size()});
 }
 
 
@@ -1415,7 +1427,7 @@ HRESULT ManagedDebugger::GetSourceFile(const std::string &sourcePath, char** fil
 {
     std::lock_guard<Utility::RWLock::Reader> guardProcessRWLock(m_debugProcessRWLock.reader);
     HRESULT Status;
-    IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
+    IfFailRet(CheckDebugProcess());
 
     ToRelease<ICorDebugModule> pModule;
     IfFailRet(GetModuleOfCurrentThreadCode(m_iCorProcess, int(GetLastStoppedThreadId()), &pModule));
@@ -1499,8 +1511,8 @@ static HRESULT ApplyMetadataAndILDeltas(Modules *pModules, const std::string &dl
     return S_OK;
 }
 
-HRESULT ManagedDebugger::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
-                                                     std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens)
+HRESULT ManagedDebuggerBase::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
+                                                         std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens)
 {
     HRESULT Status;
     ToRelease<ICorDebugModule> pModule;
@@ -1525,12 +1537,12 @@ HRESULT ManagedDebugger::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileN
     std::vector<BreakpointEvent> events;
     m_sharedBreakpoints->UpdateBreakpointsOnHotReload(pModule, pdbMethodTokens, events);
     for (const BreakpointEvent &event : events)
-        m_sharedProtocol->EmitBreakpointEvent(event);
+        pProtocol->EmitBreakpointEvent(event);
 
     return S_OK;
 }
 
-HRESULT ManagedDebugger::FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread)
+HRESULT ManagedDebuggerBase::FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread)
 {
     ThreadId lastStoppedId = GetLastStoppedThreadId();
     std::vector<ThreadId> threadIds;
index f79a84d10c8dbe732308376718fcb54b675b30f8..5ef62ef11aa40a9cae9cb8e7b9eb9fbaaebab2aa 100644 (file)
@@ -43,11 +43,19 @@ enum class ProcessAttachedState
     Unattached
 };
 
-class ManagedDebugger : public IDebugger
+enum StartMethod
 {
-private:
-    friend class ManagedCallback;
-    friend class CallbacksQueue;
+    StartNone,
+    StartLaunch,
+    StartAttach
+    //StartAttachForSuspendedLaunch
+};
+
+class ManagedDebuggerBase : public IDebugger
+{
+protected:
+    ManagedDebuggerBase(IProtocol *pProtocol);
+    ~ManagedDebuggerBase() override;
 
     std::mutex m_processAttachedMutex; // Note, in case m_debugProcessRWLock+m_processAttachedMutex, m_debugProcessRWLock must be locked first.
     std::condition_variable m_processAttachedCV;
@@ -62,21 +70,16 @@ private:
 
     void SetLastStoppedThread(ICorDebugThread *pThread);
     void SetLastStoppedThreadId(ThreadId threadId);
+    void InvalidateLastStoppedThreadId();
 
-    enum StartMethod
-    {
-        StartNone,
-        StartLaunch,
-        StartAttach
-        //StartAttachForSuspendedLaunch
-    } m_startMethod;
+    StartMethod m_startMethod;
     std::string m_execPath;
     std::vector<std::string> m_execArgs;
     std::string m_cwd;
     std::map<std::string, std::string> m_env;
     bool m_isConfigurationDone;
 
-    std::shared_ptr<IProtocol> m_sharedProtocol;
+    IProtocol *pProtocol;
     std::shared_ptr<Threads> m_sharedThreads;
     std::shared_ptr<Modules> m_sharedModules;
     std::shared_ptr<EvalWaiter> m_sharedEvalWaiter;
@@ -89,7 +92,7 @@ private:
     std::shared_ptr<CallbacksQueue> m_sharedCallbacksQueue;
     std::unique_ptr<ManagedCallback> m_uniqueManagedCallback;
 #ifdef INTEROP_DEBUGGING
-    std::unique_ptr<InteropDebugging::InteropDebugger> m_uniqueInteropDebugger;
+    std::shared_ptr<InteropDebugging::InteropDebugger> m_sharedInteropDebugger;
 #endif // INTEROP_DEBUGGING
 
     Utility::RWLock m_debugProcessRWLock;
@@ -108,31 +111,47 @@ private:
 
     IORedirectHelper m_ioredirect;
 
-    void InputCallback(IORedirectHelper::StreamType, span<char> text);
+    HRESULT CheckDebugProcess();
+    bool HaveDebugProcess();
 
-    static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr);
-    HRESULT Startup(IUnknown *punk, DWORD pid);
+    void InputCallback(IORedirectHelper::StreamType, span<char> text);
 
     void Cleanup();
-
     void DisableAllBreakpointsAndSteppers();
 
-    HRESULT GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame);
-
-    HRESULT RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs);
-    HRESULT AttachToProcess(DWORD pid);
-    HRESULT DetachFromProcess();
-    HRESULT TerminateProcess();
-
-    HRESULT RunIfReady();
+    HRESULT GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller = false);
+    HRESULT GetManagedStackTrace(ICorDebugThread *pThread, ThreadId threadId, FrameLevel startFrame, unsigned maxFrames,
+                                 std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller);
+#ifdef INTEROP_DEBUGGING
+    HRESULT GetNativeStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames);
+#endif // INTEROP_DEBUGGING
 
     HRESULT FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread);
     HRESULT ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates,
                                         std::string &updatedDLL, std::unordered_set<mdTypeDef> &updatedTypeTokens);
+};
+
+class ManagedDebuggerHelpers : public ManagedDebuggerBase
+{
+protected:
+    friend class ManagedCallback;
+    friend class CallbacksQueue;
+
+    ManagedDebuggerHelpers(IProtocol *pProtocol);
+
+    static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr);
+    HRESULT Startup(IUnknown *punk);
+    HRESULT RunIfReady();
+    HRESULT RunProcess(const std::string& fileExec, const std::vector<std::string>& execArgs);
+    HRESULT AttachToProcess();
+    HRESULT DetachFromProcess();
+    HRESULT TerminateProcess();
+};
 
+class ManagedDebugger final : public ManagedDebuggerHelpers
+{
 public:
-    ManagedDebugger(std::shared_ptr<IProtocol> &sharedProtocol);
-    ~ManagedDebugger() override;
+    ManagedDebugger(IProtocol *pProtocol);
 
     bool IsJustMyCode() const override { return m_justMyCode; }
     void SetJustMyCode(bool enable) override;
@@ -153,10 +172,9 @@ public:
     HRESULT Disconnect(DisconnectAction action = DisconnectDefault) override;
 
     ThreadId GetLastStoppedThreadId() override;
-    void InvalidateLastStoppedThreadId();
     HRESULT Continue(ThreadId threadId) override;
-    HRESULT Pause(ThreadId lastStoppedThread) override;
-    HRESULT GetThreads(std::vector<Thread> &threads) override;
+    HRESULT Pause(ThreadId lastStoppedThread, EventFormat eventFormat) override;
+    HRESULT GetThreads(std::vector<Thread> &threads, bool withNativeThreads = false) override;
     HRESULT UpdateLineBreakpoint(int id, int linenum, Breakpoint &breakpoint) 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 c763b6138149ccbf56b67eae43ec17ef9c7b0ce8..20b094bad6bc3be9b54efae9a82a2b0fa33cf9e4 100644 (file)
@@ -6,6 +6,9 @@
 #include "debugger/evaluator.h"
 #include "debugger/valueprint.h"
 #include "utils/torelease.h"
+#ifdef INTEROP_DEBUGGING
+#include "debugger/interop_debugging.h"
+#endif // INTEROP_DEBUGGING
 
 namespace netcoredbg
 {
@@ -56,7 +59,7 @@ std::string Threads::GetThreadName(ICorDebugProcess *pProcess, const ThreadId &u
             m_sharedEvaluator->WalkMembers(iCorThreadObject, nullptr, FrameLevel{0}, false, [&](
                 ICorDebugType *,
                 bool,
-                const std::string  &memberName,
+                const std::string &memberName,
                 Evaluator::GetValueCallback getValue,
                 Evaluator::SetterData*)
             {
@@ -95,12 +98,49 @@ HRESULT Threads::GetThreadsWithState(ICorDebugProcess *pProcess, std::vector<Thr
     for (auto &userThread : m_userThreads)
     {
         // ICorDebugThread::GetUserState not available for running thread.
-        threads.emplace_back(userThread, GetThreadName(pProcess, userThread), procRunning);
+        threads.emplace_back(userThread, GetThreadName(pProcess, userThread), procRunning == TRUE);
     }
 
     return S_OK;
 }
 
+#ifdef INTEROP_DEBUGGING
+// Caller should guarantee, that pProcess is not null.
+HRESULT Threads::GetInteropThreadsWithState(ICorDebugProcess *pProcess, InteropDebugging::InteropDebugger *pInteropDebugger, std::vector<Thread> &threads)
+{
+    HRESULT Status;
+    BOOL managedProcRunning = FALSE;
+    IfFailRet(pProcess->IsRunning(&managedProcRunning));
+
+    std::unordered_set<DWORD> managedThreads;
+    ToRelease<ICorDebugThreadEnum> iCorThreadEnum;
+    pProcess->EnumerateThreads(&iCorThreadEnum);
+    ULONG fetched = 0;
+    ToRelease<ICorDebugThread> iCorThread;
+    while (SUCCEEDED(iCorThreadEnum->Next(1, &iCorThread, &fetched)) && fetched == 1)
+    {
+        DWORD tid = 0;
+        if (SUCCEEDED(iCorThread->GetID(&tid)))
+        {
+            managedThreads.emplace(tid);
+        }
+        iCorThread.Free();
+    }
+
+    pInteropDebugger->WalkAllThreads([&](pid_t tid, bool isRunning)
+    {
+        ThreadId threadId(tid);
+
+        if (managedThreads.find((DWORD)tid) != managedThreads.end())
+            threads.emplace_back(threadId, GetThreadName(pProcess, threadId), managedProcRunning == TRUE, true);
+        else
+            threads.emplace_back(threadId, "<No name>", isRunning, false);
+    });
+
+    return S_OK;
+}
+#endif // INTEROP_DEBUGGING
+
 HRESULT Threads::GetThreadIds(std::vector<ThreadId> &threads)
 {
     std::unique_lock<Utility::RWLock::Reader> read_lock(m_userThreadsRWLock.reader);
@@ -118,4 +158,9 @@ void Threads::SetEvaluator(std::shared_ptr<Evaluator> &sharedEvaluator)
     m_sharedEvaluator = sharedEvaluator;
 }
 
+void Threads::ResetEvaluator()
+{
+    m_sharedEvaluator.reset();
+}
+
 } // namespace netcoredbg
index e24f72833db78c2b30329826bfedad297bd357f5..a091b3c433203766e5a07c34c7cc57274fd7760b 100644 (file)
@@ -16,6 +16,12 @@ namespace netcoredbg
 
 class Evaluator;
 ThreadId getThreadId(ICorDebugThread *pThread);
+#ifdef INTEROP_DEBUGGING
+namespace InteropDebugging
+{
+class InteropDebugger;
+}
+#endif // INTEROP_DEBUGGING
 
 class Threads
 {
@@ -29,9 +35,13 @@ public:
     void Add(const ThreadId &threadId);
     void Remove(const ThreadId &threadId);
     HRESULT GetThreadsWithState(ICorDebugProcess *pProcess, std::vector<Thread> &threads);
+#ifdef INTEROP_DEBUGGING
+    HRESULT GetInteropThreadsWithState(ICorDebugProcess *pProcess, InteropDebugging::InteropDebugger *pInteropDebugger, std::vector<Thread> &threads);
+#endif // INTEROP_DEBUGGING
     HRESULT GetThreadIds(std::vector<ThreadId> &threads);
     std::string GetThreadName(ICorDebugProcess *pProcess, const ThreadId &userThread);
     void SetEvaluator(std::shared_ptr<Evaluator> &sharedEvaluator);
+    void ResetEvaluator();
 };
 
 } // namespace netcoredbg
index 0f2c2dc8e7e31a857dcd74ea5d814d3621f883eb..da27b8ac51263fddfe4d8f8c7ed792c51788ea76 100644 (file)
@@ -91,7 +91,20 @@ void waitpid_t::SetPidExitedStatus(pid_t pid, int status)
 
     pidExited = true;
     pidStatus = status;
-    SetExitCode(pid, WEXITSTATUS(pidStatus));
+
+    if (WIFEXITED(pidStatus))
+    {
+        SetExitCode(pid, WEXITSTATUS(pidStatus));
+    }
+    else if (WIFSIGNALED(pidStatus))
+    {
+        LOGW("Process terminated without exiting, can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(pidStatus));
+        SetExitCode(pid, EXIT_FAILURE);
+    }
+    else
+    {
+        SetExitCode(pid, 0);
+    }
 }
 
 bool waitpid_t::GetPidExitedStatus(pid_t &pid, int &status)
@@ -113,7 +126,7 @@ hook::waitpid_t &GetWaitpid()
     return hook::waitpid;
 }
 
-// Note, we guaranty waitpid hook works only during debuggee process execution, it aimed to work only for PAL's waitpid calls interception.
+// Note, we guaranty `waitpid()` hook works only during debuggee process execution, it aimed to work only for PAL's `waitpid()` calls interception.
 extern "C" pid_t waitpid(pid_t pid, int *status, int options)
 {
 #ifdef INTEROP_DEBUGGING
@@ -162,7 +175,7 @@ extern "C" pid_t waitpid(pid_t pid, int *status, int options)
         }
         else if (WIFSIGNALED(*status))
         {
-            LOGW("Process terminated without exiting; can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(*status));
+            LOGW("Process terminated without exiting, can't get exit code. Killed by signal %d. Assuming EXIT_FAILURE.", WTERMSIG(*status));
             netcoredbg::hook::waitpid.SetExitCode(pid, EXIT_FAILURE);
         }
     }
@@ -170,6 +183,12 @@ extern "C" pid_t waitpid(pid_t pid, int *status, int options)
     return pidWaitRetval;
 }
 
+// Note, liblttng-ust may call `wait()` at CoreCLR global/static initialization at dlopen() (debugger managed part related).
+extern "C" pid_t wait(int *status)
+{
+    return waitpid(-1, status, 0);
+}
+
 } // namespace netcoredbg
 
 #endif // FEATURE_PAL
index 523790f3ae57122cdfa573111a22a59f7d7fadc9..77b9f8d623c6a7e09e8143efa9178f54392e8285 100644 (file)
@@ -80,8 +80,8 @@ public:
     virtual HRESULT Disconnect(DisconnectAction action = DisconnectDefault) = 0;
     virtual ThreadId GetLastStoppedThreadId() = 0;
     virtual HRESULT Continue(ThreadId threadId) = 0;
-    virtual HRESULT Pause(ThreadId lastStoppedThread) = 0;
-    virtual HRESULT GetThreads(std::vector<Thread> &threads) = 0;
+    virtual HRESULT Pause(ThreadId lastStoppedThread, EventFormat eventFormat) = 0;
+    virtual HRESULT GetThreads(std::vector<Thread> &threads, bool withNativeThreads = false) = 0;
     virtual HRESULT UpdateLineBreakpoint(int id, int linenum, Breakpoint &breakpoint) = 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 288476c4ba41400afeffeaab7eef88b33d23f085..5023e0352acbc48a3db1a4465a40876558a71842 100644 (file)
@@ -144,7 +144,13 @@ struct Thread
     std::string name;
     bool running;
 
-    Thread(ThreadId id, const std::string& name, bool running) : id(id), name(name), running(running) {}
+#ifdef INTEROP_DEBUGGING
+    bool managed ;// exposed for CLI protocols
+    Thread(ThreadId id_, const std::string &name_, bool running_) : id(id_), name(name_), running(running_), managed(true) {}
+    Thread(ThreadId id_, const std::string &name_, bool running_, bool managed_) : id(id_), name(name_), running(running_), managed(managed_) {}
+#else
+    Thread(ThreadId id_, const std::string &name_, bool running_) : id(id_), name(name_), running(running_) {}
+#endif // INTEROP_DEBUGGING
 };
 
 struct Source
@@ -297,6 +303,10 @@ struct StoppedEvent
     StackFrame frame; // exposed for MI and CLI protocols
     Breakpoint breakpoint; // exposed for MI and CLI protocols
 
+#ifdef INTEROP_DEBUGGING
+    std::string signal_name; // exposed for CLI protocols
+#endif // INTEROP_DEBUGGING
+
     StoppedEvent(StopReason reason, ThreadId threadId = ThreadId::Invalid)
         :reason(reason), threadId(threadId), allThreadsStopped(true)
     {}
@@ -319,16 +329,20 @@ struct ExitedEvent
 
 enum ThreadReason
 {
-    ThreadStarted,
-    ThreadExited
+    ManagedThreadStarted,
+    ManagedThreadExited,
+    NativeThreadAttached,
+    NativeThreadStarted,
+    NativeThreadExited
 };
 
 struct ThreadEvent
 {
     ThreadReason reason;
     ThreadId threadId;
+    bool interopDebugging;
 
-    ThreadEvent(ThreadReason reason, ThreadId threadId) : reason(reason), threadId(threadId) {}
+    ThreadEvent(ThreadReason reason_, ThreadId id_, bool interop_) : reason(reason_), threadId(id_), interopDebugging(interop_) {}
 };
 
 enum OutputCategory
@@ -526,4 +540,10 @@ struct ExceptionBreakpoint
     }
 };
 
+enum class EventFormat
+{
+    Default,
+    CLI
+};
+
 } // namespace netcoredbg
index d112689a531ba76a16ac4418ac4257e544a0442b..64573d916b70ff8eb17dac650dcbdf74c89d77bc 100644 (file)
@@ -464,7 +464,7 @@ int
     std::shared_ptr<IDebugger> debugger;
     try
     {
-        debugger.reset(new ManagedDebugger(protocol));
+        debugger.reset(new ManagedDebugger(protocol.get()));
     }
     catch (const std::exception &e)
     {
index 2f70a4219364d1b6256247cc7b0dfaf1d4f21831..f8bfdbc8abe730ce10affe0cb8d9c17ae6bd39b9 100644 (file)
@@ -283,12 +283,30 @@ static SymbolStatus LoadDebuginfo(const std::string &libLoadName, InteropLibrari
 
 static bool IsCoreCLRLibrary(const std::string &fullName)
 {
+    // Could be part of SDK, but will be never part of debuggee process:
+    // libdbgshim.so      // 2.1 - 6.0
+    // libmscordaccore.so // 2.1+
+    // libmscordbi.so     // 2.1+
+    // libsos.so          // 2.1
+    // libsosplugin.so    // 2.1
+
     static const std::vector<std::string> clrLibs{
-        "libclrjit.so",
-        "libcoreclr.so",
-        "libcoreclrtraceptprovider.so",
-        "libhostpolicy.so",
-        "libclrgc.so"
+        "libclrjit.so",                                      // 2.1+
+        "libcoreclr.so",                                     // 2.1+
+        "libcoreclrtraceptprovider.so",                      // 2.1+
+        "libhostpolicy.so",                                  // 2.1+
+        "System.Globalization.Native.so",                    // 2.1 - 3.1
+        "System.Security.Cryptography.Native.OpenSsl.so",    // 2.1 - 3.1
+        "System.IO.Compression.Native.so",                   // 2.1 - 3.1
+        "System.Net.Security.Native.so",                     // 2.1 - 3.1
+        "System.Native.so",                                  // 2.1 - 3.1
+        "System.Net.Http.Native.so",                         // 2.1 - 3.1
+        "libSystem.Native.so",                               // 5.0+
+        "libSystem.IO.Compression.Native.so",                // 5.0+
+        "libSystem.Net.Security.Native.so",                  // 5.0+
+        "libSystem.Security.Cryptography.Native.OpenSsl.so", // 5.0+
+        "libSystem.Globalization.Native.so",                 // 6.0+
+        "libclrgc.so",                                       // 7.0+
     };
 
     for (auto &clrLibName : clrLibs)
@@ -312,6 +330,7 @@ void InteropLibraries::AddLibrary(const std::string &libLoadName, const std::str
 
     LibraryInfo &info = m_librariesInfo[startAddr];
     info.fullName = fullName;
+    info.fullLoadName = libLoadName;
     info.libEndAddr = endAddr;
     symbolStatus = LoadDebuginfo(libLoadName, info);
     info.isCoreCLR = IsCoreCLRLibrary(fullName);
@@ -624,6 +643,18 @@ void InteropLibraries::FindDataForAddr(std::uintptr_t addr, std::string &libName
     });
 }
 
+bool InteropLibraries::IsUserDebuggingCode(std::uintptr_t addr)
+{
+    bool isUserCode = false;
+    FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+    {
+        if (!info.isCoreCLR && info.dw != nullptr)
+            isUserCode = true;
+    });
+
+    return isUserCode;
+}
+
 bool InteropLibraries::IsThumbCode(std::uintptr_t addr)
 {
 #if DEBUGGER_UNIX_ARM
@@ -659,5 +690,57 @@ bool InteropLibraries::IsThumbCode(std::uintptr_t libStartAddr, LibraryInfo &inf
     return false;
 }
 
+bool InteropLibraries::FindDataForNotClrAddr(std::uintptr_t addr, std::string &libLoadName, std::string &procName)
+{
+    bool isUserCode = true;
+    FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+    {
+        if (info.isCoreCLR)
+        {
+            isUserCode = false;
+            return;
+        }
+
+        libLoadName = GetBasename(info.fullLoadName);
+        // Remove version after ".so" (if load name have it)
+        static std::string versionDetect(".so.");
+        constexpr size_t versionDetectSize = 4;
+        if (libLoadName.size() > versionDetectSize)
+        {
+            size_t i = libLoadName.rfind(versionDetect);
+            if (i != std::string::npos)
+                libLoadName = libLoadName.substr(0, i + 3);
+        }
+
+        if (info.dw != nullptr)
+        {
+            std::string fullSourcePath;
+            int lineNum;
+            FindDataForAddrInDebugInfo(info.dw.get(), addr - startAddr, procName, fullSourcePath, lineNum);
+            return;
+        }
+
+        if (!info.proceduresDataValid)
+            CollectProcDataFromElf(startAddr, info);
+
+        if (info.proceduresData.empty() ||
+            addr >= info.proceduresData.rbegin()->second.endAddr)
+            return;
+
+        auto upper_bound = info.proceduresData.upper_bound(addr);
+        if (upper_bound != info.proceduresData.begin())
+        {
+            auto closest_lower = std::prev(upper_bound);
+            if (closest_lower->first <= addr && addr < closest_lower->second.endAddr)
+            {
+                if (!DemangleCXXABI(closest_lower->second.procName.c_str(), procName))
+                    procName = closest_lower->second.procName + "()";
+            }
+        }
+    });
+
+    return isUserCode;
+}
+
 } // namespace InteropDebugging
 } // namespace netcoredbg
index 8e562c922c769b6c82a3bcf580c0bb3aa672e8e1..61be08658caecb3141796f773b009a6863c26152 100644 (file)
@@ -34,6 +34,7 @@ public:
     struct LibraryInfo
     {
         std::string fullName;
+        std::string fullLoadName;
         std::uintptr_t libEndAddr; // have same logic as STL `end()` iterator - "first address after"
         // debuginfo related
         std::unique_ptr<elf::elf> ef;
@@ -70,6 +71,8 @@ public:
 
     void FindDataForAddr(std::uintptr_t addr, std::string &libName, std::uintptr_t &libStartAddr, std::string &procName,
                          std::uintptr_t &procStartAddr, std::string &fullSourcePath, int &lineNum);
+    bool FindDataForNotClrAddr(std::uintptr_t addr, std::string &libLoadName, std::string &procName);
+    bool IsUserDebuggingCode(std::uintptr_t addr);
     bool IsThumbCode(std::uintptr_t addr);
 
 private:
index 4a8528f0777d28c521fed46d106dc0be0210ecd0..b039eea771768700dcc43e43043d1136b383d425 100644 (file)
@@ -60,7 +60,7 @@ static HRESULT GetUpdateHandlerTypesForModule(IMetaDataImport *pMD, std::vector<
 // Comma (,)         Precedes the Assembly name.
 // Period (.)        Denotes namespace identifiers.
 // Plus sign (+)     Precedes a nested class.
-static void ParceTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
+static void ParseTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
 {
     std::string::size_type genericDelimiterPos = fullName.find("`");
     std::string fullTypeName;
@@ -109,7 +109,7 @@ HRESULT ModulesAppUpdate::AddUpdateHandlerTypesForModule(ICorDebugModule *pModul
     {
         std::string mainTypeName;
         std::vector<std::string> nestedClasses;
-        ParceTypeName(entry, mainTypeName, nestedClasses);
+        ParseTypeName(entry, mainTypeName, nestedClasses);
 
         // Resolve main type part.
         mdTypeDef typeToken = mdTypeDefNil;
index feeb818152adacad3c88bd63105bdc21dc2b78b6..fe020e83b5e54f1fb17f15060de003de8e090200 100644 (file)
@@ -799,6 +799,10 @@ void CLIProtocol::EmitStoppedEvent(const StoppedEvent &event)
 
     std::string frameLocation;
     PrintFrameLocation(event.frame, frameLocation);
+    if (!event.frame.moduleOrLibName.empty())
+    {
+        frameLocation = event.frame.moduleOrLibName + "` " + frameLocation;
+    }
     m_sourcePath = event.frame.source.path;
     m_sourceLine = event.frame.line - m_listSize / 2;
     m_stoppedAt = event.frame.line;
@@ -835,8 +839,18 @@ void CLIProtocol::EmitStoppedEvent(const StoppedEvent &event)
         }
         case StopPause:
         {
-            printf("\nstopped, reason: interrupted, thread id: %i, stopped threads: all, frame={%s}\n",
-                  int(event.threadId), frameLocation.c_str());
+#ifdef INTEROP_DEBUGGING
+            if (!event.signal_name.empty())
+            {
+                printf("\nstopped, reason: interrupted, signal-name=\"%s\", thread id: %i, stopped threads: all, frame={%s}\n",
+                       event.signal_name.c_str(), int(event.threadId), frameLocation.c_str());
+            }
+            else
+#endif INTEROP_DEBUGGING
+            {
+                printf("\nstopped, reason: interrupted, thread id: %i, stopped threads: all, frame={%s}\n",
+                       int(event.threadId), frameLocation.c_str());
+            }
             break;
         }
         default:
@@ -903,12 +917,23 @@ void CLIProtocol::EmitThreadEvent(const ThreadEvent &event)
     const char *reasonText = "";
     switch(event.reason)
     {
-        case ThreadStarted:
-            reasonText = "thread created";
+        case ManagedThreadStarted:
+            reasonText = event.interopDebugging ? "managed thread created" : "thread created";
+            break;
+        case ManagedThreadExited:
+            reasonText = event.interopDebugging ? "managed thread exited" : "thread exited";
+            break;
+        case NativeThreadAttached:
+            reasonText = "native thread attached";
             break;
-        case ThreadExited:
-            reasonText = "thread exited";
+        case NativeThreadStarted:
+            reasonText = "native thread created";
             break;
+        case NativeThreadExited:
+            reasonText = "native thread exited";
+            break;
+        default:
+            return;
     }
     printf("\n%s, id: %i\n", reasonText, int(event.threadId));
 }
@@ -981,11 +1006,53 @@ HRESULT CLIProtocol::doCommand<CommandTag::Backtrace>(const std::vector<std::str
     }
 
     std::vector<std::string> args = args_orig;
-    ThreadId threadId{ ProtocolUtils::GetIntArg(args, "--thread", int(tid)) };
+    ProtocolUtils::StripArgs(args);
     int lowFrame = 0;
     int highFrame = FrameLevel::MaxFrameLevel;
-    ProtocolUtils::StripArgs(args);
     ProtocolUtils::GetIndices(args, lowFrame, highFrame);
+
+    // command "bt all"
+    if (!args.empty() && args[0] == "all")
+    {
+        std::vector<Thread> threads;
+        if (FAILED(m_sharedDebugger->GetThreads(threads, true)))
+        {
+            output = "No threads.";
+            return E_FAIL;
+        }
+
+        std::ostringstream ss;
+        int number = 1;
+
+        for (const auto &thread : threads)
+        {
+            std::string stackTrace;
+            if (SUCCEEDED(PrintFrames(thread.id, stackTrace, FrameLevel{lowFrame}, FrameLevel{highFrame})))
+            {
+                ss << "\nThread " << number << ", id=\"" << int(thread.id)
+                << "\", name=\"" << thread.name << "\", state=\""
+                << (thread.running ? "running" : "stopped") << "\"";
+#ifdef INTEROP_DEBUGGING
+                ss << ", type=\"" << (thread.managed ? "managed" : "native") << "\"";
+#endif // INTEROP_DEBUGGING
+                number++;
+
+                ss << "\n" << stackTrace;
+            }
+        }
+
+        if (ss.str().empty())
+        {
+            output = "No stacktraces.";
+            return E_FAIL;
+        }
+
+        output = ss.str();
+        return S_OK;
+    }
+
+    // command "bt [--thread TID]"
+    ThreadId threadId{ ProtocolUtils::GetIntArg(args, "--thread", int(tid)) };
     return PrintFrames(threadId, output, FrameLevel{lowFrame}, FrameLevel{highFrame});
 }
 
@@ -1327,25 +1394,26 @@ HRESULT CLIProtocol::doCommand<CommandTag::InfoThreads>(const std::vector<std::s
     }
 
     std::vector<Thread> threads;
-    if (FAILED(m_sharedDebugger->GetThreads(threads)))
+    if (FAILED(m_sharedDebugger->GetThreads(threads, true)))
     {
         output = "No threads.";
         return E_FAIL;
     }
     std::ostringstream ss;
 
-    ss << "\nthreads=[\n";
+    ss << "Threads:";
 
-    const char *sep = "";
+    int number = 1;
     for (const Thread& thread : threads)
     {
-        ss << sep << "{id=\"" << int(thread.id)
+        ss << "\n" << number << ": id=\"" << int(thread.id)
            << "\", name=\"" << thread.name << "\", state=\""
-           << (thread.running ? "running" : "stopped") << "\"}";
-        sep = ",\n";
+           << (thread.running ? "running" : "stopped") << "\"";
+#ifdef INTEROP_DEBUGGING
+        ss << ", type=\"" << (thread.managed ? "managed" : "native") << "\"";
+#endif // INTEROP_DEBUGGING
+        number++;
     }
-
-    ss << "]";
     output = ss.str();
     return S_OK;
 }
@@ -1459,7 +1527,7 @@ HRESULT CLIProtocol::doCommand<CommandTag::Interrupt>(const std::vector<std::str
     }
 
     HRESULT Status;
-    IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
+    IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI));
     output = "^stopped";
     return S_OK;
 }
@@ -1731,7 +1799,7 @@ HRESULT CLIProtocol::doCommand<CommandTag::Attach>(const std::vector<std::string
     lock.unlock();
 
     IfFailRet(m_sharedDebugger->ConfigurationDone());
-    IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads));
+    IfFailRet(m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI));
     return S_OK;
 }
 
@@ -2442,7 +2510,7 @@ void CLIProtocol::Pause()
     if (m_processStatus == Running)
     {
         lock.unlock();
-        m_sharedDebugger->Pause(ThreadId::AllThreads);
+        m_sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::CLI);
     }
 }
 
index 1d0d9f7873311af3744756dbb85c9c237a88ebaa..8a2ac2084c5d29563f2deb8036c1f170c480ba02 100644 (file)
@@ -465,12 +465,14 @@ void MIProtocol::EmitThreadEvent(const ThreadEvent &event)
     const char *reasonText = "";
     switch(event.reason)
     {
-        case ThreadStarted:
+        case ManagedThreadStarted:
             reasonText = "thread-created";
             break;
-        case ThreadExited:
+        case ManagedThreadExited:
             reasonText = "thread-exited";
             break;
+        default:
+            return;
     }
     MIProtocol::Printf("=%s,id=\"%i\"\n", reasonText, int(event.threadId));
 }
@@ -567,7 +569,7 @@ static HRESULT HandleCommand(std::shared_ptr<IDebugger> &sharedDebugger, Breakpo
     } },
     { "exec-interrupt", [&](const std::vector<std::string> &, std::string &output){
         HRESULT Status;
-        IfFailRet(sharedDebugger->Pause(ThreadId::AllThreads));
+        IfFailRet(sharedDebugger->Pause(ThreadId::AllThreads, EventFormat::Default));
         output = "^done";
         return S_OK;
     } },
index 450ee28b834f4a4e7f71380380db0b589e295b17..5e6d6d9abae067bf395237ce947389ccb5631893 100644 (file)
@@ -221,12 +221,14 @@ void VSCodeProtocol::EmitThreadEvent(const ThreadEvent &event)
 
     switch(event.reason)
     {
-        case ThreadStarted:
+        case ManagedThreadStarted:
             body["reason"] = "started";
             break;
-        case ThreadExited:
+        case ManagedThreadExited:
             body["reason"] = "exited";
             break;
+        default:
+            return;
     }
 
     body["threadId"] = int(event.threadId);
@@ -641,7 +643,7 @@ static HRESULT HandleCommand(std::shared_ptr<IDebugger> &sharedDebugger, std::st
     { "pause", [&](const json &arguments, json &body){
         ThreadId threadId{int(arguments.at("threadId"))};
         body["threadId"] = int(threadId);
-        return sharedDebugger->Pause(threadId);
+        return sharedDebugger->Pause(threadId, EventFormat::Default);
     } },
     { "next", [&](const json &arguments, json &body){
         return sharedDebugger->StepCommand(ThreadId{int(arguments.at("threadId"))}, IDebugger::StepType::STEP_OVER);