summaryrefslogtreecommitdiff
path: root/src/pal/src/exception/seh-unwind.cpp
diff options
context:
space:
mode:
authorEugene <Eugene.Zemtsov@microsoft.com>2015-08-20 21:11:31 -0700
committerEugene Zemtsov <Eugene.Zemtsov@microsoft.com>2015-09-15 13:44:42 -0700
commitd0f71d0b22e9012263e4a078d24738c75e384a90 (patch)
tree0bb4840e2f42e39592bcbe18d0c0c68a4db0f376 /src/pal/src/exception/seh-unwind.cpp
parenteb3260e67bc653b56a80fbf0e8a07d78e3a404c1 (diff)
downloadcoreclr-d0f71d0b22e9012263e4a078d24738c75e384a90.tar.gz
coreclr-d0f71d0b22e9012263e4a078d24738c75e384a90.tar.bz2
coreclr-d0f71d0b22e9012263e4a078d24738c75e384a90.zip
Use out-of-proc libunwind to unwind native stack from DAC
Implementation of PAL_VirtualUnwindOutOfProc that uses ptrace libunwind to be able to unwind native stack in debugee. This allows to get valid managed stack for threads that passed HelperMethodFrames.
Diffstat (limited to 'src/pal/src/exception/seh-unwind.cpp')
-rw-r--r--src/pal/src/exception/seh-unwind.cpp231
1 files changed, 228 insertions, 3 deletions
diff --git a/src/pal/src/exception/seh-unwind.cpp b/src/pal/src/exception/seh-unwind.cpp
index 465c459dda..cf899f7f7f 100644
--- a/src/pal/src/exception/seh-unwind.cpp
+++ b/src/pal/src/exception/seh-unwind.cpp
@@ -26,12 +26,20 @@ Abstract:
#endif // !FEATURE_PAL_SXS
#include "pal/context.h"
+#include "pal.h"
#include <dlfcn.h>
#include <exception>
+
#if HAVE_LIBUNWIND_H
+#ifndef __LINUX__
#define UNW_LOCAL_ONLY
+#endif // !__LINUX__
#include <libunwind.h>
-#endif
+#ifdef __LINUX__
+#include <libunwind-ptrace.h>
+#endif // __LINUX__
+#endif // HAVE_LIBUNWIND_H
+
//----------------------------------------------------------------------
// Virtual Unwinding
@@ -226,6 +234,7 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP
int st;
unw_context_t unwContext;
unw_cursor_t cursor;
+
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_)
DWORD64 curPc;
#endif
@@ -251,7 +260,6 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP
return FALSE;
}
#endif
-
st = unw_init_local(&cursor, &unwContext);
if (st < 0)
{
@@ -308,13 +316,230 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP
{
GetContextPointers(&cursor, &unwContext, contextPointers);
}
-
return TRUE;
}
+
#else
#error don't know how to unwind on this platform
#endif
+
+#ifdef __LINUX__
+
+
+static struct LibunwindCallbacksInfoType
+{
+ CONTEXT *Context;
+ ReadMemoryWordCallback readMemCallback;
+} LibunwindCallbacksInfo;
+
+static int get_dyn_info_list_addr(unw_addr_space_t as, unw_word_t *dilap, void *arg)
+{
+ return -UNW_ENOINFO;
+}
+
+static int access_mem(unw_addr_space_t as, unw_word_t addr, unw_word_t *valp, int write, void *arg)
+{
+ if (write)
+ {
+ ASSERT("Memory write must never be called by libunwind during stackwalk");
+ return -UNW_EINVAL;
+ }
+
+ // access_mem sometimes gets called by _UPT_find_proc_info, in such cases arg has a pointer to libunwind internal data
+ // returned by _UPT_create. It makes it impossible to use arg for passing readMemCallback. That's why we have to use global variable.
+ if (LibunwindCallbacksInfo.readMemCallback((SIZE_T)addr, (SIZE_T *)valp))
+ {
+ return UNW_ESUCCESS;
+ }
+ else
+ {
+ return -UNW_EUNSPEC;
+ }
+}
+
+static int access_reg(unw_addr_space_t as, unw_regnum_t regnum, unw_word_t *valp, int write, void *arg)
+{
+ if (write)
+ {
+ ASSERT("Register write must never be called by libunwind during stackwalk");
+ return -UNW_EREADONLYREG;
+ }
+
+ CONTEXT *winContext = LibunwindCallbacksInfo.Context;
+
+ switch (regnum)
+ {
+#if defined(_AMD64_)
+ case UNW_REG_IP: *valp = (unw_word_t) winContext->Rip; break;
+ case UNW_REG_SP: *valp = (unw_word_t) winContext->Rsp; break;
+ case UNW_X86_64_RBP: *valp = (unw_word_t) winContext->Rbp; break;
+ case UNW_X86_64_RBX: *valp = (unw_word_t) winContext->Rbx; break;
+ case UNW_X86_64_R12: *valp = (unw_word_t) winContext->R12; break;
+ case UNW_X86_64_R13: *valp = (unw_word_t) winContext->R13; break;
+ case UNW_X86_64_R14: *valp = (unw_word_t) winContext->R14; break;
+ case UNW_X86_64_R15: *valp = (unw_word_t) winContext->R15; break;
+#elif defined(_ARM_)
+ case UNW_ARM_R13: *valp = (unw_word_t) winContext->Sp; break;
+ case UNW_ARM_R14: *valp = (unw_word_t) winContext->Lr; break;
+ case UNW_ARM_R15: *valp = (unw_word_t) winContext->Pc; break;
+ case UNW_ARM_R4: *valp = (unw_word_t) winContext->R4; break;
+ case UNW_ARM_R5: *valp = (unw_word_t) winContext->R5; break;
+ case UNW_ARM_R6: *valp = (unw_word_t) winContext->R6; break;
+ case UNW_ARM_R7: *valp = (unw_word_t) winContext->R7; break;
+ case UNW_ARM_R8: *valp = (unw_word_t) winContext->R8; break;
+ case UNW_ARM_R9: *valp = (unw_word_t) winContext->R9; break;
+ case UNW_ARM_R10: *valp = (unw_word_t) winContext->R10; break;
+ case UNW_ARM_R11: *valp = (unw_word_t) winContext->R11; break;
+#elif defined(_ARM64_)
+ case UNW_REG_IP: *valp = (unw_word_t) winContext->Pc; break;
+ case UNW_REG_SP: *valp = (unw_word_t) winContext->Sp; break;
+ case UNW_AARCH64_X29: *valp = (unw_word_t) winContext->Fp; break;
+ case UNW_AARCH64_X30: *valp = (unw_word_t) winContext->Lr; break;
+ case UNW_AARCH64_X19: *valp = (unw_word_t) winContext->X19; break;
+ case UNW_AARCH64_X20: *valp = (unw_word_t) winContext->X20; break;
+ case UNW_AARCH64_X21: *valp = (unw_word_t) winContext->X21; break;
+ case UNW_AARCH64_X22: *valp = (unw_word_t) winContext->X22; break;
+ case UNW_AARCH64_X23: *valp = (unw_word_t) winContext->X23; break;
+ case UNW_AARCH64_X24: *valp = (unw_word_t) winContext->X24; break;
+ case UNW_AARCH64_X25: *valp = (unw_word_t) winContext->X25; break;
+ case UNW_AARCH64_X26: *valp = (unw_word_t) winContext->X26; break;
+ case UNW_AARCH64_X27: *valp = (unw_word_t) winContext->X27; break;
+ case UNW_AARCH64_X28: *valp = (unw_word_t) winContext->X28; break;
+#else
+#error unsupported architecture
+#endif
+ default:
+ ASSERT("Attempt to read an unknown register.");
+ return -UNW_EBADREG;
+ }
+ return UNW_ESUCCESS;
+}
+
+static int access_fpreg(unw_addr_space_t as, unw_regnum_t regnum, unw_fpreg_t *fpvalp, int write, void *arg)
+{
+ ASSERT("Not supposed to be ever called");
+ return -UNW_EINVAL;
+}
+
+static int resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg)
+{
+ ASSERT("Not supposed to be ever called");
+ return -UNW_EINVAL;
+}
+
+static int get_proc_name(unw_addr_space_t as, unw_word_t addr, char *bufp, size_t buf_len, unw_word_t *offp, void *arg)
+{
+ ASSERT("Not supposed to be ever called");
+ return -UNW_EINVAL;
+}
+
+static unw_accessors_t unwind_accessors =
+{
+ .find_proc_info = _UPT_find_proc_info,
+ .put_unwind_info = _UPT_put_unwind_info,
+ .get_dyn_info_list_addr = get_dyn_info_list_addr,
+ .access_mem = access_mem,
+ .access_reg = access_reg,
+ .access_fpreg = access_fpreg,
+ .resume = resume,
+ .get_proc_name = get_proc_name
+};
+
+BOOL PAL_VirtualUnwindOutOfProc(CONTEXT *context,
+ KNONVOLATILE_CONTEXT_POINTERS *contextPointers,
+ DWORD pid,
+ ReadMemoryWordCallback readMemCallback)
+{
+ // This function can be executed only by one thread at a time.
+ // The reason for this is that we need to pass context and read mem function to libunwind callbacks
+ // but "arg" is already used by the pointer returned from _UPT_create().
+ // So we resort to using global variables and a lock.
+ struct Lock
+ {
+ CRITICAL_SECTION cs;
+ Lock()
+ {
+ // ctor of a static variable is a thread-safe way to initialize critical section exactly once (clang,gcc)
+ InitializeCriticalSection(&cs);
+ }
+ };
+ struct LockHolder
+ {
+ CRITICAL_SECTION *cs;
+ LockHolder(CRITICAL_SECTION *cs)
+ {
+ this->cs = cs;
+ EnterCriticalSection(cs);
+ }
+
+ ~LockHolder()
+ {
+ LeaveCriticalSection(cs);
+ cs = NULL;
+ }
+ };
+ static Lock lock;
+ LockHolder lockHolder(&lock.cs);
+
+ int st;
+ unw_context_t unwContext;
+ unw_cursor_t cursor;
+ unw_addr_space_t addrSpace = 0;
+ void *libunwindUptPtr = NULL;
+ BOOL result = FALSE;
+
+ LibunwindCallbacksInfo.Context = context;
+ LibunwindCallbacksInfo.readMemCallback = readMemCallback;
+ WinContextToUnwindContext(context, &unwContext);
+ addrSpace = unw_create_addr_space(&unwind_accessors, 0);
+ libunwindUptPtr = _UPT_create(pid);
+ st = unw_init_remote(&cursor, addrSpace, libunwindUptPtr);
+ if (st < 0)
+ {
+ result = FALSE;
+ goto Exit;
+ }
+
+ st = unw_step(&cursor);
+ if (st < 0)
+ {
+ result = FALSE;
+ goto Exit;
+ }
+
+ UnwindContextToWinContext(&cursor, context);
+
+ if (contextPointers != NULL)
+ {
+ GetContextPointers(&cursor, &unwContext, contextPointers);
+ }
+ result = TRUE;
+
+Exit:
+ if (libunwindUptPtr != nullptr)
+ {
+ _UPT_destroy(libunwindUptPtr);
+ }
+ if (addrSpace != 0)
+ {
+ unw_destroy_addr_space(addrSpace);
+ }
+ return result;
+}
+#else // __LINUX__
+
+BOOL PAL_VirtualUnwindOutOfProc(CONTEXT *context,
+ KNONVOLATILE_CONTEXT_POINTERS *contextPointers,
+ DWORD pid,
+ ReadMemoryWordCallback readMemCallback)
+{
+ //UNIXTODO: Implement for Mac flavor of libunwind
+ return FALSE;
+}
+
+#endif // !__LINUX__
+
/*++
Function:
RtlpRaiseException