BuildRequires: unzip
BuildRequires: corefx-managed
BuildRequires: libdlog-devel
+BuildRequires: libunwind-devel
Requires: coreclr
%{!?build_type:%define build_type Release}
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
)
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)
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)
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
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
{\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
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);
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
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
iov.iov_base = ®s;\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
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;
{
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)
// 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 = ®s;
+ 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 ¤tCtx, 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(¤tCtx) == 0)
+ IfFailRet(EmptyContextForTopFrame(pThread, cb));
+ else
+#endif // DEBUGGER_UNIX_ARM
+ IfFailRet(g_pInteropDebugger->UnwindNativeFrames(threadId, firstFrame, GetIP(¤tCtx), 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(¤tCtx, 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(¤tCtx, 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(¤tCtx, 0, sizeof(CONTEXT));
+ IfFailRet(iCorStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) ¤tCtx));
+ // 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(¤tCtx) != 0 && GetFP(¤tCtx) == 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(¤tCtx, 0, sizeof(CONTEXT));
- ULONG32 contextSize;
- IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) ¤tCtx));
- 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(¤tCtx);
+ SetFP(¤tCtx, GetSP(¤tCtx));
+ IfFailRet(iCorStackWalk->SetContext(SET_CONTEXT_FLAG_UNWIND_FRAME, sizeof(CONTEXT), (BYTE*) ¤tCtx));
}
// 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(¤tCtx) == 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, ¤tCtx, 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(¤tCtx), 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(¤tCtx), 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(¤tCtx) == 0)
+ IfFailRet(EmptyContextForTopFrame(pThread, cb));
+ else
+#endif // DEBUGGER_UNIX_ARM
+#endif // INTEROP_DEBUGGING
+ IfFailRet(UnwindNativeFrames(pThread, firstFrame, nullptr, ¤tCtx, cb));
}
- IfFailRet(cb(frameType, pFrame, nullptr, nullptr));
+ IfFailRet(cb(FrameCLRNative, GetIP(¤tCtx), 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;
}
WalkFrames(pThread, [&](
FrameType frameType,
+ std::uintptr_t addr,
ICorDebugFrame *pFrame,
- NativeFrame *pNative,
- ICorDebugFunction *pFunction)
+ NativeFrame *pNative)
{
currentFrame++;
}
}
+#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
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
#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
iov.iov_base = ®s;
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
std::uintptr_t GetBrkAddrByPC(const user_regs_struct ®s);
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
#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"
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) :
{}
// 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;
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
iov.iov_base = ®s;
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)))
{
lock.unlock();
+ ShutdownNativeFramesUnwind();
async_ptrace_shutdown();
}
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
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)
{
if (m_sharedEvalWaiter->GetEvalRunningThreadID() == (DWORD)pid)
{
StopAllRunningThreads();
- WaitAllThreadsStop();
+ WaitThreadStop(g_waitForAllThreads);
m_sharedBreakpoints->InteropStepOverBrk(pid, brkAddr);
m_TIDs[pid].stop_signal = 0;
break;
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()
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())
{
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)
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;
}
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)
{
m_sharedCallbacksQueue = sharedCallbacksQueue;
m_waitpidCV.notify_one(); // notify WaitpidWorker() to start infinite loop
+ InitNativeFramesUnwind(this);
return S_OK;
}
return;
StopAllRunningThreads();
- WaitAllThreadsStop();
+ WaitThreadStop(g_waitForAllThreads);
allThreadsWereStopped = true;
}
iov.iov_base = ®s;
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)
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
#include <thread>
#include <vector>
#include <list>
+#include <functional>
#include <condition_variable>
#include <unordered_map>
#include "interfaces/types.h"
-
+#include "debugger/frames.h"
namespace netcoredbg
{
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
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;
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;
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();
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);
--- /dev/null
+// 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 = ®s;
+ 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*)®s + 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
--- /dev/null
+// 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
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();
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
{
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));
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)))
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;
else
stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::Stale;
- TypePrinter::GetMethodName(pFrame, stackFrame.name);
-
return S_OK;
}
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 = [&] ()
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++;
{
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:
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;
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;
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();
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)
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;
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
{
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
#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
} // 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;
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()))));
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;
// 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;
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)
{
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();
}
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;
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;
}
{
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 < = 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;
if (closest_lower->first <= addr && addr < closest_lower->second)
return true;
}
-#endif
+#endif // DEBUGGER_UNIX_ARM
return false;
}
#include <memory>
#include <mutex>
#include <map>
+#include <functional>
#include "interfaces/types.h"
#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:
// 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;
{
std::ostringstream ss;
- ss << stackFrame.name;
+ ss << stackFrame.methodName;
if (!stackFrame.source.IsNull())
{
ss << " at " << stackFrame.source.path << ":" << stackFrame.line;
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);
<< "\",native-offset=\"" << stackFrame.clrAddr.nativeOffset << "\"},";
}
- ss << "func=\"" << stackFrame.name << "\"";
+ ss << "func=\"" << stackFrame.methodName << "\"";
if (stackFrame.id)
ss << ",addr=\"" << ProtocolUtils::AddrToString(stackFrame.addr) << "\"";
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();
}
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
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},