Add stack unwind with native code support.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 29 Mar 2023 18:09:13 +0000 (21:09 +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)
25 files changed:
packaging/netcoredbg.spec
src/CMakeLists.txt
src/debugger/breakpoint_interop_rendezvous.cpp
src/debugger/breakpoint_interop_rendezvous.h
src/debugger/breakpoints.h
src/debugger/breakpoints_interop.cpp
src/debugger/callbacksqueue.cpp
src/debugger/frames.cpp
src/debugger/frames.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_ptrace_helpers.cpp
src/debugger/interop_unwind.cpp [new file with mode: 0644]
src/debugger/interop_unwind.h [new file with mode: 0644]
src/debugger/manageddebugger.cpp
src/interfaces/types.h
src/metadata/interop_libraries.cpp
src/metadata/interop_libraries.h
src/protocols/cliprotocol.cpp
src/protocols/miprotocol.cpp
src/protocols/protocol_utils.cpp
src/protocols/protocol_utils.h
src/protocols/vscodeprotocol.cpp

index 3070799885b5dec3a1b49530e429a0abee6f7d85..21434ef2df2ab0e529c39b422c094df9590fd6c3 100644 (file)
@@ -29,6 +29,7 @@ BuildRequires: dotnet-build-tools
 BuildRequires: unzip
 BuildRequires: corefx-managed
 BuildRequires: libdlog-devel
+BuildRequires: libunwind-devel
 Requires: coreclr
 
 %{!?build_type:%define build_type Release}
index c98f0ce13b5601c876c0cf21ec0e24ec10b53b66..00c852a76fe815fefaa3acb9ebf5b71ac01af777 100644 (file)
@@ -163,6 +163,7 @@ if (INTEROP_DEBUGGING)
             debugger/interop_debugging.cpp
             debugger/interop_mem_helpers.cpp
             debugger/interop_ptrace_helpers.cpp
+            debugger/interop_unwind.cpp
             debugger/sigaction.cpp
             metadata/interop_libraries.cpp
         )
@@ -199,6 +200,10 @@ if (NCDB_DOTNET_STARTUP_HOOK)
 endif (NCDB_DOTNET_STARTUP_HOOK)
 
 if (INTEROP_DEBUGGING)
+    if (NOT CLR_CMAKE_PLATFORM_LINUX)
+        message(FATAL_ERROR "Interop debugging feature not implemented for this OS.")
+    endif()
+
     add_definitions(-DINTEROP_DEBUGGING)
 
     if (CLR_CMAKE_PLATFORM_UNIX_AMD64)
@@ -209,10 +214,46 @@ if (INTEROP_DEBUGGING)
         add_definitions(-DDEBUGGER_UNIX_ARM64)
     elseif (CLR_CMAKE_PLATFORM_UNIX_ARM)
         add_definitions(-DDEBUGGER_UNIX_ARM)
+    else()
+        message(FATAL_ERROR "Interop debugging feature not implemented for this CPU architecture.")
     endif()
 
     target_link_libraries(netcoredbg elf++ dwarf++)
     include_directories(${PROJECT_SOURCE_DIR}/third_party/libelfin/elf ${PROJECT_SOURCE_DIR}/third_party/libelfin/dwarf)
+
+
+    # libunwind
+    find_path(LIBUNWIND_INCLUDE_DIRS NAMES libunwind.h)
+    find_library(LIBUNWIND_LIBRARY unwind)
+    find_library(LIBUNWIND_PTRACE_LIBRARY unwind-ptrace)
+    if (CLR_CMAKE_PLATFORM_UNIX_AMD64)
+        find_library(LIBUNWIND_LIBRARY_ARCH unwind-x86_64)
+    elseif (CLR_CMAKE_PLATFORM_UNIX_X86)
+        find_library(LIBUNWIND_LIBRARY_ARCH unwind-x86)
+    elseif (CLR_CMAKE_PLATFORM_UNIX_ARM64)
+        find_library(LIBUNWIND_LIBRARY_ARCH unwind-aarch64)
+    elseif (CLR_CMAKE_PLATFORM_UNIX_ARM)
+        find_library(LIBUNWIND_LIBRARY_ARCH unwind-arm)
+    endif()
+
+    if (${LIBUNWIND_INCLUDE_DIRS} STREQUAL "LIBUNWIND_INCLUDE_DIRS-NOTFOUND")
+        message(FATAL_ERROR "Libunwind headers not found")
+    endif()
+
+    if (${LIBUNWIND_LIBRARY} STREQUAL "LIBUNWIND_LIBRARY-NOTFOUND")
+        message(FATAL_ERROR "Libunwind lib not found")
+    endif()
+
+    if (${LIBUNWIND_PTRACE_LIBRARY} STREQUAL "LIBUNWIND_PTRACE_LIBRARY-NOTFOUND")
+        message(FATAL_ERROR "Libunwind-ptrace libs not found")
+    endif()
+
+    if (${LIBUNWIND_LIBRARY_ARCH} STREQUAL "LIBUNWIND_LIBRARY_ARCH-NOTFOUND")
+        message(FATAL_ERROR "Libunwind libs for this arch not found")
+    endif()
+
+    target_link_libraries(netcoredbg ${LIBUNWIND_LIBRARY} ${LIBUNWIND_PTRACE_LIBRARY} ${LIBUNWIND_LIBRARY_ARCH})
+    include_directories(${LIBUNWIND_INCLUDE_DIRS})
 endif (INTEROP_DEBUGGING)
 
 if (CLR_CMAKE_TARGET_TIZEN_LINUX)
index 8c25c306f2e2dc3f85ccc5950614c6584452dadd..10d415a6e9361865b5951f156a7e8cb51e870921 100644 (file)
@@ -34,7 +34,7 @@ bool InteropRendezvousBreakpoint::SetupRendezvousBrk(pid_t pid, LoadLibCallback
         std::uintptr_t endAddr = GetLibEndAddrAndRealName(pid, 0, realLibName, startAddr);\r
         if (endAddr == 0 || realLibName.empty()) // ignore in case error or linux-vdso.so\r
             return;\r
-        m_loadLibCB(pid, realLibName, startAddr, endAddr);\r
+        m_loadLibCB(pid, libName, realLibName, startAddr, endAddr);\r
         m_libsNameToRealNameMap.emplace(libName, std::move(realLibName));\r
     });\r
 \r
@@ -70,7 +70,7 @@ void InteropRendezvousBreakpoint::ChangeRendezvousState(pid_t TGID, pid_t pid)
                 std::uintptr_t endAddr = GetLibEndAddrAndRealName(TGID, pid, realLibName, startAddr);\r
                 if (endAddr == 0 || realLibName.empty()) // ignore in case error or linux-vdso.so\r
                     return;\r
-                m_loadLibCB(pid, realLibName, startAddr, endAddr);\r
+                m_loadLibCB(pid, libName, realLibName, startAddr, endAddr);\r
                 m_libsNameToRealNameMap.emplace(libName, std::move(realLibName));\r
             });\r
         }\r
index 395706801d167d527a5d31af40779b934f8b2719..4b77c5573714530f6fcc4a4a5b3c74a6ad9e03b4 100644 (file)
@@ -23,7 +23,7 @@ class InteropRendezvousBreakpoint
 {\r
 public:\r
 \r
-    typedef std::function<void(pid_t, const std::string&, std::uintptr_t, std::uintptr_t)> LoadLibCallback;\r
+    typedef std::function<void(pid_t, const std::string&, const std::string&, std::uintptr_t, std::uintptr_t)> LoadLibCallback;\r
     typedef std::function<void(const std::string&)> UnloadLibCallback;\r
     typedef std::function<bool(std::uintptr_t)> IsThumbCodeCallback;\r
 \r
index 7fbcc372850149f7f21c27c7928efadb2db4a4fb..07371209f01a58008b50dd36e5aa70a550cfa4ca 100644 (file)
@@ -88,7 +88,7 @@ public:
     HRESULT InteropAllBreakpointsActivate(pid_t pid, bool act, std::function<void()> StopAllThreads, std::function<void(std::uintptr_t)> FixAllThreads);
     HRESULT InteropBreakpointActivate(pid_t pid, uint32_t id, bool act, std::function<void()> StopAllThreads, std::function<void(std::uintptr_t)> FixAllThreads);
     // In case of error - return `false`.
-    typedef std::function<void(pid_t, const std::string&, std::uintptr_t, std::uintptr_t)> LoadLibCallback;
+    typedef std::function<void(pid_t, const std::string&, const std::string&, std::uintptr_t, std::uintptr_t)> LoadLibCallback;
     typedef std::function<void(const std::string&)> UnloadLibCallback;
     typedef std::function<bool(std::uintptr_t)> IsThumbCodeCallback;
     bool InteropSetupRendezvousBrk(pid_t pid, LoadLibCallback loadLibCB, UnloadLibCallback unloadLibCB, IsThumbCodeCallback isThumbCode, int &err_code);
index 9e31fc75712062fda0b5a209b0b01d45154ff13a..6a4b61df65b8e5fd8ed7c1af1a595b09b3a6c9e0 100644 (file)
@@ -104,12 +104,13 @@ void InteropBreakpoints::RemoveAllAtDetach(pid_t pid)
             if (errno != 0)\r
             {\r
                 LOGE("Ptrace peekdata error: %s", strerror(errno));\r
+                continue;\r
             }\r
             word_t restoredData = RestoredOpcode(brkData, entry.second.m_savedData);\r
 \r
             if (async_ptrace(PTRACE_POKEDATA, pid, (void*)entry.first, (void*)restoredData) == -1)\r
             {\r
-                LOGW("Ptrace pokedata error: %s\n", strerror(errno));\r
+                LOGE("Ptrace pokedata error: %s\n", strerror(errno));\r
             }\r
         }\r
     }\r
@@ -134,7 +135,8 @@ void InteropBreakpoints::StepOverBrk(pid_t pid, std::uintptr_t brkAddr)
     if (find == m_currentBreakpointsInMemory.end())\r
         return;\r
 \r
-    InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData);\r
+    if (!InteropDebugging::StepOverBrk(pid, brkAddr, find->second.m_savedData))\r
+        std::abort(); // Fatal error, we already logged all data about this error.\r
 }\r
 \r
 bool InteropBreakpoints::StepPrevToBrk(pid_t pid, std::uintptr_t brkAddr)\r
@@ -153,12 +155,18 @@ bool InteropBreakpoints::StepPrevToBrk(pid_t pid, std::uintptr_t brkAddr)
     iov.iov_base = &regs;\r
     iov.iov_len = sizeof(user_regs_struct);\r
     if (async_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)\r
-        LOGW("Ptrace getregset error: %s\n", strerror(errno));\r
+    {\r
+        LOGE("Ptrace getregset error: %s\n", strerror(errno));\r
+        std::abort(); // Fatal error, we already logged all data about this error.\r
+    }\r
 \r
     SetPrevBrkPC(regs);\r
 \r
     if (async_ptrace(PTRACE_SETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)\r
-        LOGW("Ptrace setregset error: %s\n", strerror(errno));\r
+    {\r
+        LOGE("Ptrace setregset error: %s\n", strerror(errno));\r
+        std::abort(); // Fatal error, we already logged all data about this error.\r
+    }\r
 \r
     return true;\r
 }\r
index eb083cb9226dc28da890813fff07312dc7cf0f2a..fa9424ab71dd09da49d2e74e050618a9e60ce5a9 100644 (file)
@@ -462,8 +462,12 @@ bool CallbacksQueue::CallbacksWorkerInteropBreakpoint(pid_t pid, std::uintptr_t
 
     m_debugger.SetLastStoppedThreadId(ThreadId(pid));
 
-    event.frame.source = event.breakpoint.source;
-    event.frame.line = event.breakpoint.line;
+    if (FAILED(m_debugger.m_uniqueInteropDebugger->GetFrameForAddr(brkAddr, event.frame)))
+    {
+        event.frame.source = event.breakpoint.source;
+        event.frame.line = event.breakpoint.line;
+    }
+
     m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
     m_debugger.m_ioredirect.async_cancel();
     return true;
@@ -473,15 +477,15 @@ HRESULT CallbacksQueue::AddInteropCallbackToQueue(std::function<void()> callback
 {
     std::unique_lock<std::mutex> lock(m_callbacksMutex);
 
-    callback();
+    callback(); // Caller could add entries into m_callbacksQueue (this is why m_callbacksMutex cover this call).
 
     if (!m_callbacksQueue.empty())
     {
-        // NOTE in case `m_stopEventInProcess` is `true` process have "stopped" status for sure, but could already execute some eval (that mean managed process is running).
+        // 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 managed callbacks we are safe with managed process continue, since as soon as `m_callbacksQueue` is not empty,
-            // no one will continue
             BOOL procRunning = FALSE;
             m_debugger.m_debugProcessRWLock.reader.lock();
             if (m_debugger.m_iCorProcess && SUCCEEDED(m_debugger.m_iCorProcess->IsRunning(&procRunning)) && procRunning == TRUE)
index 744f4e735775b334778a9992120d1d7b79684de5..2151a43ff067bdbf0339c6343255a7ba8605ad37 100644 (file)
 // See the LICENSE file in the project root for more information.
 
 #include <sstream>
-#include <algorithm>
-#include <limits>
 #include "debugger/frames.h"
 #include "metadata/typeprinter.h"
 #include "utils/platform.h"
 #include "utils/logger.h"
 #include "utils/torelease.h"
+#ifdef INTEROP_DEBUGGING
+#include "debugger/interop_debugging.h"
+#if DEBUGGER_UNIX_ARM
+#include "debugger/interop_ptrace_helpers.h"
+#include <sys/uio.h> // iovec
+#include <elf.h> // NT_PRSTATUS
+#endif
+#endif // INTEROP_DEBUGGING
+
 
 namespace netcoredbg
 {
 
-uint64_t GetFrameAddr(ICorDebugFrame *pFrame)
+#ifdef INTEROP_DEBUGGING
+namespace
+{
+    std::mutex g_mutexInteropDebugger;
+    InteropDebugging::InteropDebugger *g_pInteropDebugger = nullptr;
+} // unnamed namespace
+#endif // INTEROP_DEBUGGING
+
+static std::uintptr_t GetIP(CONTEXT *context)
 {
-    CORDB_ADDRESS startAddr = 0;
-    CORDB_ADDRESS endAddr = 0;
-    pFrame->GetStackRange(&startAddr, &endAddr);
-    return startAddr;
+#if defined(_TARGET_AMD64_)
+    return (std::uintptr_t)context->Rip;
+#elif defined(_TARGET_X86_)
+    return (std::uintptr_t)context->Eip;
+#elif defined(_TARGET_ARM_)
+    return (std::uintptr_t)context->Pc;
+#elif defined(_TARGET_ARM64_)
+    return (std::uintptr_t)context->Pc;
+#else
+#error "Unsupported platform"
+#endif
 }
 
-static uint64_t GetSP(CONTEXT *context)
+static std::uintptr_t GetSP(CONTEXT *context)
 {
 #if defined(_TARGET_AMD64_)
-    return (uint64_t)(size_t)context->Rsp;
+    return (std::uintptr_t)context->Rsp;
 #elif defined(_TARGET_X86_)
-    return (uint64_t)(size_t)context->Esp;
+    return (std::uintptr_t)context->Esp;
 #elif defined(_TARGET_ARM_)
-    return (uint64_t)(size_t)context->Sp;
+    return (std::uintptr_t)context->Sp;
 #elif defined(_TARGET_ARM64_)
-    return (uint64_t)(size_t)context->Sp;
+    return (std::uintptr_t)context->Sp;
 #else
 #error "Unsupported platform"
 #endif
 }
 
-HRESULT UnwindNativeFrames(ICorDebugThread *pThread, uint64_t startValue, uint64_t endValue, std::vector<NativeFrame> &frames)
+static std::uintptr_t GetFP(CONTEXT *context)
 {
-    return S_OK;
+#if defined(_TARGET_AMD64_)
+    return (std::uintptr_t)context->Rbp;
+#elif defined(_TARGET_X86_)
+    return (std::uintptr_t)context->Ebp;
+#elif defined(_TARGET_ARM_)
+    return (std::uintptr_t)context->R11;
+#elif defined(_TARGET_ARM64_)
+    return (std::uintptr_t)context->Fp;
+#else
+#error "Unsupported platform"
+#endif
 }
 
-HRESULT StitchInternalFrames(
-    ICorDebugThread *pThread,
-    const std::vector< ToRelease<ICorDebugFrame> > &internalFrameCache,
-    const std::vector<NativeFrame> &nativeFrames,
-    WalkFramesCallback cb)
+static void SetFP(CONTEXT *context, std::uintptr_t value)
 {
-    HRESULT Status;
+#if defined(_TARGET_AMD64_)
+    context->Rbp = value;
+#elif defined(_TARGET_X86_)
+    context->Ebp = value;
+#elif defined(_TARGET_ARM_)
+    context->R11 = value;
+#elif defined(_TARGET_ARM64_)
+    context->Fp = value;
+#else
+#error "Unsupported platform"
+#endif
+}
 
-    struct FrameIndex {
-        size_t index;
-        bool internal;
-        uint64_t addr;
+#ifdef INTEROP_DEBUGGING
+#if DEBUGGER_UNIX_ARM
+static HRESULT EmptyContextForFrame(WalkFramesCallback cb)
+{
+    NativeFrame result;
+    result.unknownFrameAddr = true;
+    result.procName = "[Native Frame(s), unwind failed - CoreCLR don't provide registers context]";
+    return cb(FrameNative, result.addr, nullptr, &result);
+}
 
-        FrameIndex(size_t ind, const ToRelease<ICorDebugFrame> &frame)
-            : index(ind), internal(true), addr(GetFrameAddr(frame.GetPtr())) {}
-        FrameIndex(size_t ind, const NativeFrame &frame)
-            : index(ind), internal(false), addr(frame.addr) {}
-    };
+static HRESULT EmptyContextForTopFrame(ICorDebugThread *pThread, WalkFramesCallback cb)
+{
+    std::lock_guard<std::mutex> lock(g_mutexInteropDebugger);
 
-    std::vector<FrameIndex> frames;
+    if (g_pInteropDebugger == nullptr)
+        return EmptyContextForFrame(cb);
 
-    for (size_t i = 0; i < nativeFrames.size(); i++) frames.emplace_back(i, nativeFrames[i]);
-    for (size_t i = 0; i < internalFrameCache.size(); i++) frames.emplace_back(i, internalFrameCache[i]);
+    DWORD threadId = 0;
+    StackFrame frame;
+    if (SUCCEEDED(pThread->GetID(&threadId)))
+    {
+        user_regs_struct regs;
+        iovec iov;
+        iov.iov_base = &regs;
+        iov.iov_len = sizeof(user_regs_struct);
+        if (InteropDebugging::async_ptrace(PTRACE_GETREGSET, threadId, (void*)NT_PRSTATUS, &iov) == -1)
+        {
+            LOGW("Ptrace getregset error: %s\n", strerror(errno));
+        }
+        else
+        {
+            const static int REG_PC = 15;
+            if (SUCCEEDED(g_pInteropDebugger->GetFrameForAddr(regs.uregs[REG_PC], frame)))
+            {
+                NativeFrame result;
+                result.addr = regs.uregs[REG_PC];
+                result.libName = frame.moduleOrLibName;
+                result.procName = frame.methodName;
+                result.fullSourcePath = frame.source.path;
+                result.lineNum = frame.line;
+                HRESULT Status;
+                IfFailRet(cb(FrameNative, result.addr, nullptr, &result));
+            }
+        }
+    }
+
+    return EmptyContextForFrame(cb);
+}
+#endif // DEBUGGER_UNIX_ARM
+#endif // INTEROP_DEBUGGING
+
+static HRESULT UnwindNativeFrames(ICorDebugThread *pThread, bool firstFrame, CONTEXT *pStartContext, CONTEXT *pEndContext, WalkFramesCallback cb)
+{
+#ifdef INTEROP_DEBUGGING
+    std::lock_guard<std::mutex> lock(g_mutexInteropDebugger);
 
-    std::sort(frames.begin( ), frames.end(), [](const FrameIndex& lhs, const FrameIndex& rhs)
+    if (g_pInteropDebugger == nullptr)
+        return S_OK; // In case debug session without interop, we merge "[CoreCLR Native Frame]" and "user's native frame" into "[Native Frames]".
+
+    DWORD threadId = 0;
+    if (FAILED(pThread->GetID(&threadId)))
     {
-        return lhs.addr < rhs.addr;
+        NativeFrame result;
+        result.addr = pStartContext ? GetIP(pStartContext) : 0;
+        result.unknownFrameAddr = !result.addr;
+        result.procName = "[Native Frame(s)]";
+        return cb(FrameNative, result.addr, nullptr, &result);
+    }
+
+    std::uintptr_t endAddr = pEndContext ? GetIP(pEndContext) : 0;
+    return g_pInteropDebugger->UnwindNativeFrames(threadId, firstFrame, endAddr, pStartContext, [&](NativeFrame &nativeFrame)
+    {
+        return cb(FrameNative, nativeFrame.addr, nullptr, &nativeFrame);
     });
+#else
+    // In case not interop build we merge "CoreCLR native frame" and "user's native frame" into "[Native Frames]".
+    return S_OK;
+#endif // INTEROP_DEBUGGING
+}
 
-    for (auto &fr : frames)
+#ifdef INTEROP_DEBUGGING
+static HRESULT UnwindInlinedTopNativeFrames(ICorDebugThread *pThread, ICorDebugFunction *pFunction, CONTEXT &currentCtx, WalkFramesCallback cb)
+{
+    ToRelease<ICorDebugFunction2> iCorFunc2;
+    BOOL bJustMyCode;
+    if (SUCCEEDED(pFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID *)&iCorFunc2)) &&
+        SUCCEEDED(iCorFunc2->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(iCorFunc2->SetJMCStatus(TRUE)))
     {
-        if (fr.internal)
-            IfFailRet(cb(FrameCLRInternal, internalFrameCache[fr.index].GetPtr(), nullptr, nullptr));
-        else
-            IfFailRet(cb(FrameNative, nullptr, const_cast<NativeFrame*>(&nativeFrames[fr.index]), nullptr));
+        // Revert back JMC status if need.
+        if (bJustMyCode != TRUE)
+            iCorFunc2->SetJMCStatus(bJustMyCode);
+
+        return S_OK; // not optimized code for sure, ignore native top frames.
     }
 
+    // Prevent native top frame unwinding in case thread stopped by managed exception.
+    ToRelease<ICorDebugValue> iCorExceptionValue;
+    if (SUCCEEDED(pThread->GetCurrentException(&iCorExceptionValue)) && iCorExceptionValue != nullptr)
+        return S_OK;
+
+    std::lock_guard<std::mutex> lock(g_mutexInteropDebugger);
+
+    DWORD threadId = 0;
+    if (g_pInteropDebugger == nullptr || FAILED(pThread->GetID(&threadId)))
+        return S_OK;
+
+    HRESULT Status;
+    static const bool firstFrame = true;
+#if DEBUGGER_UNIX_ARM
+    // Linux arm32 CoreCLR have issue:
+    // - ICorDebugStackWalk::GetContext return empty registers context for all frames;
+    if (GetIP(&currentCtx) == 0)
+        IfFailRet(EmptyContextForTopFrame(pThread, cb));
+    else
+#endif // DEBUGGER_UNIX_ARM
+    IfFailRet(g_pInteropDebugger->UnwindNativeFrames(threadId, firstFrame, GetIP(&currentCtx), nullptr, [&](NativeFrame &nativeFrame)
+    {
+        return cb(FrameNative, nativeFrame.addr, nullptr, &nativeFrame);
+    }));
+
     return S_OK;
 }
+#endif // INTEROP_DEBUGGING
 
 // From https://github.com/SymbolSource/Microsoft.Samples.Debugging/blob/master/src/debugger/mdbgeng/FrameFactory.cs
 HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb)
 {
     HRESULT Status;
 
-    ToRelease<ICorDebugThread3> pThread3;
-    ToRelease<ICorDebugStackWalk> pStackWalk;
-
-    IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3));
-    IfFailRet(pThread3->CreateStackWalk(&pStackWalk));
-
-    std::vector< ToRelease<ICorDebugFrame> > iFrameCache;
-    std::vector<NativeFrame> nFrames;
+    ToRelease<ICorDebugThread3> iCorThread3;
+    IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &iCorThread3));
+    ToRelease<ICorDebugStackWalk> iCorStackWalk;
+    IfFailRet(iCorThread3->CreateStackWalk(&iCorStackWalk));
 
     static const ULONG32 ctxFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
     CONTEXT ctxUnmanagedChain;
     bool ctxUnmanagedChainValid = false;
     CONTEXT currentCtx;
-    bool currentCtxValid = false;
+    ULONG32 contextSize;
     memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT));
     memset(&currentCtx, 0, sizeof(CONTEXT));
 
+    // TODO ICorDebugInternalFrame support for more info about CoreCLR related internal routine and call cb() with `FrameCLRInternal`
+    // ICorDebugThread3::GetActiveInternalFrames
+    // https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugthread3-getactiveinternalframes-method
+
     int level = -1;
+    static const bool firstFrame = true;
 
-    for (Status = S_OK; ; Status = pStackWalk->Next())
+    for (Status = S_OK; ; Status = iCorStackWalk->Next())
     {
-        level++;
-
         if (Status == CORDBG_S_AT_END_OF_STACK)
             break;
 
+        level++;
+
         IfFailRet(Status);
 
-        ToRelease<ICorDebugFrame> pFrame;
-        IfFailRet(pStackWalk->GetFrame(&pFrame));
+        ToRelease<ICorDebugFrame> iCorFrame;
+        IfFailRet(iCorStackWalk->GetFrame(&iCorFrame));
         if (Status == S_FALSE) // S_FALSE - The current frame is a native stack frame.
         {
             // We've hit a native frame, we need to store the CONTEXT
             memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT));
-            ULONG32 contextSize;
-            IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &ctxUnmanagedChain));
+            IfFailRet(iCorStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &ctxUnmanagedChain));
             ctxUnmanagedChainValid = true;
-            // We need to invalidate the currentCtx since it won't be valid on the next loop iteration
-            memset(&currentCtx, 0, sizeof(CONTEXT));
-            currentCtxValid = false;
             continue;
         }
 
         // At this point (Status == S_OK).
-        // Accordingly to CoreCLR sources, S_OK could be with nulled pFrame, that must be skipped.
-        if (pFrame == NULL)
+        // 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;
 
-        // If we get a RuntimeUnwindableFrame, then the stackwalker is stopped at a native
-        // stack frame, but requires special unwinding help from the runtime.
-        ToRelease<ICorDebugRuntimeUnwindableFrame> pRuntimeUnwindableFrame;
-        Status = pFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &pRuntimeUnwindableFrame);
-        if (SUCCEEDED(Status))
-        {
+        // If we get a RuntimeUnwindableFrame, then the stackwalker is also stopped at a native
+        // stack frame, but it's a native stack frame which requires special unwinding help from
+        // the runtime. When a debugger gets a RuntimeUnwindableFrame, it should use the runtime
+        // to unwind, but it has to do inspection on its own. It can call
+        // ICorDebugStackWalk::GetContext() to retrieve the context of the native stack frame.
+        ToRelease<ICorDebugRuntimeUnwindableFrame> iCorRuntimeUnwindableFrame;
+        if (SUCCEEDED(iCorFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &iCorRuntimeUnwindableFrame)))
             continue;
-        }
 
-        // We need to check for an internal frame.
-        // If the internal frame happens to come after the last managed frame, any call to GetContext() will assert
-        ToRelease<ICorDebugInternalFrame> pInternalFrame;
-        if (FAILED(pFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID*) &pInternalFrame)))
+        // We need to store the CONTEXT when we're at a managed frame.
+        memset(&currentCtx, 0, sizeof(CONTEXT));
+        IfFailRet(iCorStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &currentCtx));
+        // Note, we don't change top managed frame FP in case we don't have SP (for example, registers context related issue)
+        // or CoreCLR was able to restore it. This case could happens only with "managed" top frame (`GetFrame()` return `S_OK`),
+        // where real top frame is native (for example, optimized managed code with inlined pinvoke or CoreCLR native frame).
+        if (level == 0 && GetSP(&currentCtx) != 0 && GetFP(&currentCtx) == 0)
         {
-            // We need to store the CONTEXT when we're at a managed frame.
-            // If there's an internal frame after this, then we'll use this CONTEXT
-            memset(&currentCtx, 0, sizeof(CONTEXT));
-            ULONG32 contextSize;
-            IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &currentCtx));
-            currentCtxValid = true;
-        }
-        else if (ctxUnmanagedChainValid)
-        {
-            // We need to check if this internal frame could have been sandwiched between native frames,
-            // this will be the case if ctxUnmanagedChain is valid
-
-            // We need to store ALL internal frames until we hit the next managed frame
-            iFrameCache.emplace_back(std::move(pFrame));
-            continue;
-        }
-        // Else we'll use the 'stored' currentCtx if we're at an InternalFrame
-
-        uint64_t pEndVal = std::numeric_limits<uint64_t>::max();
-        if (currentCtxValid)
-        {
-            pEndVal = GetSP(&currentCtx);
+            SetFP(&currentCtx, GetSP(&currentCtx));
+            IfFailRet(iCorStackWalk->SetContext(SET_CONTEXT_FLAG_UNWIND_FRAME, sizeof(CONTEXT), (BYTE*) &currentCtx));
         }
 
         // Check if we have native frames to unwind
         if (ctxUnmanagedChainValid)
-            IfFailRet(UnwindNativeFrames(pThread, GetSP(&ctxUnmanagedChain), pEndVal, nFrames));
-        IfFailRet(StitchInternalFrames(pThread, iFrameCache, nFrames, cb));
-
-        // Clear out the CONTEXT and native frame cache
-        memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT));
-        ctxUnmanagedChainValid = false;
-        nFrames.clear();
-        iFrameCache.clear();
+        {
+#ifdef INTEROP_DEBUGGING
+#if DEBUGGER_UNIX_ARM
+            // Linux arm32 CoreCLR have issues:
+            // - ICorDebugStackWalk::Next have first stack frame "native", ICorDebugStackWalk::GetFrame return S_FALSE;
+            // - ICorDebugStackWalk::GetContext return empty registers context for all frames;
+            if (GetIP(&ctxUnmanagedChain) == 0 || GetIP(&currentCtx) == 0)
+            {
+                if (level == 1)
+                    IfFailRet(EmptyContextForTopFrame(pThread, cb));
+                else
+                    IfFailRet(EmptyContextForFrame(cb));
+            }
+            else
+#endif // DEBUGGER_UNIX_ARM
+#endif // INTEROP_DEBUGGING
+            IfFailRet(UnwindNativeFrames(pThread, !firstFrame, &ctxUnmanagedChain, &currentCtx, cb));
+            level++;
+            // Clear out the CONTEXT
+            memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT));
+            ctxUnmanagedChainValid = false;
+        }
 
         // Return the managed frame
-        ToRelease<ICorDebugFunction> pFunction;
-        Status = pFrame->GetFunction(&pFunction);
-        if (SUCCEEDED(Status))
+        ToRelease<ICorDebugFunction> iCorFunction;
+        if (SUCCEEDED(iCorFrame->GetFunction(&iCorFunction)))
         {
-            IfFailRet(cb(FrameCLRManaged, pFrame, nullptr, pFunction));
+#ifdef INTEROP_DEBUGGING
+            // 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.
+            if (level == 0 && FAILED(Status = UnwindInlinedTopNativeFrames(pThread, iCorFunction.GetPtr(), currentCtx, cb)))
+                return Status;
+#endif // INTEROP_DEBUGGING
+
+            IfFailRet(cb(FrameCLRManaged, GetIP(&currentCtx), iCorFrame, nullptr));
             continue;
         }
-        // If we cannot get managed function then return internal or native frame
-        FrameType frameType = pInternalFrame ? FrameCLRInternal : FrameCLRNative;
 
-        ToRelease<ICorDebugNativeFrame> pNativeFrame;
-        HRESULT hrNativeFrame = pFrame->QueryInterface(IID_ICorDebugNativeFrame, (LPVOID*) &pNativeFrame);
-        if (FAILED(hrNativeFrame))
+        ToRelease<ICorDebugNativeFrame> iCorNativeFrame;
+        if (FAILED(iCorFrame->QueryInterface(IID_ICorDebugNativeFrame, (LPVOID*) &iCorNativeFrame)))
         {
-            IfFailRet(cb(FrameUnknown, pFrame, nullptr, nullptr));
+            IfFailRet(cb(FrameUnknown, GetIP(&currentCtx), iCorFrame, nullptr));
             continue;
         }
-        // If the first frame is either internal or native then we might be in a call to unmanaged code
+        // If the first frame is CoreCLR native frame then we might be in a call to unmanaged code.
+        // Note, in case start unwinding from native code we get CoreCLR native frame first, not some native frame at the top,
+        // since CoreCLR debug API don't track native code execution and don't really "see" native code at the beginning of unwinding.
         if (level == 0)
         {
-            IfFailRet(UnwindNativeFrames(pThread, 0, GetFrameAddr(pFrame), nFrames));
-            IfFailRet(StitchInternalFrames(pThread, {}, nFrames, cb));
-            nFrames.clear();
+#ifdef INTEROP_DEBUGGING
+#if DEBUGGER_UNIX_ARM
+            // Linux arm32 CoreCLR have issue:
+            // - ICorDebugStackWalk::GetContext return empty registers context for all frames;
+            if (GetIP(&currentCtx) == 0)
+                IfFailRet(EmptyContextForTopFrame(pThread, cb));
+            else
+#endif // DEBUGGER_UNIX_ARM
+#endif // INTEROP_DEBUGGING
+            IfFailRet(UnwindNativeFrames(pThread, firstFrame, nullptr, &currentCtx, cb));
         }
-        IfFailRet(cb(frameType, pFrame, nullptr, nullptr));
+        IfFailRet(cb(FrameCLRNative, GetIP(&currentCtx), iCorFrame, nullptr));
     }
 
     // We may have native frames at the end of the stack
-    uint64_t pEndVal = std::numeric_limits<uint64_t>::max();
     if (ctxUnmanagedChainValid)
-        IfFailRet(UnwindNativeFrames(pThread, GetSP(&ctxUnmanagedChain), pEndVal, nFrames));
-    IfFailRet(StitchInternalFrames(pThread, iFrameCache, nFrames, cb));
-
-    // After stitching frames they should be cleared like this:
-    // nFrames.clear();
-    // memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT));
-    // ctxUnmanagedChainValid = false;
+    {
+        if (level == 0) // in case this is first and last frame - unwind all
+            IfFailRet(UnwindNativeFrames(pThread, firstFrame, nullptr, nullptr, cb));
+        else
+        {
+#ifdef INTEROP_DEBUGGING
+#if DEBUGGER_UNIX_ARM
+            // Linux arm32 CoreCLR have issue:
+            // - ICorDebugStackWalk::GetContext return empty registers context for all frames;
+            if (GetIP(&ctxUnmanagedChain) == 0)
+                IfFailRet(EmptyContextForFrame(cb));
+            else
+#endif // DEBUGGER_UNIX_ARM
+#endif // INTEROP_DEBUGGING
+            IfFailRet(UnwindNativeFrames(pThread, !firstFrame, &ctxUnmanagedChain, nullptr, cb));
+        }
+    }
 
     return S_OK;
 }
@@ -238,9 +388,9 @@ HRESULT GetFrameAt(ICorDebugThread *pThread, FrameLevel level, ICorDebugFrame **
 
     WalkFrames(pThread, [&](
         FrameType frameType,
+        std::uintptr_t addr,
         ICorDebugFrame *pFrame,
-        NativeFrame *pNative,
-        ICorDebugFunction *pFunction)
+        NativeFrame *pNative)
     {
         currentFrame++;
 
@@ -278,4 +428,22 @@ const char *GetInternalTypeName(CorDebugInternalFrameType frameType)
     }
 }
 
+#ifdef INTEROP_DEBUGGING
+
+void InitNativeFramesUnwind(InteropDebugging::InteropDebugger *pInteropDebugger)
+{
+    g_mutexInteropDebugger.lock();
+    g_pInteropDebugger = pInteropDebugger;
+    g_mutexInteropDebugger.unlock();
+}
+
+void ShutdownNativeFramesUnwind()
+{
+    g_mutexInteropDebugger.lock();
+    g_pInteropDebugger = nullptr;
+    g_mutexInteropDebugger.unlock();
+}
+
+#endif // INTEROP_DEBUGGING
+
 } // namespace netcoredbg
index 3e55ef4517885ef6fa44d38a17322eee8c818190..f209e8a53e8ca5788e910e17750ae6441cae8a03 100644 (file)
@@ -23,22 +23,30 @@ enum FrameType
 
 struct NativeFrame
 {
-    uint64_t addr;
-    std::string symbol;
-    std::string file;
-    std::string fullname;
-    int linenum;
-    int tid;
-    NativeFrame() : addr(0), linenum(0), tid(0) {}
+    std::uintptr_t addr = 0;
+    bool unknownFrameAddr = false;
+    std::string libName;
+    std::string procName;
+    std::string fullSourcePath;
+    int lineNum = 0;
 };
 
-typedef std::function<HRESULT(FrameType,ICorDebugFrame*,NativeFrame*,ICorDebugFunction*)> WalkFramesCallback;
+typedef std::function<HRESULT(FrameType,std::uintptr_t,ICorDebugFrame*,NativeFrame*)> WalkFramesCallback;
 
 struct Thread;
 
 HRESULT GetFrameAt(ICorDebugThread *pThread, FrameLevel level, ICorDebugFrame **ppFrame);
-uint64_t GetFrameAddr(ICorDebugFrame *pFrame);
 const char *GetInternalTypeName(CorDebugInternalFrameType frameType);
 HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb);
 
+#ifdef INTEROP_DEBUGGING
+namespace InteropDebugging
+{
+class InteropDebugger;
+}
+
+void InitNativeFramesUnwind(InteropDebugging::InteropDebugger *pInteropDebugger);
+void ShutdownNativeFramesUnwind();
+#endif // INTEROP_DEBUGGING
+
 } // namespace netcoredbg
index daa4d6dcaa08796316e218c168b6d0baab635cf8..5de940dcb0ee7e0b4f47d76b9a657db3db157083 100644 (file)
@@ -124,7 +124,7 @@ word_t RestoredOpcode(word_t dataWithBrk, word_t restoreData)
 #endif
 }
 
-void StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
+bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
 {
     // 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
@@ -138,36 +138,65 @@ void StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData)
         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));
+        {
+            LOGE("Ptrace getregset error: %s\n", strerror(errno));
+            return false;
+        }
 
         SetPrevBrkPC(regs);
 
         if (async_ptrace(PTRACE_SETREGSET, pid, (void*)NT_PRSTATUS, &iov) == -1)
-            LOGW("Ptrace setregset error: %s\n", strerror(errno));
+        {
+            LOGE("Ptrace setregset error: %s\n", strerror(errno));
+            return false;
+        }
     }
 
     errno = 0;
     word_t brkData = async_ptrace(PTRACE_PEEKDATA, pid, (void*)addr, nullptr);
     if (errno != 0)
-        LOGW("Ptrace peekdata error: %s", strerror(errno));
+    {
+        LOGE("Ptrace peekdata error: %s", strerror(errno));
+        return false;
+    }
 
     restoreData = RestoredOpcode(brkData, restoreData);
 
     // restore data
     if (async_ptrace(PTRACE_POKEDATA, pid, (void*)addr, (void*)restoreData) == -1)
-        LOGW("Ptrace pokedata error: %s\n", strerror(errno));
+    {
+        LOGE("Ptrace pokedata error: %s\n", strerror(errno));
+        return false;
+    }
 
     // single step
     if (async_ptrace(PTRACE_SINGLESTEP, pid, nullptr, nullptr) == -1)
-        LOGW("Ptrace singlestep error: %s\n", strerror(errno));
+    {
+        LOGE("Ptrace singlestep error: %s\n", strerror(errno));
+        return false;
+    }
 
     int wait_status;
-    GetWaitpid()(pid, &wait_status, __WALL);
-    // TODO check that we get SIGTRAP + TRAP_TRACE here before continue
+    if (GetWaitpid()(pid, &wait_status, 0) == -1)
+    {
+        LOGE("Waitpid error: %s\n", strerror(errno));
+        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)
-        LOGW("Ptrace pokedata error: %s\n", strerror(errno));
+    {
+        LOGE("Ptrace pokedata error: %s\n", strerror(errno));
+        return false;
+    }
+
+    return true;
 }
 
 } // namespace InteropDebugging
index 9b33fd6f4e18d1457ae94f7d3e546750173e32ce..c144dec68562733491fe41e435c1e61cbe566715 100644 (file)
@@ -17,7 +17,7 @@ namespace InteropDebugging
     std::uintptr_t GetBrkAddrByPC(const user_regs_struct &regs);
     word_t EncodeBrkOpcode(word_t data, bool thumbCode);
     word_t RestoredOpcode(word_t dataWithBrk, word_t restoreData);
-    void StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData);
+    bool StepOverBrk(pid_t pid, std::uintptr_t addr, word_t restoreData);
 
 } // namespace InteropDebugging
 } // namespace netcoredbg
index c6b31319f801aca00b78033b1fc38d1375931839..a11f533b9f7d2431eaf03e4b255ff6450c618989 100644 (file)
 
 #include <vector>
 #include <algorithm>
+#include <sstream>
 #include "interfaces/iprotocol.h"
 #include "debugger/breakpoints.h"
 #include "debugger/waitpid.h"
 #include "debugger/callbacksqueue.h"
 #include "debugger/evalwaiter.h"
+#include "debugger/interop_unwind.h"
 #include "metadata/interop_libraries.h"
+#include "debugger/frames.h"
 #include "utils/logger.h"
 #include "elf++.h"
 #include "dwarf++.h"
@@ -40,6 +43,12 @@ namespace netcoredbg
 namespace InteropDebugging
 {
 
+namespace
+{
+    constexpr pid_t g_waitForAllThreads = -1;
+} // unnamed namespace
+
+
 InteropDebugger::InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
                                  std::shared_ptr<Breakpoints> &sharedBreakpoints,
                                  std::shared_ptr<EvalWaiter> &sharedEvalWaiter) :
@@ -50,10 +59,18 @@ InteropDebugger::InteropDebugger(std::shared_ptr<IProtocol> &sharedProtocol,
 {}
 
 // NOTE caller must care about m_waitpidMutex.
-void InteropDebugger::WaitAllThreadsStop()
+void InteropDebugger::WaitThreadStop(pid_t stoppedPid)
 {
-    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;
+    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;
+    }
+    else
+    {
+        if (m_TIDs[stoppedPid].stat != thread_stat_e::running)
+            return;
+    }
 
     // At this point all threads must be stopped or interrupted, we need parse all signals now.
     pid_t pid = 0;
@@ -73,23 +90,48 @@ void InteropDebugger::WaitAllThreadsStop()
                 GetWaitpid().SetPidExitedStatus(pid, status);
             }
 
+            if (stoppedPid == pid)
+                break;
+
             continue;
         }
 
+        unsigned stop_signal = WSTOPSIG(status);
+
+        if (stop_signal == (unsigned)SIGRTMIN)
+        {
+            // Ignore CoreCLR INJECT_ACTIVATION_SIGNAL here, we can't guarantee it will delivered only once and intime.
+            // Note, CoreCLR will be Ok in case INJECT_ACTIVATION_SIGNAL will be never delivered and rely on the GCPOLL mechanism, see
+            // https://github.com/dotnet/runtime/blob/8f517afeda93e031b3a797a0eb9e6643adcece2f/src/coreclr/vm/threadsuspend.cpp#L3407-L3425
+            siginfo_t siginfo;
+            bool sendByItself = false;
+            if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &siginfo) == -1)
+                LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+            else
+                sendByItself = (siginfo.si_pid == m_TGID);
+
+            if (sendByItself)
+                stop_signal = 0;
+        }
+
         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 = WSTOPSIG(status);
+        m_TIDs[pid].stop_signal = stop_signal;
         m_TIDs[pid].event = (unsigned)status >> 16;
         m_changedThreads.emplace_back(pid);
 
-        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())
+        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()))
+        {
             break;
+        }
     }
 }
 
 // NOTE caller must care about m_waitpidMutex.
 void InteropDebugger::StopAndDetach(pid_t tgid)
 {
-    WaitAllThreadsStop();
+    WaitThreadStop(g_waitForAllThreads);
 
     // TODO Reset threads status stopped by native steppers
     // TODO Remove all native steppers
@@ -103,7 +145,10 @@ void InteropDebugger::StopAndDetach(pid_t tgid)
         iov.iov_base = &regs;
         iov.iov_len = sizeof(user_regs_struct);
         if (async_ptrace(PTRACE_GETREGSET, entry.first, (void*)NT_PRSTATUS, &iov) == -1)
+        {
             LOGW("Ptrace getregset error: %s\n", strerror(errno));
+            continue; // Will hope, this thread didn't stopped at breakpoint.
+        }
 
         if (m_sharedBreakpoints->InteropStepPrevToBrk(entry.first, GetBrkAddrByPC(regs)))
         {
@@ -171,6 +216,7 @@ void InteropDebugger::Shutdown()
 
     lock.unlock();
 
+    ShutdownNativeFramesUnwind();
     async_ptrace_shutdown();
 }
 
@@ -242,7 +288,7 @@ static HRESULT SeizeAndInterruptAllThreads(std::unordered_map<pid_t, thread_stat
     return S_OK;
 }
 
-void InteropDebugger::LoadLib(pid_t pid, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr)
+void InteropDebugger::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
 
@@ -252,7 +298,7 @@ void InteropDebugger::LoadLib(pid_t pid, const std::string &realLibName, std::ui
     module.path = realLibName;
     module.baseAddress = startAddr;
     module.size = endAddr - startAddr;
-    m_uniqueInteropLibraries->AddLibrary(module.path, startAddr, endAddr, module.symbolStatus);
+    m_uniqueInteropLibraries->AddLibrary(libLoadName, realLibName, startAddr, endAddr, module.symbolStatus);
 
     if (module.symbolStatus == SymbolStatus::SymbolsLoaded)
     {
@@ -361,7 +407,7 @@ void InteropDebugger::ParseThreadsChanges()
                                     if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
                                     {
                                         StopAllRunningThreads();
-                                        WaitAllThreadsStop();
+                                        WaitThreadStop(g_waitForAllThreads);
                                         m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
                                         m_TIDs[pid].stop_signal = 0;
                                         break;
@@ -415,33 +461,82 @@ void InteropDebugger::ParseThreadsChanges()
     m_changedThreads.clear();
 }
 
-// NOTE mutexes lock sequence must be CallbacksQueue->InteropDebugger.
-void InteropDebugger::ParseThreadsEvents()
+// Separate thread for callbacks setup in order to make waitpid and CoreCLR debug API work in the same time.
+void InteropDebugger::CallbackEventWorker()
 {
-    m_sharedCallbacksQueue->AddInteropCallbackToQueue([&]()
+    std::unique_lock<std::mutex> lock(m_callbackEventMutex);
+    m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that thread init complete
+
+    while (true)
     {
-        std::lock_guard<std::mutex> lock(m_waitpidMutex);
+        m_callbackEventCV.wait(lock); // wait for request from ParseThreadsEvents() or exit request from WaitpidWorker()
 
-        if (m_eventedThreads.empty())
-            return;
+        if (m_callbackEventNeedExit)
+            break;
 
-        for (const auto &pid : m_eventedThreads)
+        m_sharedCallbacksQueue->AddInteropCallbackToQueue([&]()
         {
-            switch (m_TIDs[pid].stat)
+            for (const auto &entry : m_callbackEvents)
             {
-            case thread_stat_e::stopped_breakpoint_event_detected:
-                m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, pid, m_TIDs[pid].stop_event_data.addr);
-                m_TIDs[pid].stat = thread_stat_e::stopped_breakpoint_event_in_progress;
-                break;
+                switch (entry.stat)
+                {
+                case thread_stat_e::stopped_breakpoint_event_detected:
+                    m_sharedCallbacksQueue->EmplaceBackInterop(CallbackQueueCall::InteropBreakpoint, entry.pid, entry.stop_event_data.addr);
+                    {
+                        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;
 
-            default:
-                LOGW("This event type is not stop event: %d", m_TIDs[pid].stat);
-                break;
+                default:
+                    LOGW("This event type is not stop event: %d", entry.stat);
+                    break;
+                }
             }
+
+            m_callbackEvents.clear();
+        });
+    }
+
+    m_callbackEventCV.notify_one(); // notify WaitpidWorker(), that execution exit from CallbackEventWorker()
+}
+
+void InteropDebugger::ParseThreadsEvents()
+{
+    std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+    if (m_eventedThreads.empty())
+        return;
+
+    // NOTE we can't setup callbacks in waitpid thread, since CoreCLR could use native breakpoints in managed threads, some managed threads
+    //      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.
+    if (!m_callbackEventMutex.try_lock())
+        return;
+
+    for (const auto &pid : m_eventedThreads)
+    {
+        switch (m_TIDs[pid].stat)
+        {
+        case thread_stat_e::stopped_breakpoint_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;
         }
+    }
 
-        m_eventedThreads.clear();
-    });
+    m_eventedThreads.clear();
+
+    if (!m_callbackEvents.empty())
+        m_callbackEventCV.notify_one();
+
+    m_callbackEventMutex.unlock();
 }
 
 void InteropDebugger::ContinueAllThreadsWithEvents()
@@ -478,12 +573,20 @@ void InteropDebugger::ContinueAllThreadsWithEvents()
 
 void InteropDebugger::WaitpidWorker()
 {
+    std::unique_lock<std::mutex> lockEvent(m_callbackEventMutex);
+    m_callbackEventNeedExit = false;
+    m_callbackEventWorker = std::thread(&InteropDebugger::CallbackEventWorker, this);
+    m_callbackEventCV.wait(lockEvent); // wait for init complete from CallbackEventWorker()
+    lockEvent.unlock();
+
     std::unique_lock<std::mutex> lock(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()
 
     pid_t pid = 0;
     int status = 0;
+    std::unordered_map<pid_t, int> injectTIDs; // CoreCLR's INJECT_ACTIVATION_SIGNAL related.
+    static const int injectSignalResetCountdown = 5; // 5 * 10 ms
 
     while (!m_TIDs.empty())
     {
@@ -497,12 +600,27 @@ void InteropDebugger::WaitpidWorker()
 
         if (pid == 0) // No changes (see `waitpid` man for WNOHANG).
         {
+            // INJECT_ACTIVATION_SIGNAL could be delivered with some delay and we could have "no signals" return from `waitpid`.
+            // In the same time, injectTIDs should be reseted, since after some time next signal also could be INJECT_ACTIVATION_SIGNAL.
+            // Note, CoreCLR will be Ok in case INJECT_ACTIVATION_SIGNAL will be never delivered and rely on the GCPOLL mechanism, see
+            // https://github.com/dotnet/runtime/blob/8f517afeda93e031b3a797a0eb9e6643adcece2f/src/coreclr/vm/threadsuspend.cpp#L3407-L3425
+            for (auto it = injectTIDs.begin(); it != injectTIDs.end();)
+            {
+                if (it->second == 0)
+                    it = injectTIDs.erase(it);
+                else
+                {
+                    it->second--;
+                    ++it;
+                }
+            }
+
             ParseThreadsChanges();
 
             lock.unlock();
             // NOTE mutexes lock sequence must be CallbacksQueue->InteropDebugger.
             ParseThreadsEvents();
-            usleep(50*1000); // sleep 50 ms before next waitpid call
+            usleep(10*1000); // sleep 10 ms before next waitpid call
             lock.lock();
 
             if (m_waitpidNeedExit)
@@ -526,12 +644,52 @@ void InteropDebugger::WaitpidWorker()
             continue;
         }
 
+        unsigned stop_signal = WSTOPSIG(status);
+
+        if (stop_signal == (unsigned)SIGRTMIN)
+        {
+            // CoreCLR could send a lot of INJECT_ACTIVATION_SIGNALs for thread between our `waitpid` calls,
+            // in order to start code execution on thread in time. Make sure only one was really send and ignore others.
+            // Note, CoreCLR don't expect that bunch of signals will return, it need start related code only once.
+
+            // Note, CoreCLR at INJECT_ACTIVATION_SIGNAL will (from CoreCLR cources): "Only accept activations from the current process".
+            siginfo_t siginfo;
+            bool sendByItself = false;
+            if (async_ptrace(PTRACE_GETSIGINFO, pid, nullptr, &siginfo) == -1)
+                LOGW("Ptrace getsiginfo error: %s\n", strerror(errno));
+            else
+                sendByItself = (siginfo.si_pid == m_TGID);
+
+            if (sendByItself)
+            {
+                auto find = injectTIDs.find(pid);
+                if (find != injectTIDs.end())
+                {
+                    stop_signal = 0;
+                    find->second = injectSignalResetCountdown;
+                }
+                else
+                    injectTIDs.emplace(std::make_pair(pid, injectSignalResetCountdown));
+
+                if (async_ptrace(PTRACE_CONT, pid, nullptr, (void*)((word_t)stop_signal)) == -1)
+                    LOGW("Ptrace cont error: %s", strerror(errno));
+                // No need change `m_TIDs[pid].stat` and `m_TIDs[pid].stop_signal` here.
+                continue;
+            }
+        }
+
         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 = WSTOPSIG(status);
+        m_TIDs[pid].stop_signal = stop_signal;
         m_TIDs[pid].event = (unsigned)status >> 16;
         m_changedThreads.emplace_back(pid);
     }
 
+    lockEvent.lock();
+    m_callbackEventNeedExit = true;
+    m_callbackEventCV.notify_one(); // notify CallbackEventWorker() for exit from infinite loop
+    m_callbackEventCV.wait(lockEvent); // wait for exit from infinite loop
+    m_callbackEventWorker.join();
+
     m_waitpidCV.notify_one(); // notify Shutdown(), that execution exit from WaitpidWorker()
     m_waitpidThreadStatus = WaitpidThreadStatus::FINISHED;
 }
@@ -557,11 +715,11 @@ HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &shared
     if (FAILED(SeizeAndInterruptAllThreads(m_TIDs, pid, error_n)))
         return ExitWithError();
 
-    WaitAllThreadsStop();
+    WaitThreadStop(g_waitForAllThreads);
 
-    auto loadLib = [this] (pid_t stop_pid, const std::string &libRealName, std::uintptr_t startAddr, std::uintptr_t endAddr)
+    auto loadLib = [this] (pid_t stop_pid, const std::string &libLoadName, const std::string &libRealName, std::uintptr_t startAddr, std::uintptr_t endAddr)
     {
-        LoadLib(stop_pid, libRealName, startAddr, endAddr);
+        LoadLib(stop_pid, libLoadName, libRealName, startAddr, endAddr);
     };
     auto unloadLib = [this] (const std::string &libRealName)
     {
@@ -584,6 +742,7 @@ HRESULT InteropDebugger::Init(pid_t pid, std::shared_ptr<CallbacksQueue> &shared
     m_sharedCallbacksQueue = sharedCallbacksQueue;
     m_waitpidCV.notify_one(); // notify WaitpidWorker() to start infinite loop
 
+    InitNativeFramesUnwind(this);
     return S_OK;
 }
 
@@ -594,7 +753,7 @@ void InteropDebugger::BrkStopAllThreads(bool &allThreadsWereStopped)
         return;
 
     StopAllRunningThreads();
-    WaitAllThreadsStop();
+    WaitThreadStop(g_waitForAllThreads);
     allThreadsWereStopped = true;
 }
 
@@ -610,7 +769,10 @@ void InteropDebugger::BrkFixAllThreads(std::uintptr_t checkAddr)
         iov.iov_base = &regs;
         iov.iov_len = sizeof(user_regs_struct);
         if (async_ptrace(PTRACE_GETREGSET, entry.first, (void*)NT_PRSTATUS, &iov) == -1)
+        {
             LOGW("Ptrace getregset error: %s\n", strerror(errno));
+            continue; // Will hope, this thread didn't stopped at breakpoint.
+        }
 
         std::uintptr_t brkAddrByPC = GetBrkAddrByPC(regs);
         if (checkAddr != brkAddrByPC)
@@ -673,5 +835,242 @@ HRESULT InteropDebugger::BreakpointActivate(uint32_t id, bool act)
     return Status;
 }
 
+static std::array<unw_word_t, UNW_REG_LAST + 1> *InitContextRegs(std::array<unw_word_t, UNW_REG_LAST + 1> &contextRegs, CONTEXT *context)
+{
+    assert(!!context);
+
+#if defined(UNW_TARGET_X86)
+    contextRegs[UNW_X86_EAX]       = context->Eax;
+    contextRegs[UNW_X86_EBX]       = context->Ebx;
+    contextRegs[UNW_X86_ECX]       = context->Ecx;
+    contextRegs[UNW_X86_EDX]       = context->Edx;
+    contextRegs[UNW_X86_ESI]       = context->Esi;
+    contextRegs[UNW_X86_EDI]       = context->Edi;
+    contextRegs[UNW_X86_EBP]       = context->Ebp;
+    contextRegs[UNW_X86_EIP]       = context->Eip;
+    contextRegs[UNW_X86_ESP]       = context->Esp;
+#elif defined(UNW_TARGET_X86_64)
+    contextRegs[UNW_X86_64_RAX]    = context->Rax;
+    contextRegs[UNW_X86_64_RDX]    = context->Rdx;
+    contextRegs[UNW_X86_64_RCX]    = context->Rcx;
+    contextRegs[UNW_X86_64_RBX]    = context->Rbx;
+    contextRegs[UNW_X86_64_RSI]    = context->Rsi;
+    contextRegs[UNW_X86_64_RDI]    = context->Rdi;
+    contextRegs[UNW_X86_64_RBP]    = context->Rbp;
+    contextRegs[UNW_X86_64_RSP]    = context->Rsp;
+    contextRegs[UNW_X86_64_R8]     = context->R8;
+    contextRegs[UNW_X86_64_R9]     = context->R9;
+    contextRegs[UNW_X86_64_R10]    = context->R10;
+    contextRegs[UNW_X86_64_R11]    = context->R11;
+    contextRegs[UNW_X86_64_R12]    = context->R12;
+    contextRegs[UNW_X86_64_R13]    = context->R13;
+    contextRegs[UNW_X86_64_R14]    = context->R14;
+    contextRegs[UNW_X86_64_R15]    = context->R15;
+    contextRegs[UNW_X86_64_RIP]    = context->Rip;
+#elif defined(UNW_TARGET_ARM)
+    contextRegs[UNW_ARM_R0]        = context->R0;
+    contextRegs[UNW_ARM_R1]        = context->R1;
+    contextRegs[UNW_ARM_R2]        = context->R2;
+    contextRegs[UNW_ARM_R3]        = context->R3;
+    contextRegs[UNW_ARM_R4]        = context->R4;
+    contextRegs[UNW_ARM_R5]        = context->R5;
+    contextRegs[UNW_ARM_R6]        = context->R6;
+    contextRegs[UNW_ARM_R7]        = context->R7;
+    contextRegs[UNW_ARM_R8]        = context->R8;
+    contextRegs[UNW_ARM_R9]        = context->R9;
+    contextRegs[UNW_ARM_R10]       = context->R10;
+    contextRegs[UNW_ARM_R11]       = context->R11;
+    contextRegs[UNW_ARM_R12]       = context->R12;
+    contextRegs[UNW_ARM_R13]       = context->Sp;
+    contextRegs[UNW_ARM_R14]       = context->Lr;
+    contextRegs[UNW_ARM_R15]       = context->Pc;
+#elif defined(UNW_TARGET_AARCH64)
+    contextRegs[UNW_AARCH64_X0]       = context->X0;
+    contextRegs[UNW_AARCH64_X1]       = context->X1;
+    contextRegs[UNW_AARCH64_X2]       = context->X2;
+    contextRegs[UNW_AARCH64_X3]       = context->X3;
+    contextRegs[UNW_AARCH64_X4]       = context->X4;
+    contextRegs[UNW_AARCH64_X5]       = context->X5;
+    contextRegs[UNW_AARCH64_X6]       = context->X6;
+    contextRegs[UNW_AARCH64_X7]       = context->X7;
+    contextRegs[UNW_AARCH64_X8]       = context->X8;
+    contextRegs[UNW_AARCH64_X9]       = context->X9;
+    contextRegs[UNW_AARCH64_X10]      = context->X10;
+    contextRegs[UNW_AARCH64_X11]      = context->X11;
+    contextRegs[UNW_AARCH64_X12]      = context->X12;
+    contextRegs[UNW_AARCH64_X13]      = context->X13;
+    contextRegs[UNW_AARCH64_X14]      = context->X14;
+    contextRegs[UNW_AARCH64_X15]      = context->X15;
+    contextRegs[UNW_AARCH64_X16]      = context->X16;
+    contextRegs[UNW_AARCH64_X17]      = context->X17;
+    contextRegs[UNW_AARCH64_X18]      = context->X18;
+    contextRegs[UNW_AARCH64_X19]      = context->X19;
+    contextRegs[UNW_AARCH64_X20]      = context->X20;
+    contextRegs[UNW_AARCH64_X21]      = context->X21;
+    contextRegs[UNW_AARCH64_X22]      = context->X22;
+    contextRegs[UNW_AARCH64_X23]      = context->X23;
+    contextRegs[UNW_AARCH64_X24]      = context->X24;
+    contextRegs[UNW_AARCH64_X25]      = context->X25;
+    contextRegs[UNW_AARCH64_X26]      = context->X26;
+    contextRegs[UNW_AARCH64_X27]      = context->X27;
+    contextRegs[UNW_AARCH64_X28]      = context->X28;
+    contextRegs[UNW_AARCH64_X29]      = context->Fp;
+    contextRegs[UNW_AARCH64_X30]      = context->Lr;
+    contextRegs[UNW_AARCH64_SP]       = context->Sp;
+    contextRegs[UNW_AARCH64_PC]       = context->Pc;
+    contextRegs[UNW_AARCH64_PSTATE]   = context->Cpsr;
+
+#else
+#error "Unsupported platform"
+#endif
+
+    return &contextRegs;
+}
+
+HRESULT InteropDebugger::UnwindNativeFrames(pid_t pid, bool firstFrame, std::uintptr_t endAddr, CONTEXT *pStartContext,
+                                            std::function<HRESULT(NativeFrame &nativeFrame)> nativeFramesCallback)
+{
+    std::lock_guard<std::mutex> lock(m_waitpidMutex);
+
+    // Note, user could provide TID with `bt --thread TID` that don't even belong to debuggee process.
+    auto tid = m_TIDs.find(pid);
+    if (tid == m_TIDs.end())
+        return E_INVALIDARG;
+
+    bool threadWasStopped = false;
+    if (tid->second.stat == thread_stat_e::running)
+    {
+        if (async_ptrace(PTRACE_INTERRUPT, pid, nullptr, nullptr) == -1)
+            LOGW("Ptrace interrupt error: %s\n", strerror(errno));
+        else
+        {
+            WaitThreadStop(pid);
+            threadWasStopped = true;
+        }
+    }
+
+#if defined(DEBUGGER_UNIX_ARM)
+    if (endAddr != 0)
+        endAddr = endAddr & ~((std::uintptr_t)1); // convert to proper (even) address (we use only even addresses here for testing and debug info search)
+#endif
+
+    // Note, CoreCLR could provide wrong SP in context for some cases, so, we can't use it for find "End" point of unwinding.
+    // The main point is - all unwind blocks that we have with `endAddr` provided is "native -> CoreCLR native frame".
+    // In case we have `endAddr` and don't reach it during unwind (that usually mean we failed to find CoreCLR native frame address),
+    // use first unknown address in unknown memory (that don't belong any native libs) as "End" point.
+    // In case we don't have frames with unknown address in unknown memory, just add "[Unknown native frame(s)]" frame at the end.
+
+    bool endAddrReached = false;
+    bool unwindTruncated = false;
+    static const std::size_t maxFrames = 1000;
+    std::vector<std::uintptr_t> addrFrames;
+    addrFrames.reserve(maxFrames);
+
+    std::array<unw_word_t, UNW_REG_LAST + 1> contextRegs;
+    ThreadStackUnwind(pid, pStartContext ? InitContextRegs(contextRegs, pStartContext) : 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
+
+        if (endAddr != 0 && endAddr == addr)
+        {
+            endAddrReached = true;
+            return false;
+        }
+
+        if (addrFrames.size() == maxFrames)
+        {
+            unwindTruncated = true;
+            return false;
+        }
+
+        addrFrames.emplace_back(addr);
+        return true;
+    });
+
+    HRESULT Status = S_OK;
+    for (auto addr : addrFrames)
+    {
+        NativeFrame result;
+        result.addr = addr;
+
+        std::uintptr_t libStartAddr = 0;
+        std::uintptr_t procStartAddr = 0;
+        // Note, in case unwind we need info for address that is part of previous (already executed) code for all frames except first.
+        m_uniqueInteropLibraries->FindDataForAddr(firstFrame ? addr : addr - 1, result.libName, libStartAddr, result.procName, procStartAddr, result.fullSourcePath, result.lineNum);
+        firstFrame = false;
+
+        if (endAddr != 0 && !endAddrReached && result.libName.empty())
+            break;
+
+        std::ostringstream ss;
+        if (result.procName.empty()) // in case we can't find procedure name at all - this is "unnamed symbol"
+        {
+            ss << "unnamed_symbol";
+            if (!result.libName.empty() && libStartAddr)
+                ss << ", " << result.libName << " + " << addr - libStartAddr;
+        }
+        else if (result.fullSourcePath.empty()) // in case we have procedure name without code source info - no debug data available (dynsym table data was used)
+        {
+            ss << result.procName;
+
+            if (procStartAddr)
+                ss << " + " << addr - procStartAddr;
+        }
+        else // we found all data we need from debug info
+        {
+            ss << result.procName;
+        }
+        result.procName = ss.str();
+
+        if (FAILED(Status = nativeFramesCallback(result)))
+            break;
+    }
+
+    // In case we not found frame with end address.
+    if (endAddr != 0 && !endAddrReached && SUCCEEDED(Status))
+    {
+        NativeFrame result;
+        result.unknownFrameAddr = true;
+        result.procName = "[Unknown native frame(s)]";
+        Status = nativeFramesCallback(result);
+    }
+
+    // In case unwind was truncated.
+    if (unwindTruncated && endAddr == 0 && SUCCEEDED(Status))
+    {
+        NativeFrame result;
+        result.unknownFrameAddr = true;
+        result.procName = "Unwind was truncated";
+        Status = nativeFramesCallback(result);
+    }
+
+    if (threadWasStopped)
+        ParseThreadsChanges();
+
+    return Status;
+}
+
+HRESULT InteropDebugger::GetFrameForAddr(std::uintptr_t addr, StackFrame &frame)
+{
+    std::uintptr_t libStartAddr = 0;
+    std::uintptr_t procStartAddr = 0;
+    std::string libName;
+    std::string methodName;
+    std::string fullSourcePath;
+    int lineNum = 0;
+    m_uniqueInteropLibraries->FindDataForAddr(addr, libName, libStartAddr, methodName, procStartAddr, fullSourcePath, lineNum);
+    if (methodName.empty())
+        methodName = "unnamed_symbol";
+
+    frame.moduleOrLibName = libName;
+    frame.methodName = methodName;
+    frame.source = Source(fullSourcePath);
+    frame.line = lineNum;
+    return S_OK;
+}
+
 } // namespace InteropDebugging
 } // namespace netcoredbg
index a57215671d69a4129055f7ce5ce907ac6fdc62a1..45fcba749b314d057920c8628fc2bf1834921311 100644 (file)
 #include <thread>
 #include <vector>
 #include <list>
+#include <functional>
 #include <condition_variable>
 #include <unordered_map>
 #include "interfaces/types.h"
-
+#include "debugger/frames.h"
 
 namespace netcoredbg
 {
@@ -38,17 +39,32 @@ enum class thread_stat_e
     running
 };
 
+struct stop_event_data_t
+{
+    std::uintptr_t addr = 0;
+};
+
 struct thread_status_t
 {
     thread_stat_e stat = thread_stat_e::running;
-    unsigned int stop_signal = 0;
+    unsigned stop_signal = 0;
     unsigned event = 0;
 
     // Data, that should be stored in order to create stop event (CallbacksQueue) and/or continue thread execution.
-    struct
-    {
-        std::uintptr_t addr = 0;
-    } stop_event_data;
+    stop_event_data_t stop_event_data;
+};
+
+struct callback_event_t
+{
+    pid_t pid;
+    thread_stat_e stat;
+    stop_event_data_t stop_event_data;
+
+    callback_event_t(pid_t pid_, thread_stat_e stat_, const stop_event_data_t &data_) :
+        pid(pid_),
+        stat(stat_),
+        stop_event_data(data_)
+    {}
 };
 
 class InteropDebugger
@@ -70,6 +86,10 @@ public:
     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);
+
 private:
 
     std::shared_ptr<IProtocol> m_sharedProtocol;
@@ -86,6 +106,16 @@ private:
         FINISHED_AND_JOINED
     };
 
+    // NOTE we can't setup callbacks in waitpid thread, since CoreCLR could use native breakpoints in managed threads, some managed threads
+    //      could be stopped at CoreCLR's breakpoint and wait for waitpid, but we wait for managed process `Stop()` in the same time.
+    std::mutex m_callbackEventMutex;
+    std::thread m_callbackEventWorker;
+    bool m_callbackEventNeedExit = false;
+    std::condition_variable m_callbackEventCV;
+    std::list<callback_event_t> m_callbackEvents;
+
+    void CallbackEventWorker();
+
     std::mutex m_waitpidMutex;
     std::thread m_waitpidWorker;
     bool m_waitpidNeedExit = false;
@@ -99,11 +129,11 @@ private:
 
     void WaitpidWorker();
 
-    void LoadLib(pid_t pid, const std::string &realLibName, std::uintptr_t startAddr, std::uintptr_t endAddr);
+    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 StopAllRunningThreads();
-    void WaitAllThreadsStop();
+    void WaitThreadStop(pid_t stoppedPid);
     void StopAndDetach(pid_t tgid);
     void Detach(pid_t tgid);
     void ParseThreadsChanges();
index c939812321aa5ed38ada04f71b4b99d491341512..571323b1d07f7483c2122c16e590148f1e6e5e62 100644 (file)
@@ -108,7 +108,10 @@ long async_ptrace(__ptrace_request request, pid_t pid, void *addr, void *data)
     std::unique_lock<std::mutex> lock(g_ptraceMutex);
 
     if (g_ptraceThreadStatus != PtraceThreadStatus::WORK)
-        return EAGAIN;
+    {
+        errno = EPERM;
+        return -1;
+    }
 
     g_ptraceArgs.Set(request, pid, addr, data);
 
diff --git a/src/debugger/interop_unwind.cpp b/src/debugger/interop_unwind.cpp
new file mode 100644 (file)
index 0000000..d531a8b
--- /dev/null
@@ -0,0 +1,326 @@
+// Copyright (c) 2023 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "debugger/interop_unwind.h"
+#include "debugger/interop_ptrace_helpers.h"
+
+#include <libunwind-ptrace.h> // _UPT_find_proc_info()
+#include <sys/mman.h>
+#include <array>
+#include <cstring>
+#include <errno.h>
+#include <sys/uio.h> // iovec
+#include <elf.h> // NT_PRSTATUS
+#include "utils/logger.h"
+#include "metadata/interop_libraries.h"
+
+
+namespace netcoredbg
+{
+namespace InteropDebugging
+{
+
+struct elf_image_t
+{
+    void *image; // pointer to mmap'd image
+    size_t size; // (file-) size of the image
+};
+
+struct elf_dyn_info_t
+{
+    elf_image_t ei;
+    unw_dyn_info_t di_cache;
+    unw_dyn_info_t di_debug; // additional table info for .debug_frame
+#if UNW_TARGET_ARM
+    unw_dyn_info_t di_arm; // additional table info for .ARM.exidx
+#endif
+};
+
+struct UPT_info
+{
+    struct
+    {
+        pid_t pid; // the process-id of the child we're unwinding
+        elf_dyn_info_t edi;
+    } libunwind_UPT_info;
+    const std::array<unw_word_t, UNW_REG_LAST + 1> *contextRegs;
+};
+
+static inline void invalidate_edi(elf_dyn_info_t *edi)
+{
+    if (edi->ei.image)
+    {
+        munmap(edi->ei.image, edi->ei.size);
+    }
+    memset(edi, 0, sizeof(*edi));
+    edi->di_cache.format = -1;
+    edi->di_debug.format = -1;
+#if UNW_TARGET_ARM
+    edi->di_arm.format = -1;
+#endif
+}
+
+// ptrace related registers data (see <sys/user.h>).
+static std::array<int, UNW_REG_LAST + 1> InitPtraceRegOffset()
+{
+    std::array<int, UNW_REG_LAST + 1> res;
+
+#if defined(UNW_TARGET_X86)
+    res[UNW_X86_EAX]       = 0x18;
+    res[UNW_X86_EBX]       = 0x00;
+    res[UNW_X86_ECX]       = 0x04;
+    res[UNW_X86_EDX]       = 0x08;
+    res[UNW_X86_ESI]       = 0x0c;
+    res[UNW_X86_EDI]       = 0x10;
+    res[UNW_X86_EBP]       = 0x14;
+    res[UNW_X86_EIP]       = 0x30;
+    res[UNW_X86_ESP]       = 0x3c;
+#elif defined(UNW_TARGET_X86_64)
+    res[UNW_X86_64_RAX]    = 0x50;
+    res[UNW_X86_64_RDX]    = 0x60;
+    res[UNW_X86_64_RCX]    = 0x58;
+    res[UNW_X86_64_RBX]    = 0x28;
+    res[UNW_X86_64_RSI]    = 0x68;
+    res[UNW_X86_64_RDI]    = 0x70;
+    res[UNW_X86_64_RBP]    = 0x20;
+    res[UNW_X86_64_RSP]    = 0x98;
+    res[UNW_X86_64_R8]     = 0x48;
+    res[UNW_X86_64_R9]     = 0x40;
+    res[UNW_X86_64_R10]    = 0x38;
+    res[UNW_X86_64_R11]    = 0x30;
+    res[UNW_X86_64_R12]    = 0x18;
+    res[UNW_X86_64_R13]    = 0x10;
+    res[UNW_X86_64_R14]    = 0x08;
+    res[UNW_X86_64_R15]    = 0x00;
+    res[UNW_X86_64_RIP]    = 0x80;
+#elif defined(UNW_TARGET_ARM)
+    res[UNW_ARM_R0]        = 0x00;
+    res[UNW_ARM_R1]        = 0x04;
+    res[UNW_ARM_R2]        = 0x08;
+    res[UNW_ARM_R3]        = 0x0c;
+    res[UNW_ARM_R4]        = 0x10;
+    res[UNW_ARM_R5]        = 0x14;
+    res[UNW_ARM_R6]        = 0x18;
+    res[UNW_ARM_R7]        = 0x1c;
+    res[UNW_ARM_R8]        = 0x20;
+    res[UNW_ARM_R9]        = 0x24;
+    res[UNW_ARM_R10]       = 0x28;
+    res[UNW_ARM_R11]       = 0x2c;
+    res[UNW_ARM_R12]       = 0x30;
+    res[UNW_ARM_R13]       = 0x34;
+    res[UNW_ARM_R14]       = 0x38;
+    res[UNW_ARM_R15]       = 0x3c;
+#elif defined(UNW_TARGET_AARCH64)
+    res[UNW_AARCH64_X0]       = 0x00;
+    res[UNW_AARCH64_X1]       = 0x08;
+    res[UNW_AARCH64_X2]       = 0x10;
+    res[UNW_AARCH64_X3]       = 0x18;
+    res[UNW_AARCH64_X4]       = 0x20;
+    res[UNW_AARCH64_X5]       = 0x28;
+    res[UNW_AARCH64_X6]       = 0x30;
+    res[UNW_AARCH64_X7]       = 0x38;
+    res[UNW_AARCH64_X8]       = 0x40;
+    res[UNW_AARCH64_X9]       = 0x48;
+    res[UNW_AARCH64_X10]      = 0x50;
+    res[UNW_AARCH64_X11]      = 0x58;
+    res[UNW_AARCH64_X12]      = 0x60;
+    res[UNW_AARCH64_X13]      = 0x68;
+    res[UNW_AARCH64_X14]      = 0x70;
+    res[UNW_AARCH64_X15]      = 0x78;
+    res[UNW_AARCH64_X16]      = 0x80;
+    res[UNW_AARCH64_X17]      = 0x88;
+    res[UNW_AARCH64_X18]      = 0x90;
+    res[UNW_AARCH64_X19]      = 0x98;
+    res[UNW_AARCH64_X20]      = 0xa0;
+    res[UNW_AARCH64_X21]      = 0xa8;
+    res[UNW_AARCH64_X22]      = 0xb0;
+    res[UNW_AARCH64_X23]      = 0xb8;
+    res[UNW_AARCH64_X24]      = 0xc0;
+    res[UNW_AARCH64_X25]      = 0xc8;
+    res[UNW_AARCH64_X26]      = 0xd0;
+    res[UNW_AARCH64_X27]      = 0xd8;
+    res[UNW_AARCH64_X28]      = 0xe0;
+    res[UNW_AARCH64_X29]      = 0xe8; // FP
+    res[UNW_AARCH64_X30]      = 0xf0; // LR
+    res[UNW_AARCH64_SP]       = 0xf8;
+    res[UNW_AARCH64_PC]       = 0x100;
+    res[UNW_AARCH64_PSTATE]   = 0x108;
+#else
+#error "Unsupported platform"
+#endif
+
+    return res;
+}
+const std::array<int, UNW_REG_LAST + 1> g_ptraceRegOffset(InitPtraceRegOffset());
+
+
+
+
+static void *UnwindContextCreate(pid_t pid, const std::array<unw_word_t, UNW_REG_LAST + 1> *contextRegs)
+{
+    UPT_info *ui = (UPT_info*)malloc(sizeof(UPT_info));
+    if (!ui)
+        return nullptr;
+
+    memset(ui, 0, sizeof(*ui));
+    ui->libunwind_UPT_info.pid = pid;
+    ui->contextRegs = contextRegs;
+    ui->libunwind_UPT_info.edi.di_cache.format = -1;
+    ui->libunwind_UPT_info.edi.di_debug.format = -1;
+    return ui;
+}
+
+static void UnwindContextDestroy(void *ptr)
+{
+    UPT_info *ui = (UPT_info*)ptr;
+    invalidate_edi(&ui->libunwind_UPT_info.edi);
+    free(ptr);
+}
+
+static int FindProcInfo(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi, int need_unwind_info, void *arg)
+{
+    UPT_info *ui = (UPT_info*)arg;
+    return _UPT_find_proc_info(as, ip, pi, need_unwind_info, &ui->libunwind_UPT_info);
+}
+
+static void PutUnwindInfo(unw_addr_space_t as, unw_proc_info_t *pi, void *arg)
+{
+    if (!pi->unwind_info)
+        return;
+    free (pi->unwind_info);
+    pi->unwind_info = NULL;
+}
+
+static int GetDynInfoListAddr(unw_addr_space_t, unw_word_t *, void *)
+{
+    // TODO there is currently no way to locate the dyn-info list by a remote unwinder. On ia64, this is done via a special
+    //      unwind-table entry. Perhaps something similar can be done with DWARF2 unwind info. 
+    return -UNW_ENOINFO;
+}
+
+static int AccessMem(unw_addr_space_t as, unw_word_t addr, unw_word_t *val, int write, void *arg)
+{
+    if (write)
+        return -UNW_EINVAL;
+
+    UPT_info *ui = (UPT_info*)arg;
+    if (!ui)
+        return -UNW_EINVAL;
+
+    errno = 0;
+    *val = async_ptrace(PTRACE_PEEKDATA, ui->libunwind_UPT_info.pid, (void*)addr, 0);
+
+    return errno ? -UNW_EINVAL : 0;
+}
+
+static int AccessReg(unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, int write, void *arg)
+{
+    if (write)
+        return -UNW_EINVAL;
+
+    UPT_info *ui = (UPT_info*)arg;
+
+    if (ui->contextRegs)
+    {
+        if ((unsigned)reg >= ui->contextRegs->size())
+            return -UNW_EBADREG;
+
+        *val = (*(ui->contextRegs))[reg];
+        return 0;
+    }
+
+    if ((unsigned)reg >= g_ptraceRegOffset.size())
+        return -UNW_EBADREG;
+
+    user_regs_struct regs;
+    iovec loc;
+    loc.iov_base = &regs;
+    loc.iov_len = sizeof(regs);
+
+    if (async_ptrace(PTRACE_GETREGSET, ui->libunwind_UPT_info.pid, (void*)NT_PRSTATUS, &loc) == -1)
+        return -UNW_EBADREG;
+
+    char *r = (char*)&regs + g_ptraceRegOffset[reg];
+    memcpy(val, r, sizeof(unw_word_t));
+
+    return 0;
+}
+
+static int AccessFpreg(unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val, int write, void *arg)
+{
+    // We don't need this for sure.
+    return -UNW_EINVAL;
+}
+
+static int GetProcName(unw_addr_space_t, unw_word_t, char *, size_t, unw_word_t *, void *)
+{
+    // We don't need this for sure.
+    return -UNW_EINVAL;
+}
+
+static int ResumeExecution(unw_addr_space_t, unw_cursor_t *, void *)
+{
+    // We don't need this for sure.
+    return -UNW_EINVAL;
+}
+
+void ThreadStackUnwind(pid_t pid, std::array<unw_word_t, UNW_REG_LAST + 1> *contextRegs, std::function<bool(std::uintptr_t)> threadStackUnwindCallback)
+{
+    // TODO ? setup for arm32 env UNW_ARM_UNWIND_METHOD with value UNW_ARM_METHOD_FRAME (looks like all unwinding good by default, no unwind method changes needed)
+
+    // TODO ? use global cache for libunwinde (increase unwinding speed in exchange of memory usage)
+    //  - unw_create_addr_space() at "unwind init"
+    //  - unw_set_caching_policy() with UNW_CACHE_GLOBAL https://www.nongnu.org/libunwind/man/unw_set_caching_policy(3).html
+    //  - unw_flush_cache() in case some lib unload or at "unwind shutdown" https://www.nongnu.org/libunwind/man/unw_flush_cache(3).html
+
+    static unw_accessors_t accessors =
+    {
+        .find_proc_info             = FindProcInfo,
+        .put_unwind_info            = PutUnwindInfo,
+        .get_dyn_info_list_addr     = GetDynInfoListAddr,
+        .access_mem                 = AccessMem,
+        .access_reg                 = AccessReg,
+        .access_fpreg               = AccessFpreg,
+        .resume                     = ResumeExecution,
+        .get_proc_name              = GetProcName
+    };
+
+    unw_addr_space_t addrSpace = unw_create_addr_space(&accessors, 0);
+    void *unwind_context = UnwindContextCreate(pid, contextRegs);
+    unw_cursor_t unwind_cursor;
+    if (unw_init_remote(&unwind_cursor, addrSpace, unwind_context) < 0)
+        LOGE("ERROR: cannot initialize cursor for remote unwinding");
+    else
+    {
+#if defined(UNW_TARGET_AARCH64)
+        unw_word_t prev_pc = 0;
+#endif
+        do
+        {
+            unw_word_t pc;
+            if (unw_get_reg(&unwind_cursor, UNW_REG_IP, &pc) < 0)
+            {
+                LOGE("ERROR: cannot read program counter");
+                break;
+            }
+
+#if defined(UNW_TARGET_AARCH64)
+            if (prev_pc == pc)
+                break;
+            else
+                prev_pc = pc;
+#endif
+
+            if (!threadStackUnwindCallback(pc))
+                break;
+        }
+        while (unw_step(&unwind_cursor) > 0);
+    }
+    UnwindContextDestroy(unwind_context);
+    unw_destroy_addr_space(addrSpace);
+}
+
+
+} // namespace InteropDebugging
+} // namespace netcoredbg
diff --git a/src/debugger/interop_unwind.h b/src/debugger/interop_unwind.h
new file mode 100644 (file)
index 0000000..65bfaad
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (c) 2023 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+#pragma once
+
+#ifdef INTEROP_DEBUGGING
+
+#include <sys/types.h>
+#include <functional>
+#include <libunwind.h>
+
+namespace netcoredbg
+{
+namespace InteropDebugging
+{
+
+void ThreadStackUnwind(pid_t pid, std::array<unw_word_t, UNW_REG_LAST + 1> *contextRegs, std::function<bool(std::uintptr_t)> threadStackUnwindCallback);
+
+} // namespace InteropDebugging
+} // namespace netcoredbg
+
+#endif // INTEROP_DEBUGGING
index a1f6c9306ebeeb7623a05249065037fcde350bd0..77276d66577e75bc4b31b8273605a3cb5b270ede 100644 (file)
@@ -554,7 +554,6 @@ HRESULT ManagedDebugger::Startup(IUnknown *punk, DWORD pid)
     m_sharedCallbacksQueue.reset(new CallbacksQueue(*this));
     m_uniqueManagedCallback.reset(new ManagedCallback(*this, m_sharedCallbacksQueue));
     Status = iCorDebug->SetManagedHandler(m_uniqueManagedCallback.get());
-
     if (FAILED(Status))
     {
         iCorDebug->Terminate();
@@ -936,6 +935,7 @@ 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
     if (isNativeSource(filename))
         return m_uniqueInteropDebugger->SetLineBreakpoints(filename, lineBreakpoints, breakpoints);
 #endif // INTEROP_DEBUGGING
@@ -980,6 +980,10 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
 {
     HRESULT Status;
 
+    stackFrame = StackFrame(threadId, level, "");
+    if (FAILED(TypePrinter::GetMethodName(pFrame, stackFrame.methodName)))
+        stackFrame.methodName = "Unnamed method in optimized code";
+
     ToRelease<ICorDebugFunction> pFunc;
     IfFailRet(pFrame->GetFunction(&pFunc));
 
@@ -1012,17 +1016,13 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
                 moduleNamePrefix += "!";
             }
 
-            std::string methodName;
-            TypePrinter::GetMethodName(pFrame, methodName);
             // [Outdated Code] module.dll!MethodName()
-            stackFrame = StackFrame(threadId, level, "[Outdated Code] " + moduleNamePrefix + methodName);
+            stackFrame.methodName = "[Outdated Code] " + moduleNamePrefix + stackFrame.methodName;
 
             return S_OK;
         }
     }
 
-    stackFrame = StackFrame(threadId, level, "");
-
     ULONG32 ilOffset;
     Modules::SequencePoint sp;
     if (SUCCEEDED(pModules->GetFrameILAndSequencePoint(pFrame, ilOffset, sp)))
@@ -1049,7 +1049,7 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
     stackFrame.clrAddr.nativeOffset = nOffset;
     stackFrame.clrAddr.methodVersion = methodVersion;
 
-    stackFrame.addr = GetFrameAddr(pFrame);
+    stackFrame.addr = 0; // This method used for managed stop events only, but all implemented protocols don't use `addr` in managed stop events outputs.
 
     if (stackFrame.clrAddr.ilOffset != 0)
         stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::PartiallyExecuted;
@@ -1058,8 +1058,6 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
     else
         stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::Stale;
 
-    TypePrinter::GetMethodName(pFrame, stackFrame.name);
-
     return S_OK;
 }
 
@@ -1068,17 +1066,30 @@ HRESULT ManagedDebugger::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threa
     return InternalGetFrameLocation(pFrame, m_sharedModules.get(), m_hotReload, threadId, level, stackFrame, false);
 }
 
-static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebugThread *pThread, FrameLevel startFrame,
+static std::string GetModuleNameForFrame(ICorDebugFrame *pFrame)
+{
+    ToRelease<ICorDebugFunction> pFunc;
+    if (!pFrame || FAILED(pFrame->GetFunction(&pFunc)))
+        return std::string{};
+
+    ToRelease<ICorDebugModule> pModule;
+    if (FAILED(pFunc->GetModule(&pModule)))
+        return std::string{};
+
+    WCHAR name[mdNameLen];
+    ULONG32 name_len = 0;
+    if (FAILED(pModule->GetName(_countof(name), &name_len, name)))
+        return std::string{};
+
+    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)
 {
     LogFuncEntry();
 
     HRESULT Status;
-
-    DWORD tid = 0;
-    pThread->GetID(&tid);
-    ThreadId threadId{tid};
-
     int currentFrame = -1;
 
     auto AddFrameStatementFlag = [&] ()
@@ -1089,11 +1100,19 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
             stackFrames.back().activeStatementFlags |= StackFrame::ActiveStatementFlags::NonLeafFrame;
     };
 
+#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]";
+#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]";
+#endif // INTEROP_DEBUGGING
+
     IfFailRet(WalkFrames(pThread, [&](
         FrameType frameType,
+        std::uintptr_t addr,
         ICorDebugFrame *pFrame,
-        NativeFrame *pNative,
-        ICorDebugFunction *pFunction)
+        NativeFrame *pNative)
     {
         currentFrame++;
 
@@ -1106,19 +1125,22 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
         {
             case FrameUnknown:
                 stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, "?");
-                stackFrames.back().addr = GetFrameAddr(pFrame);
+                stackFrames.back().addr = addr;
                 AddFrameStatementFlag();
                 break;
             case FrameNative:
-                stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, pNative->symbol);
+                stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, pNative->procName);
                 stackFrames.back().addr = pNative->addr;
-                stackFrames.back().source = Source(pNative->file);
-                stackFrames.back().line = pNative->linenum;
+                stackFrames.back().unknownFrameAddr = pNative->unknownFrameAddr;
+                stackFrames.back().moduleOrLibName = pNative->libName;
+                stackFrames.back().source = Source(pNative->fullSourcePath);
+                stackFrames.back().line = pNative->lineNum;
                 AddFrameStatementFlag();
                 break;
             case FrameCLRNative:
-                stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, "[Native Frame]");
-                stackFrames.back().addr = GetFrameAddr(pFrame);
+                stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, FrameCLRNativeText);
+                stackFrames.back().addr = addr;
+                stackFrames.back().unknownFrameAddr = !addr; // Could be 0 here only in case some CoreCLR registers context issue.
                 AddFrameStatementFlag();
                 break;
             case FrameCLRInternal:
@@ -1131,7 +1153,8 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
                     name += GetInternalTypeName(corFrameType);
                     name += "]";
                     stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, name);
-                    stackFrames.back().addr = GetFrameAddr(pFrame);
+                    stackFrames.back().addr = addr;
+                    stackFrames.back().unknownFrameAddr = !addr; // Could be 0 here only in case some CoreCLR registers context issue.
                     AddFrameStatementFlag();
                 }
                 break;
@@ -1140,6 +1163,9 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
                     StackFrame stackFrame;
                     InternalGetFrameLocation(pFrame, pModules, hotReload, 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.
+                    stackFrames.back().moduleOrLibName = GetModuleNameForFrame(pFrame);
                     AddFrameStatementFlag();
                 }
                 break;
@@ -1153,7 +1179,46 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
     return S_OK;
 }
 
-HRESULT ManagedDebugger::GetStackTrace(ThreadId  threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
+#ifdef INTEROP_DEBUGGING
+static HRESULT InternalGetNativeStackTrace(InteropDebugging::InteropDebugger *pInteropDebugger, 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)
+    {
+        currentFrame++;
+
+        if (currentFrame < int(startFrame))
+            return S_OK;
+        if (maxFrames != 0 && currentFrame >= int(startFrame) + int(maxFrames))
+            return S_OK;
+
+        stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, nativeFrame.procName);
+        stackFrames.back().addr = nativeFrame.addr;
+        stackFrames.back().unknownFrameAddr = nativeFrame.unknownFrameAddr;
+        stackFrames.back().moduleOrLibName = nativeFrame.libName;
+        stackFrames.back().source = Source(nativeFrame.fullSourcePath);
+        stackFrames.back().line = nativeFrame.lineNum;
+
+        if (currentFrame == 0)
+            stackFrames.back().activeStatementFlags |= StackFrame::ActiveStatementFlags::LeafFrame;
+        else
+            stackFrames.back().activeStatementFlags |= StackFrame::ActiveStatementFlags::NonLeafFrame;
+
+        return S_OK;
+    });
+
+    totalFrames = currentFrame + 1;
+    
+    return Status;
+}
+#endif // INTEROP_DEBUGGING
+
+HRESULT ManagedDebugger::GetStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
 {
     LogFuncEntry();
 
@@ -1162,8 +1227,16 @@ HRESULT ManagedDebugger::GetStackTrace(ThreadId  threadId, FrameLevel startFrame
     IfFailRet(CheckDebugProcess(m_iCorProcess, m_processAttachedMutex, m_processAttachedState));
 
     ToRelease<ICorDebugThread> pThread;
-    IfFailRet(m_iCorProcess->GetThread(int(threadId), &pThread));
-    return InternalGetStackTrace(m_sharedModules.get(), m_hotReload, pThread, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
+    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);
+
+#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);
+#endif // INTEROP_DEBUGGING
+
+    return Status;
 }
 
 int ManagedDebugger::GetNamedVariables(uint32_t variablesReference)
index 4c158d7d83c36e655b68c53f0d0ac6d023755385..288476c4ba41400afeffeaab7eef88b33d23f085 100644 (file)
@@ -177,8 +177,8 @@ private:
     mutable Optional<FrameLevel> level;
 
 public:
-    FrameId id;         // should be assigned only once, befor calls to GetLevel or GetThreadId.
-    std::string name;
+    FrameId id;         // should be assigned only once, before calls to GetLevel or GetThreadId.
+    std::string methodName;
     Source source;
     int line;
     int column;
@@ -187,7 +187,9 @@ public:
     std::string moduleId;
 
     ClrAddr clrAddr; // exposed for MI protocol
-    uint64_t addr; // exposed for MI protocol
+    std::uintptr_t addr; // exposed for MI and CLI protocols
+    bool unknownFrameAddr; // exposed for CLI protocol
+    std::string moduleOrLibName; // exposed for CLI protocol
 
     enum ActiveStatementFlags : uint16_t
     {
@@ -202,16 +204,16 @@ public:
 
     StackFrame() :
         thread(ThreadId{}, true), level(FrameLevel{}, true), id(),
-        line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0) {}
+        line(0), column(0), endLine(0), endColumn(0), addr(0), unknownFrameAddr(false), activeStatementFlags(0) {}
 
-    StackFrame(ThreadId threadId, FrameLevel level, const std::string& name) :
-        thread(threadId, true), level(level, true), id(FrameId(threadId, level)),
-        name(name), line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0)
+    StackFrame(ThreadId threadId, FrameLevel level_, const std::string& methodName_) :
+        thread(threadId, true), level(level_, true), id(FrameId(threadId, level_)),
+        methodName(methodName_), line(0), column(0), endLine(0), endColumn(0), addr(0), unknownFrameAddr(false), activeStatementFlags(0)
     {}
 
     StackFrame(FrameId id) :
         thread(ThreadId{}, false), level(FrameLevel{}, false), id(id),
-        line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0)
+        line(0), column(0), endLine(0), endColumn(0), addr(0), unknownFrameAddr(false), activeStatementFlags(0)
     {}
 
     FrameLevel GetLevel() const
index 84690b17af0b2a3bd3b83e6b048d1699c2d2b937..2f70a4219364d1b6256247cc7b0dfaf1d4f21831 100644 (file)
@@ -9,9 +9,9 @@
 #include "elf++.h"
 #include "dwarf++.h"
 #include "utils/logger.h"
-#if DEBUGGER_UNIX_ARM
 #include <elf.h>
-#endif
+#include "utils/filesystem.h"
+#include <cxxabi.h> // demangle
 
 
 namespace netcoredbg
@@ -26,11 +26,86 @@ namespace
 
 } // unnamed namespace
 
+static bool OpenElf(const std::string &file, std::unique_ptr<elf::elf> &ef)
+{
+    int fd = open(file.c_str(), O_RDONLY);
+    if (fd == -1)
+    {
+        LOGI("Load elf failed at file open %s: %s\n", file.c_str(), strerror(errno));
+        return false;
+    }
+
+    try
+    {
+        ef.reset(new elf::elf(elf::create_mmap_loader(fd)));
+    }
+    catch(const std::exception& e)
+    {
+        close(fd);
+        LOGI("Load elf failed at elf::elf() for file %s: %s\n", file.c_str(), e.what());
+        return false;
+    }
+    close(fd);
+    return true;
+}
 
 #if DEBUGGER_UNIX_ARM
-void InteropLibraries::CollectThumbCodeRegions(std::uintptr_t startAddr, LibraryInfo &info)
+static bool CollectThumbCodeRegionsBySymtab(std::uintptr_t startAddr, InteropLibraries::LibraryInfo &info, std::unique_ptr<elf::elf> &ef)
+{
+    std::map<std::uintptr_t, char> blocksByTypes;
+    bool symtabDetected = false;
+    for (auto &sec : ef->sections())
+    {
+        if (sec.get_name() != ".symtab")
+            continue;
+
+        symtabDetected = true;
+        elf::symtab symTab = sec.as_symtab();
+
+        for (auto sym : symTab)
+        {
+            auto data = sym.get_data();
+            if (ELF32_ST_TYPE(data.info) != STT_NOTYPE ||
+                data.size != 0)
+                continue;
+
+            std::string symName = sym.get_name();
+            if (symName.size() < 2 || symName[0] != '$')
+                continue;
+
+            std::uintptr_t addrStart = data.value + startAddr; // address is even, no need to convert
+            blocksByTypes[addrStart] = symName[1];
+        }
+    }
+
+    if (!symtabDetected)
+        return false;
+
+    // At this point we have blocksByTypes ordered by address and data will be looks like: `d`, `a`, `a`, `a`, `t`, `a`, `t`, `t`, `a`, `t`, `d`, `d`, ...
+    std::uintptr_t thumbStart = 0;
+    for (auto &entry : blocksByTypes)
+    {
+        if (entry.second == 't')
+        {
+            if (!thumbStart)
+                thumbStart = entry.first;
+        }
+        else if (thumbStart)
+        {
+            info.thumbRegions[thumbStart] = entry.first;
+            thumbStart = 0;
+        }
+    }
+    if (thumbStart)
+        info.thumbRegions[thumbStart] = info.libEndAddr;
+
+    info.thumbRegionsValid = true;
+    return true;
+}
+
+static void CollectThumbCodeRegionsByDynsymtab(std::uintptr_t startAddr, InteropLibraries::LibraryInfo &info, std::unique_ptr<elf::elf> &ef)
 {
-    for (auto &sec : info.ef->sections())
+    for (auto &sec : ef->sections())
     {
         if (sec.get_name() != ".dynsym")
             continue;
@@ -78,37 +153,68 @@ void InteropLibraries::CollectThumbCodeRegions(std::uintptr_t startAddr, Library
             info.thumbRegions[addrStart] = addrEnd;
         }
     }
+
+    info.thumbRegionsValid = true;
+}
+
+static void CollectThumbCodeRegions(std::uintptr_t startAddr, InteropLibraries::LibraryInfo &info)
+{
+    std::unique_ptr<elf::elf> ef;
+    if (!OpenElf(info.fullName, ef))
+        return;
+
+    // Note, in case of thumb code, ".symtab" provide more info than ".dynsym", since in this case we have all thumb code blocks, even for code without symbol name.
+    if (CollectThumbCodeRegionsBySymtab(startAddr, info, ef))
+        return;
+
+    CollectThumbCodeRegionsByDynsymtab(startAddr, info, ef);
 }
 #endif
 
-bool InteropLibraries::LoadDebuginfoFromFile(std::uintptr_t startAddr, const std::string &fileName, LibraryInfo &info, bool collectElfData)
+static void CollectProcDataFromElf(std::uintptr_t startAddr, InteropLibraries::LibraryInfo &info)
 {
-    int fd = open(fileName.c_str(), O_RDONLY);
-    if (fd == -1)
-    {
-        LOGI("Load debuginfo failed at file open %s: %s\n", fileName.c_str(), strerror(errno));
-        return false;
-    }
+    std::unique_ptr<elf::elf> ef;
+    if (!OpenElf(info.fullName, ef))
+        return;
 
-    try
+    for (auto &sec : ef->sections())
     {
-        info.ef.reset(new elf::elf(elf::create_mmap_loader(fd)));
-    }
-    catch(const std::exception& e)
-    {
-        close(fd);
-        LOGI("Load debuginfo failed at elf::elf() for file %s: %s\n", fileName.c_str(), e.what());
-        return false;
-    }
-    close(fd);
+        if (sec.get_name() != ".dynsym" &&
+            sec.get_name() != ".symtab")
+            continue;
+
+        elf::symtab symTab = sec.as_symtab();
+
+        for (auto sym : symTab)
+        {
+            auto data = sym.get_data();
+            // Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field.
+            if (ELF32_ST_TYPE(data.info) != STT_FUNC || // we need executed code only
+                data.size == 0)
+                continue;
+
+            std::uintptr_t addrStart = data.value + startAddr;
+
+            if (info.proceduresData.find(addrStart) != info.proceduresData.end())
+                continue;
 
-    if (collectElfData)
-    {
 #if DEBUGGER_UNIX_ARM
-        CollectThumbCodeRegions(startAddr, info);
+            addrStart = addrStart & ~((std::uintptr_t)1); // convert to proper (even) address of code block start
 #endif
+            std::uintptr_t addrEnd = addrStart + data.size;
+
+            info.proceduresData.emplace(std::make_pair(addrStart, InteropLibraries::LibraryInfo::proc_data_t(addrEnd, sym.get_name())));
+        }
     }
 
+    info.proceduresDataValid = true;
+}
+
+static bool LoadDebuginfoFromFile(const std::string &fileName, InteropLibraries::LibraryInfo &info)
+{
+    if (!OpenElf(fileName, info.ef))
+        return false;
+
     try
     {
         info.dw.reset(new dwarf::dwarf(dwarf::elf::create_loader(*(info.ef.get()))));
@@ -136,7 +242,7 @@ static bool GetFileNameAndPath(const std::string &path, std::string &fileName, s
     return true;
 }
 
-SymbolStatus InteropLibraries::LoadDebuginfo(std::uintptr_t startAddr, LibraryInfo &info)
+static SymbolStatus LoadDebuginfo(const std::string &libLoadName, InteropLibraries::LibraryInfo &info)
 {
     // Debuginfo search sequence:
     // 1. Check debuginfo section in target file itself;
@@ -145,7 +251,7 @@ SymbolStatus InteropLibraries::LoadDebuginfo(std::uintptr_t startAddr, LibraryIn
     // 4. Check file with same location as target file inside `/usr/lib/debug/` directory and with `.debug` extension.
 
     // Note, in case `.so` we also need collect all elf data we could need.
-    if (LoadDebuginfoFromFile(startAddr, info.fullName, info, true))
+    if (LoadDebuginfoFromFile(info.fullName, info))
         return SymbolStatus::SymbolsLoaded;
 
     std::string fileName;
@@ -153,19 +259,48 @@ SymbolStatus InteropLibraries::LoadDebuginfo(std::uintptr_t startAddr, LibraryIn
     if (!GetFileNameAndPath(info.fullName, fileName, filePath))
         return SymbolStatus::SymbolsNotFound;
 
-    if (LoadDebuginfoFromFile(startAddr, filePath + fileName + ".debug", info))
+    if (LoadDebuginfoFromFile(filePath + fileName + ".debug", info))
         return SymbolStatus::SymbolsLoaded;
 
-    if (LoadDebuginfoFromFile(startAddr, filePath + ".debug/"+ fileName + ".debug", info))
+    if (LoadDebuginfoFromFile(filePath + ".debug/"+ fileName + ".debug", info))
         return SymbolStatus::SymbolsLoaded;
 
-    if (LoadDebuginfoFromFile(startAddr, "/usr/lib/debug/" + filePath + fileName + ".debug", info))
+    if (LoadDebuginfoFromFile("/usr/lib/debug/" + filePath + fileName + ".debug", info))
         return SymbolStatus::SymbolsLoaded;
 
+    // In case lib installed into directory that is symlink to another directory on target system,
+    // but `/usr/lib/debug/_lib_path_` with related to this lib debug info is not symlink.
+    std::size_t i = libLoadName.find_last_of("/");
+    if (i != std::string::npos)
+    {
+        filePath = libLoadName.substr(0, i + 1);
+        if (LoadDebuginfoFromFile("/usr/lib/debug/" + filePath + fileName + ".debug", info))
+            return SymbolStatus::SymbolsLoaded;
+    }
+
     return SymbolStatus::SymbolsNotFound;
 }
 
-void InteropLibraries::AddLibrary(const std::string &fullName, std::uintptr_t startAddr, std::uintptr_t endAddr, SymbolStatus &symbolStatus)
+static bool IsCoreCLRLibrary(const std::string &fullName)
+{
+    static const std::vector<std::string> clrLibs{
+        "libclrjit.so",
+        "libcoreclr.so",
+        "libcoreclrtraceptprovider.so",
+        "libhostpolicy.so",
+        "libclrgc.so"
+    };
+
+    for (auto &clrLibName : clrLibs)
+    {
+        if (clrLibName.size() <= fullName.size() &&
+            std::equal(clrLibName.rbegin(), clrLibName.rend(), fullName.rbegin())) // "end with"
+            return true;
+    }
+    return false;
+}
+
+void InteropLibraries::AddLibrary(const std::string &libLoadName, const std::string &fullName, std::uintptr_t startAddr, std::uintptr_t endAddr, SymbolStatus &symbolStatus)
 {
     if (endAddr <= startAddr)
     {
@@ -178,7 +313,8 @@ void InteropLibraries::AddLibrary(const std::string &fullName, std::uintptr_t st
     LibraryInfo &info = m_librariesInfo[startAddr];
     info.fullName = fullName;
     info.libEndAddr = endAddr;
-    symbolStatus = LoadDebuginfo(startAddr, info);
+    symbolStatus = LoadDebuginfo(libLoadName, info);
+    info.isCoreCLR = IsCoreCLRLibrary(fullName);
 
     m_librariesInfoMutex.unlock();
 }
@@ -223,7 +359,8 @@ static std::uintptr_t FindOffsetBySourceAndLineForDwarf(const std::unique_ptr<dw
         bool nameFound = false;
         cu.get_line_table().iterate_file_names([&fileName, &nameFound](dwarf::line_table::file* sourceFile)
         {
-            if (std::equal(fileName.rbegin(), fileName.rend(), sourceFile->path.rbegin())) // "end with"
+            if (fileName.size() <= sourceFile->path.size() &&
+                std::equal(fileName.rbegin(), fileName.rend(), sourceFile->path.rbegin())) // "end with"
             {
                 nameFound = true;
                 return false;
@@ -280,12 +417,15 @@ std::uintptr_t InteropLibraries::FindAddrBySourceAndLineForLib(std::uintptr_t li
     if (find == m_librariesInfo.end())
         return NOT_FOUND;
 
+    if (find->second.isCoreCLR) // NOTE we don't allow setup breakpoint in CoreCLR native code
+        return NOT_FOUND;
+
     std::uintptr_t offset = FindOffsetBySourceAndLineForDwarf(find->second.dw, fileName, lineNum, resolvedLineNum, resolvedFullPath);
     if (offset == NOT_FOUND)
         return NOT_FOUND;
 
     std::uintptr_t addr = libStartAddr + offset; // lib address + offset for line's code
-    resolvedIsThumbCode = IsThumbCode(find->second, addr);
+    resolvedIsThumbCode = IsThumbCode(libStartAddr, find->second, addr);
     return addr;
 }
 
@@ -294,43 +434,216 @@ std::uintptr_t InteropLibraries::FindAddrBySourceAndLine(const std::string &file
 {
     std::lock_guard<std::mutex> lock(m_librariesInfoMutex);
 
-    for (const auto &debugInfo : m_librariesInfo)
+    for (auto &debugInfo : m_librariesInfo)
     {
+        if (debugInfo.second.isCoreCLR) // NOTE we don't allow setup breakpoint in CoreCLR native code
+            continue;
+
         std::uintptr_t offset = FindOffsetBySourceAndLineForDwarf(debugInfo.second.dw, fileName, lineNum, resolvedLineNum, resolvedFullPath);
         if (offset == NOT_FOUND)
             continue;
 
         std::uintptr_t addr = debugInfo.first + offset; // lib address + offset for line's code
-        resolvedIsThumbCode = IsThumbCode(debugInfo.second, addr);
+        resolvedIsThumbCode = IsThumbCode(debugInfo.first, debugInfo.second, addr);
         return addr;
     }
 
     return NOT_FOUND;
 }
 
-bool InteropLibraries::IsThumbCode(std::uintptr_t addr)
+void InteropLibraries::FindLibraryInfoForAddr(std::uintptr_t addr, std::function<void(std::uintptr_t startAddr, LibraryInfo&)> cb)
 {
-#if DEBUGGER_UNIX_ARM
     std::lock_guard<std::mutex> lock(m_librariesInfoMutex);
 
     if (m_librariesInfo.empty() ||
         addr >= m_librariesInfo.rbegin()->second.libEndAddr)
-        return false;
+        return;
 
     auto upper_bound = m_librariesInfo.upper_bound(addr);
     if (upper_bound != m_librariesInfo.begin())
     {
         auto closest_lower = std::prev(upper_bound);
         if (closest_lower->first <= addr && addr < closest_lower->second.libEndAddr)
-            return IsThumbCode(closest_lower->second, addr);
+            cb(closest_lower->first, closest_lower->second);
+    }
+}
+
+static bool FindDwarfDieByAddr(const dwarf::die &d, dwarf::taddr addr, dwarf::die &node)
+{
+    // Scan children first to find most specific DIE
+    for (const auto &child : d)
+    {
+        if (FindDwarfDieByAddr(child, addr, node))
+                return true;
+    }
+
+    switch (d.tag)
+    {
+    case dwarf::DW_TAG::subprogram:
+    case dwarf::DW_TAG::inlined_subroutine:
+        try
+        {
+            if (die_pc_range(d).contains(addr))
+            {
+                node = d;
+                return true;
+            }
+        }
+        catch (std::out_of_range &e) {}
+        catch (dwarf::value_type_mismatch &e) {}
+        break;
+    default:
+        break;
+    }
+
+    return false;
+}
+
+static void ParseDwarfDie(const dwarf::die &node, std::string &methodName, std::string &methodLinkageName)
+{
+    for (auto &attr : node.attributes())
+    {
+        switch (attr.first)
+        {
+        case dwarf::DW_AT::specification:
+            ParseDwarfDie(attr.second.as_reference(), methodName, methodLinkageName);
+            break;
+        case dwarf::DW_AT::abstract_origin:
+            ParseDwarfDie(attr.second.as_reference(), methodName, methodLinkageName);
+            break;
+        case dwarf::DW_AT::name:
+            assert(methodName.empty());
+            methodName = to_string(attr.second);
+            break;
+        case dwarf::DW_AT::linkage_name:
+            assert(methodLinkageName.empty());
+            methodLinkageName = to_string(attr.second);
+        default:
+            break;
+        }
+    }
+}
+
+static bool DemangleCXXABI(const char *mangledName, std::string &realName)
+{
+    // CXX ABI demangle only supported now
+    // https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
+    // https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
+    int demangleStatus;
+    char *demangledName;
+    demangledName = abi::__cxa_demangle(mangledName, 0, 0, &demangleStatus);
+    if (demangledName) // could be not CXX ABI mangle name (for example, plane `C` name)
+    {
+        realName = demangledName;
+        free(demangledName);
+        return true;
     }
-#endif
     return false;
 }
 
-bool InteropLibraries::IsThumbCode(const LibraryInfo &info, std::uintptr_t addr)
+static void FindDataForAddrInDebugInfo(dwarf::dwarf *dw, std::uintptr_t addr, std::string &procName, std::string &fullSourcePath, int &lineNum)
+{
+    if (!dw)
+        return;
+
+    // TODO use `.debug_aranges`
+
+    for (auto &cu : dw->compilation_units())
+    {
+        try
+        {
+            if (!die_pc_range(cu.root()).contains(addr))
+                continue;
+        }
+        catch (std::out_of_range &e) {continue;}
+        catch (dwarf::value_type_mismatch &e) {continue;}
+
+        // Map address to source file and line
+        auto &lt = cu.get_line_table();
+        auto it = lt.find_address(addr);
+        if (it == lt.end())
+            return;
+
+        fullSourcePath = it->file->path;
+        lineNum = it->line;
+
+        // Map address to method name
+        // TODO index/helper/something for looking up address
+        dwarf::die node;
+        if (FindDwarfDieByAddr(cu.root(), addr, node))
+        {
+            std::string methodName;
+            std::string methodLinkageName;
+            ParseDwarfDie(node, methodName, methodLinkageName);
+
+            if (!methodLinkageName.empty())
+            {
+                if (!DemangleCXXABI(methodLinkageName.c_str(), procName))
+                    procName = methodLinkageName + "()";
+            }
+            else if (!methodName.empty())
+                procName = methodName + "()";
+            else
+                procName = "unknown";
+
+            break;
+        }
+    }
+}
+
+void InteropLibraries::FindDataForAddr(std::uintptr_t addr, std::string &libName, std::uintptr_t &libStartAddr, std::string &procName,
+                                       std::uintptr_t &procStartAddr, std::string &fullSourcePath, int &lineNum)
+{
+    FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+    {
+        libName = GetBasename(info.fullName);
+        libStartAddr = startAddr;
+
+        FindDataForAddrInDebugInfo(info.dw.get(), addr - startAddr, procName, fullSourcePath, lineNum);
+        if (!procName.empty())
+            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)
+            {
+                procStartAddr = closest_lower->first;
+                if (!DemangleCXXABI(closest_lower->second.procName.c_str(), procName))
+                    procName = closest_lower->second.procName + "()";
+            }
+        }
+    });
+}
+
+bool InteropLibraries::IsThumbCode(std::uintptr_t addr)
+{
+#if DEBUGGER_UNIX_ARM
+    bool result = false;
+    FindLibraryInfoForAddr(addr, [&](std::uintptr_t startAddr, LibraryInfo &info)
+    {
+        result = IsThumbCode(startAddr, info, addr);
+    });
+    return result;
+#else
+    return false;
+#endif // DEBUGGER_UNIX_ARM
+}
+
+bool InteropLibraries::IsThumbCode(std::uintptr_t libStartAddr, LibraryInfo &info, std::uintptr_t addr)
 {
 #if DEBUGGER_UNIX_ARM
+    if (!info.thumbRegionsValid)
+        CollectThumbCodeRegions(libStartAddr, info);
+
     if (info.thumbRegions.empty() ||
         addr >= info.thumbRegions.rbegin()->second)
         return false;
@@ -342,7 +655,7 @@ bool InteropLibraries::IsThumbCode(const LibraryInfo &info, std::uintptr_t addr)
         if (closest_lower->first <= addr && addr < closest_lower->second)
             return true;
     }
-#endif
+#endif // DEBUGGER_UNIX_ARM
     return false;
 }
 
index 1b639161b1fe9bf2c6ac19f5fbbbbbcb9d9e6c68..8e562c922c769b6c82a3bcf580c0bb3aa672e8e1 100644 (file)
@@ -9,6 +9,7 @@
 #include <memory>
 #include <mutex>
 #include <map>
+#include <functional>
 #include "interfaces/types.h"
 
 
@@ -40,17 +41,35 @@ public:
 #if DEBUGGER_UNIX_ARM
         // All thumb code related adress blocks in form [`start address`, `end address`),
         // where `start address` is `key` and `end address` is `value` of map.
+        bool thumbRegionsValid = false;
         std::map<std::uintptr_t, std::uintptr_t> thumbRegions;
 #endif
+        // Procedure data in case lib don't have debug info, in this case for stacktrace we need start address (for offset calculation) and name.
+        struct proc_data_t
+        {
+            std::uintptr_t endAddr = 0; // have same logic as STL `end()` iterator - "first address after"
+            std::string procName;
+            proc_data_t(std::uintptr_t addr, const std::string &name) :
+                endAddr(addr),
+                procName(name)
+            {}
+        };
+        // Procedure `start address` is `key`.
+        bool proceduresDataValid = false;
+        std::map<std::uintptr_t, proc_data_t> proceduresData;
+        // Is this lib related to CoreCLR (Note, we don't allow debug CoreCLR native code).
+        bool isCoreCLR = false;
     };
 
-    void AddLibrary(const std::string &fullName, std::uintptr_t startAddr, std::uintptr_t endAddr, SymbolStatus &symbolStatus);
+    void AddLibrary(const std::string &libLoadName, const std::string &fullName, std::uintptr_t startAddr, std::uintptr_t endAddr, SymbolStatus &symbolStatus);
     bool RemoveLibrary(const std::string &fullName, std::uintptr_t &startAddr, std::uintptr_t &endAddr);
     void RemoveAllLibraries();
 
     std::uintptr_t FindAddrBySourceAndLine(const std::string &fileName, unsigned lineNum, unsigned &resolvedLineNum, std::string &resolvedFullPath, bool &resolvedIsThumbCode);
     std::uintptr_t FindAddrBySourceAndLineForLib(std::uintptr_t startAddr, const std::string &fileName, unsigned lineNum, unsigned &resolvedLineNum, std::string &resolvedFullPath, bool &resolvedIsThumbCode);
 
+    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 IsThumbCode(std::uintptr_t addr);
 
 private:
@@ -60,13 +79,8 @@ private:
     // Lib's `start address` is `key`.
     std::map<std::uintptr_t, LibraryInfo> m_librariesInfo;
 
-    bool LoadDebuginfoFromFile(std::uintptr_t startAddr, const std::string &fileName, LibraryInfo &info, bool collectElfData = false);
-    SymbolStatus LoadDebuginfo(std::uintptr_t startAddr, LibraryInfo &info);
-
-#if DEBUGGER_UNIX_ARM
-    void CollectThumbCodeRegions(std::uintptr_t startAddr, LibraryInfo &info);
-#endif
-    bool IsThumbCode(const LibraryInfo &info, std::uintptr_t addr);
+    void FindLibraryInfoForAddr(std::uintptr_t addr, std::function<void(std::uintptr_t startAddr, LibraryInfo&)> cb);
+    bool IsThumbCode(std::uintptr_t libStartAddr, LibraryInfo &info, std::uintptr_t addr);
 
     // TODO addr search optimization during breakpoint setup by source + line;
 
index 9fb1c860137dd7631f883751284bdbbaacb66fe7..feeb818152adacad3c88bd63105bdc21dc2b78b6 100644 (file)
@@ -722,7 +722,7 @@ HRESULT CLIProtocol::PrintFrameLocation(const StackFrame &stackFrame, std::strin
 {
     std::ostringstream ss;
 
-    ss << stackFrame.name;
+    ss << stackFrame.methodName;
     if (!stackFrame.source.IsNull())
     {
         ss << " at "  << stackFrame.source.path << ":" << stackFrame.line;
@@ -751,7 +751,20 @@ HRESULT CLIProtocol::PrintFrames(ThreadId threadId, std::string &output, FrameLe
 
     for (const StackFrame &stackFrame : stackFrames)
     {
-        ss << "#" << currentFrame;
+        ss << "#" << currentFrame << ": ";
+        if (stackFrame.unknownFrameAddr)
+        {
+            // Note, `AddrToString()` return string with proper amount of symbols for current arch.
+            // For now, this is 2 ("0x") + number of symbols that need for print max address for current arch.
+            std::string tmpString = ProtocolUtils::AddrToString(stackFrame.addr);
+            std::fill(tmpString.begin(), tmpString.end(), '?');
+            ss << tmpString;
+        }
+        else
+            ss << ProtocolUtils::AddrToString(stackFrame.addr);
+
+        if (!stackFrame.moduleOrLibName.empty())
+            ss << " " << stackFrame.moduleOrLibName << "`";
 
         std::string frameLocation;
         PrintFrameLocation(stackFrame, frameLocation);
index adf418b37a07852930317ff3dfdaf56f4b0e6c6a..1d0d9f7873311af3744756dbb85c9c237a88ebaa 100644 (file)
@@ -130,7 +130,7 @@ static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &out
            << "\",native-offset=\"" << stackFrame.clrAddr.nativeOffset << "\"},";
     }
 
-    ss << "func=\"" << stackFrame.name << "\"";
+    ss << "func=\"" << stackFrame.methodName << "\"";
     if (stackFrame.id)
         ss << ",addr=\"" << ProtocolUtils::AddrToString(stackFrame.addr) << "\"";
 
index ff487138330cbad303978d6c63e35117f1707485..983c28e307484f22ad579ad97d8e7f3519158fcd 100644 (file)
@@ -448,10 +448,10 @@ bool ParseBreakpoint(std::vector<std::string> &args, struct FuncBreak &fb)
     return true;
 }
 
-std::string AddrToString(uint64_t addr)
+std::string AddrToString(std::uintptr_t addr)
 {
     std::ostringstream ss;
-    ss << "0x" << std::setw(2 * sizeof(void*)) << std::setfill('0') << std::hex << addr;
+    ss << "0x" << std::setw(2 * sizeof(std::uintptr_t)) << std::setfill('0') << std::hex << addr;
     return ss.str();
 }
 
index 81379ac8ece88500f99651e22bf66ef404916011..c98c56724203ba0aa132171d3cba0b587ff9d67d 100644 (file)
@@ -68,7 +68,7 @@ namespace ProtocolUtils
     std::string GetConditionPrepareArgs(std::vector<std::string> &args);
     bool ParseBreakpoint(std::vector<std::string> &args, struct LineBreak &lb);
     bool ParseBreakpoint(std::vector<std::string> &args, struct FuncBreak &fb);
-    std::string AddrToString(uint64_t addr);
+    std::string AddrToString(std::uintptr_t addr);
 
 } // namespace ProtocolUtils
 
index 96db9888078c2512cc269112ec3a6c49eaa47a26..450ee28b834f4a4e7f71380380db0b589e295b17 100644 (file)
@@ -77,7 +77,7 @@ void to_json(json &j, const Breakpoint &b) {
 void to_json(json &j, const StackFrame &f) {
     j = json{
         {"id",        int(f.id)},
-        {"name",      f.name},
+        {"name",      f.methodName},
         {"line",      f.line},
         {"column",    f.column},
         {"endLine",   f.endLine},