Implement out of context stack unwinder (#13302)
authorMike McLaughlin <mikem@microsoft.com>
Wed, 16 Aug 2017 00:33:04 +0000 (17:33 -0700)
committerGitHub <noreply@github.com>
Wed, 16 Aug 2017 00:33:04 +0000 (17:33 -0700)
Implement out of context stack unwinder

Decode the eh frame info found in the in-memory module image
and pass it back to the remote libunwind8 to do the unwind.

Added remote-unwind.cpp for all the out of context unwind code.

Added an all managed threads option -all the "clrstack" (sos ClrStack).

The IDebugDataTarget4 feature needs to be enabled for OS X.

Add libunwind license notice to third party notices file.

THIRD-PARTY-NOTICES.TXT
clrdefinitions.cmake
src/ToolBox/SOS/Strike/strike.cpp
src/debug/daccess/dacfn.cpp
src/dlls/mscordac/mscordac_unixexports.src
src/pal/inc/pal.h
src/pal/src/CMakeLists.txt
src/pal/src/exception/remote-unwind.cpp [new file with mode: 0644]
src/pal/src/exception/seh-unwind.cpp

index 03d4d3c..d2ccf3b 100644 (file)
@@ -172,3 +172,27 @@ Greg Parker     gparker@cs.stanford.edu     December 2000
 This code is in the public domain and may be copied or modified without 
 permission. 
 
+License notice for libunwind8 based code
+----------------------------------------
+
+Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P.
+   Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
index b181475..f6b8de4 100644 (file)
@@ -40,6 +40,7 @@ if (CLR_CMAKE_PLATFORM_UNIX)
 
   if(CLR_CMAKE_PLATFORM_DARWIN)
     add_definitions(-D_XOPEN_SOURCE)
+    add_definitions(-DFEATURE_DATATARGET4)
   endif(CLR_CMAKE_PLATFORM_DARWIN)
 
   if (CLR_CMAKE_TARGET_ARCH_AMD64)
index 7e01635..f9d02c5 100644 (file)
@@ -331,7 +331,7 @@ DEBUG_STACK_FRAME g_Frames[MAX_STACK_FRAMES];
 AMD64_CONTEXT g_X64FrameContexts[MAX_STACK_FRAMES];
 
 static HRESULT
-GetContextStackTrace(PULONG pnumFrames)
+GetContextStackTrace(ULONG osThreadId, PULONG pnumFrames)
 {
     PDEBUG_CONTROL4 debugControl4;
     HRESULT hr;
@@ -339,6 +339,14 @@ GetContextStackTrace(PULONG pnumFrames)
     // Do we have advanced capability?
     if ((hr = g_ExtControl->QueryInterface(__uuidof(IDebugControl4), (void **)&debugControl4)) == S_OK)
     {
+        ULONG oldId, id;
+        g_ExtSystem->GetCurrentThreadId(&oldId);
+
+        if ((hr = g_ExtSystem->GetThreadIdBySystemId(osThreadId, &id)) != S_OK) {
+            return hr;
+        }
+        g_ExtSystem->SetCurrentThreadId(id);
+
         // GetContextStackTrace fills g_X64FrameContexts as an array of 
         // contexts packed as target architecture contexts. We cannot 
         // safely cast this as an array of CROSS_PLATFORM_CONTEXT, since 
@@ -353,6 +361,7 @@ GetContextStackTrace(PULONG pnumFrames)
             g_targetMachine->GetContextSize(),
             pnumFrames);
 
+        g_ExtSystem->SetCurrentThreadId(oldId);
         debugControl4->Release();
     }
     return hr;
@@ -418,6 +427,9 @@ void DumpStackInternal(DumpStackFlag *pDSFlag)
     DumpStackWorker(*pDSFlag);
 }
 
+#if defined(FEATURE_PAL) && defined(_TARGET_WIN64_)
+static BOOL UnwindStackFrames(ULONG32 osThreadId);
+#endif
 
 DECLARE_API(DumpStack)
 {
@@ -431,11 +443,13 @@ DECLARE_API(DumpStack)
     DSFlag.top = 0;
     DSFlag.end = 0;
 
+    BOOL unwind = FALSE;
     BOOL dml = FALSE;
     CMDOption option[] = {
         // name, vptr, type, hasValue
         {"-EE", &DSFlag.fEEonly, COBOOL, FALSE},
         {"-n",  &DSFlag.fSuppressSrcInfo, COBOOL, FALSE},
+        {"-unwind",  &unwind, COBOOL, FALSE},
 #ifndef FEATURE_PAL
         {"/d", &dml, COBOOL, FALSE}
 #endif
@@ -459,13 +473,22 @@ DECLARE_API(DumpStack)
 
     EnableDMLHolder enabledml(dml);
 
-    ULONG id = 0;
-    g_ExtSystem->GetCurrentThreadSystemId(&id);
-    ExtOut("OS Thread Id: 0x%x ", id);
+    ULONG sysId = 0, id = 0;
+    g_ExtSystem->GetCurrentThreadSystemId(&sysId);
+    ExtOut("OS Thread Id: 0x%x ", sysId);
     g_ExtSystem->GetCurrentThreadId(&id);
     ExtOut("(%d)\n", id);
 
-    DumpStackInternal(&DSFlag);
+#if defined(FEATURE_PAL) && defined(_TARGET_WIN64_)
+    if (unwind)
+    {
+        UnwindStackFrames(sysId);
+    }
+    else
+#endif
+    {
+        DumpStackInternal(&DSFlag);
+    }
     return Status;
 }
 
@@ -12045,7 +12068,7 @@ public:
         ULONG numNativeFrames = 0;
         if (bFull)
         {
-            hr = GetContextStackTrace(&numNativeFrames);
+            hr = GetContextStackTrace(osID, &numNativeFrames);
             if (FAILED(hr))
             {
                 ExtOut("Failed to get native stack frames: %lx\n", hr);
@@ -12282,15 +12305,43 @@ public:
         
         PrintThread(osid, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals);
     }
-private: 
 
+    static void PrintAllThreads(BOOL bParams, BOOL bLocals, BOOL bSuppressLines, BOOL bGC, BOOL bNative, BOOL bDisplayRegVals)
+    {
+        HRESULT Status;
+
+        DacpThreadStoreData ThreadStore;
+        if ((Status = ThreadStore.Request(g_sos)) != S_OK)
+        {
+            ExtErr("Failed to request ThreadStore\n");
+            return;
+        }
+
+        DacpThreadData Thread;
+        CLRDATA_ADDRESS CurThread = ThreadStore.firstThread;
+        while (CurThread != 0)
+        {
+            if (IsInterrupt())
+                break;
+
+            if ((Status = Thread.Request(g_sos, CurThread)) != S_OK)
+            {
+                ExtErr("Failed to request thread at %p\n", CurThread);
+                return;
+            }
+            ExtOut("OS Thread Id: 0x%x\n", Thread.osThreadId);
+            PrintThread(Thread.osThreadId, bParams, bLocals, bSuppressLines, bGC, bNative, bDisplayRegVals);
+            CurThread = Thread.nextThread;
+        }
+    }
+
+private: 
     static HRESULT CreateStackWalk(ULONG osID, IXCLRDataStackWalk **ppStackwalk)
     {
         HRESULT hr = S_OK;
         ToRelease<IXCLRDataTask> pTask;
 
-        if ((hr = g_ExtSystem->GetCurrentThreadSystemId(&osID)) != S_OK ||
-            (hr = g_clrData->GetTaskByOSThreadID(osID, &pTask)) != S_OK)
+        if ((hr = g_clrData->GetTaskByOSThreadID(osID, &pTask)) != S_OK)
         {
             ExtOut("Unable to walk the managed stack. The current thread is likely not a \n");
             ExtOut("managed thread. You can run !threads to get a list of managed threads in\n");
@@ -12693,6 +12744,7 @@ DECLARE_API(ClrStack)
     BOOL dml = FALSE;
     BOOL bFull = FALSE;
     BOOL bDisplayRegVals = FALSE;
+    BOOL bAllThreads = FALSE;    
     DWORD frameToDumpVariablesFor = -1;
     StringHolder cvariableName;
     ArrayHolder<WCHAR> wvariableName = new NOTHROW WCHAR[mdNameLen];
@@ -12708,6 +12760,7 @@ DECLARE_API(ClrStack)
     CMDOption option[] = 
     {   // name, vptr, type, hasValue
         {"-a", &bAll, COBOOL, FALSE},
+        {"-all", &bAllThreads, COBOOL, FALSE},
         {"-p", &bParams, COBOOL, FALSE},
         {"-l", &bLocals, COBOOL, FALSE},
         {"-n", &bSuppressLines, COBOOL, FALSE},
@@ -12765,7 +12818,12 @@ DECLARE_API(ClrStack)
         return ClrStackImplWithICorDebug::ClrStackFromPublicInterface(bParams, bLocals, FALSE, wvariableName, frameToDumpVariablesFor);
     }
     
-    ClrStackImpl::PrintCurrentThread(bParams, bLocals, bSuppressLines, bGC, bFull, bDisplayRegVals);
+    if (bAllThreads) {
+        ClrStackImpl::PrintAllThreads(bParams, bLocals, bSuppressLines, bGC, bFull, bDisplayRegVals);
+    }
+    else {
+        ClrStackImpl::PrintCurrentThread(bParams, bLocals, bSuppressLines, bGC, bFull, bDisplayRegVals);
+    }
     
     return S_OK;
 }
@@ -13297,7 +13355,7 @@ HRESULT CALLBACK ImplementEFNStackTrace(
     ULONG numFrames = 0;
     BOOL bInNative = TRUE;
 
-    Status = GetContextStackTrace(&numFrames);
+    Status = GetContextStackTrace(ThreadId, &numFrames);
     if (FAILED(Status))
     {
         goto Exit;
@@ -14597,3 +14655,86 @@ DECLARE_API(Help)
     
     return S_OK;
 }
+
+#if defined(FEATURE_PAL) && defined(_TARGET_WIN64_)
+
+static BOOL 
+ReadMemoryAdapter(PVOID address, PVOID buffer, SIZE_T size)
+{
+    ULONG fetched;
+    HRESULT hr = g_ExtData->ReadVirtual(TO_CDADDR(address), buffer, size, &fetched);
+    return SUCCEEDED(hr);
+}
+
+static BOOL
+GetStackFrame(CONTEXT* context, ULONG numNativeFrames)
+{
+    KNONVOLATILE_CONTEXT_POINTERS contextPointers;
+    memset(&contextPointers, 0, sizeof(contextPointers));
+
+    ULONG64 baseAddress;
+    HRESULT hr = g_ExtSymbols->GetModuleByOffset(context->Rip, 0, NULL, &baseAddress);
+    if (FAILED(hr))
+    {
+        PDEBUG_STACK_FRAME frame = &g_Frames[0];
+        for (int i = 0; i < numNativeFrames; i++, frame++) {
+            if (frame->InstructionOffset == context->Rip)
+            {
+                if ((i + 1) >= numNativeFrames) {
+                    return FALSE;
+                }
+                memcpy(context, &(g_X64FrameContexts[i + 1]), sizeof(*context));
+                return TRUE;
+            }
+        }
+        return FALSE;
+    }
+    if (!PAL_VirtualUnwindOutOfProc(context, &contextPointers, baseAddress, ReadMemoryAdapter))
+    {
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static BOOL
+UnwindStackFrames(ULONG32 osThreadId)
+{
+    ULONG numNativeFrames = 0;
+    HRESULT hr = GetContextStackTrace(osThreadId, &numNativeFrames);
+    if (FAILED(hr))
+    {
+        return FALSE;
+    }
+    CONTEXT context;
+    memset(&context, 0, sizeof(context));
+    context.ContextFlags = CONTEXT_FULL;
+
+    hr = g_ExtSystem->GetThreadContextById(osThreadId, CONTEXT_FULL, sizeof(context), (PBYTE)&context);
+    if (FAILED(hr))
+    {
+        return FALSE;
+    }
+    TableOutput out(3, POINTERSIZE_HEX, AlignRight);
+    out.WriteRow("RSP", "RIP", "Call Site");
+
+    DEBUG_STACK_FRAME nativeFrame;
+    memset(&nativeFrame, 0, sizeof(nativeFrame));
+
+    do 
+    {
+        if (context.Rip == 0)
+        {
+            break;
+        }
+        nativeFrame.InstructionOffset = context.Rip;
+        nativeFrame.ReturnOffset = context.Rip;
+        nativeFrame.FrameOffset = context.Rbp;
+        nativeFrame.StackOffset = context.Rsp;
+        ClrStackImpl::PrintNativeStackFrame(out, &nativeFrame, FALSE);
+
+    } while (GetStackFrame(&context, numNativeFrames));
+
+    return TRUE;
+}
+
+#endif // FEATURE_PAL && _TARGET_WIN64_
index 2f7a98d..78a7b9f 100644 (file)
@@ -203,8 +203,7 @@ DacWriteAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx)
 
     HRESULT status;
 
-    status = g_dacImpl->m_pMutableTarget->
-        WriteVirtual(addr, (PBYTE)buffer, size);
+    status = g_dacImpl->m_pMutableTarget->WriteVirtual(addr, (PBYTE)buffer, size);
     if (status != S_OK)
     {
         if (throwEx)
@@ -218,6 +217,13 @@ DacWriteAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx)
 }
 
 #ifdef FEATURE_PAL
+
+static BOOL DacReadAllAdapter(PVOID address, PVOID buffer, SIZE_T size)
+{
+    HRESULT hr = DacReadAll((TADDR)address, (PVOID)buffer, size, false);
+    return SUCCEEDED(hr);
+}
+
 HRESULT 
 DacVirtualUnwind(DWORD threadId, PT_CONTEXT context, PT_KNONVOLATILE_CONTEXT_POINTERS contextPointers)
 {
@@ -233,15 +239,28 @@ DacVirtualUnwind(DWORD threadId, PT_CONTEXT context, PT_KNONVOLATILE_CONTEXT_POI
         memset(contextPointers, 0, sizeof(T_KNONVOLATILE_CONTEXT_POINTERS));
     }
 
+    HRESULT hr = S_OK;
+
+#ifdef FEATURE_DATATARGET4
     ReleaseHolder<ICorDebugDataTarget4> dt;
-    HRESULT hr = g_dacImpl->m_pTarget->QueryInterface(IID_ICorDebugDataTarget4, (void **)&dt);
+    hr = g_dacImpl->m_pTarget->QueryInterface(IID_ICorDebugDataTarget4, (void **)&dt);
     if (SUCCEEDED(hr))
     {
         hr = dt->VirtualUnwind(threadId, sizeof(CONTEXT), (BYTE*)context);
     }
+    else 
+#endif
+    {
+        SIZE_T baseAddress = DacGlobalBase();
+        if (baseAddress == 0 || !PAL_VirtualUnwindOutOfProc(context, contextPointers, baseAddress, DacReadAllAdapter))
+        {
+            hr = E_FAIL;
+        }
+    }
 
     return hr;
 }
+
 #endif // FEATURE_PAL
 
 // DacAllocVirtual - Allocate memory from the target process
index 5ec7bfe..99ed375 100644 (file)
@@ -41,6 +41,7 @@ PAL__wcstoui64
 PAL_wcstoul
 PAL_iswprint
 PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange
+PAL_VirtualUnwindOutOfProc
 PAL_wcslen
 PAL_wcsncmp
 PAL_wcsrchr
index c749e77..09ad199 100644 (file)
@@ -2570,14 +2570,11 @@ size_t
 PALAPI
 PAL_GetLogicalProcessorCacheSizeFromOS(VOID);
 
-typedef BOOL (*ReadMemoryWordCallback)(SIZE_T address, SIZE_T *value);
+typedef BOOL(*UnwindReadMemoryCallback)(PVOID address, PVOID buffer, SIZE_T size);
 
 PALIMPORT BOOL PALAPI PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers);
 
-PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, 
-                                                 KNONVOLATILE_CONTEXT_POINTERS *contextPointers, 
-                                                 DWORD pid, 
-                                                 ReadMemoryWordCallback readMemCallback);
+PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback);
 
 #define GetLogicalProcessorCacheSizeFromOS PAL_GetLogicalProcessorCacheSizeFromOS
 
index b8a9fe9..578b116 100644 (file)
@@ -147,6 +147,7 @@ set(SOURCES
   debug/debug.cpp
   exception/seh.cpp
   exception/signal.cpp
+  exception/remote-unwind.cpp
   file/directory.cpp
   file/disk.cpp
   file/file.cpp
diff --git a/src/pal/src/exception/remote-unwind.cpp b/src/pal/src/exception/remote-unwind.cpp
new file mode 100644 (file)
index 0000000..18733de
--- /dev/null
@@ -0,0 +1,1086 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*++
+
+Module Name:
+
+    remote-unwind.cpp
+
+Abstract:
+
+    Implementation of out of context unwind using libunwind8
+    remote unwind API.
+
+This file contains code based on libunwind8
+
+Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P.
+   Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--*/
+
+#include "config.h"
+#include "pal/palinternal.h"
+#include "pal/dbgmsg.h"
+#include "pal/critsect.h"
+#include "pal/debug.h"
+#include "pal_endian.h"
+#include "pal.h"
+#include <dlfcn.h>
+
+#if HAVE_LIBUNWIND_H
+#ifndef __linux__
+#define UNW_LOCAL_ONLY
+#endif // !__linux__       
+#include <libunwind.h>
+#endif // HAVE_LIBUNWIND_H
+
+SET_DEFAULT_DEBUG_CHANNEL(EXCEPT);
+
+// Only used on the AMD64 build
+#if defined(_AMD64_) && defined(HAVE_UNW_GET_ACCESSORS)
+
+#include <elf.h>
+#include <link.h>
+
+#define Ehdr   ElfW(Ehdr)
+#define Phdr   ElfW(Phdr)
+#define Shdr   ElfW(Shdr)
+#define Nhdr   ElfW(Nhdr)
+#define Dyn    ElfW(Dyn)
+
+extern void UnwindContextToWinContext(unw_cursor_t *cursor, CONTEXT *winContext);
+extern void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, KNONVOLATILE_CONTEXT_POINTERS *contextPointers);
+
+typedef struct _libunwindInfo
+{
+    SIZE_T BaseAddress;
+    CONTEXT *Context;
+    UnwindReadMemoryCallback ReadMemory;
+} libunwindInfo;
+
+#define DW_EH_VERSION           1
+
+// DWARF Pointer-Encoding (PEs).
+//
+// Pointer-Encodings were invented for the GCC exception-handling
+// support for C++, but they represent a rather generic way of
+// describing the format in which an address/pointer is stored.
+// The Pointer-Encoding format is partially documented in Linux Base
+// Spec v1.3 (http://www.linuxbase.org/spec/).
+
+#define DW_EH_PE_FORMAT_MASK    0x0f    // format of the encoded value
+#define DW_EH_PE_APPL_MASK      0x70    // how the value is to be applied
+#define DW_EH_PE_indirect       0x80    // Flag bit. If set, the resulting pointer is the
+                                        //  address of the word that contains the final address
+// Pointer-encoding formats
+#define DW_EH_PE_omit           0xff
+#define DW_EH_PE_ptr            0x00    // pointer-sized unsigned value
+#define DW_EH_PE_uleb128        0x01    // unsigned LE base-128 value
+#define DW_EH_PE_udata2         0x02    // unsigned 16-bit value
+#define DW_EH_PE_udata4         0x03    // unsigned 32-bit value
+#define DW_EH_PE_udata8         0x04    // unsigned 64-bit value
+#define DW_EH_PE_sleb128        0x09    // signed LE base-128 value
+#define DW_EH_PE_sdata2         0x0a    // signed 16-bit value
+#define DW_EH_PE_sdata4         0x0b    // signed 32-bit value
+#define DW_EH_PE_sdata8         0x0c    // signed 64-bit value
+
+// Pointer-encoding application
+#define DW_EH_PE_absptr         0x00    // absolute value
+#define DW_EH_PE_pcrel          0x10    // rel. to addr. of encoded value
+#define DW_EH_PE_textrel        0x20    // text-relative (GCC-specific???)
+#define DW_EH_PE_datarel        0x30    // data-relative
+
+// The following are not documented by LSB v1.3, yet they are used by
+// GCC, presumably they aren't documented by LSB since they aren't
+// used on Linux
+#define DW_EH_PE_funcrel        0x40    // start-of-procedure-relative
+#define DW_EH_PE_aligned        0x50    // aligned pointer
+
+#define DWARF_CIE_VERSION       3       // GCC emits version 1???
+
+// DWARF frame header
+typedef struct _eh_frame_hdr
+{
+    unsigned char version;
+    unsigned char eh_frame_ptr_enc;
+    unsigned char fde_count_enc;
+    unsigned char table_enc;
+    // The rest of the header is variable-length and consists of the
+    // following members:
+    //
+    //   encoded_t eh_frame_ptr;
+    //   encoded_t fde_count;
+    //   struct
+    //   {
+    //      encoded_t start_ip;     // first address covered by this FDE
+    //      encoded_t fde_offset;   // offset of the FDE
+    //   } binary_search_table[fde_count]; 
+} eh_frame_hdr;
+
+// "DW_EH_PE_datarel|DW_EH_PE_sdata4" encoded fde table entry
+typedef struct _table_entry
+{
+    int32_t start_ip;
+    int32_t fde_offset;
+} table_entry;
+
+// DWARF unwind info
+typedef struct dwarf_cie_info
+{
+    unw_word_t cie_instr_start;     // start addr. of CIE "initial_instructions"
+    unw_word_t cie_instr_end;       // end addr. of CIE "initial_instructions"
+    unw_word_t fde_instr_start;     // start addr. of FDE "instructions"
+    unw_word_t fde_instr_end;       // end addr. of FDE "instructions"
+    unw_word_t code_align;          // code-alignment factor
+    unw_word_t data_align;          // data-alignment factor
+    unw_word_t ret_addr_column;     // column of return-address register
+    unw_word_t handler;             // address of personality-routine
+    uint16_t abi;
+    uint16_t tag;
+    uint8_t fde_encoding;
+    uint8_t lsda_encoding;
+    unsigned int sized_augmentation : 1;
+    unsigned int have_abi_marker : 1;
+    unsigned int signal_frame : 1;
+} dwarf_cie_info_t;
+
+static bool 
+ReadValue8(const libunwindInfo* info, unw_word_t* addr, uint8_t* valp)
+{
+    uint8_t value;
+    if (!info->ReadMemory((PVOID)*addr, &value, sizeof(value))) {
+        return false;
+    }
+    *addr += sizeof(value);
+    *valp = value;
+    return true;
+}
+
+static bool 
+ReadValue16(const libunwindInfo* info, unw_word_t* addr, uint16_t* valp)
+{
+    uint16_t value;
+    if (!info->ReadMemory((PVOID)*addr, &value, sizeof(value))) {
+        return false;
+    }
+    *addr += sizeof(value);
+    *valp = VAL16(value);
+    return true;
+}
+
+static bool 
+ReadValue32(const libunwindInfo* info, unw_word_t* addr, uint32_t* valp)
+{
+    uint32_t value;
+    if (!info->ReadMemory((PVOID)*addr, &value, sizeof(value))) {
+        return false;
+    }
+    *addr += sizeof(value);
+    *valp = VAL32(value);
+    return true;
+}
+
+static bool 
+ReadValue64(const libunwindInfo* info, unw_word_t* addr, uint64_t* valp)
+{
+    uint64_t value;
+    if (!info->ReadMemory((PVOID)*addr, &value, sizeof(value))) {
+        return false;
+    }
+    *addr += sizeof(value);
+    *valp = VAL64(value);
+    return true;
+}
+
+static bool 
+ReadPointer(const libunwindInfo* info, unw_word_t* addr, unw_word_t* valp)
+{
+#ifdef BIT64
+    uint64_t val64;
+    if (ReadValue64(info, addr, &val64)) {
+        *valp = val64;
+        return true;
+    }
+#else
+    uint32_t val32;
+    if (ReadValue32(info, addr, &val32)) {
+        *valp = val32;
+        return true;
+    }
+#endif
+    return false;
+}
+
+// Read a unsigned "little-endian base 128" value. See Chapter 7.6 of DWARF spec v3.
+static bool 
+ReadULEB128(const libunwindInfo* info, unw_word_t* addr, unw_word_t* valp)
+{
+    unw_word_t value = 0;
+    unsigned char byte;
+    int shift = 0;
+
+    do
+    {
+        if (!ReadValue8(info, addr, &byte)) {
+            return false;
+        }
+        value |= ((unw_word_t)byte & 0x7f) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+
+    *valp = value;
+    return true;
+}
+
+// Read a signed "little-endian base 128" value. See Chapter 7.6 of DWARF spec v3.
+static bool 
+ReadSLEB128(const libunwindInfo* info, unw_word_t* addr, unw_word_t* valp)
+{
+    unw_word_t value = 0;
+    unsigned char byte;
+    int shift = 0;
+
+    do
+    {
+        if (!ReadValue8(info, addr, &byte)) {
+            return false;
+        }
+        value |= ((unw_word_t)byte & 0x7f) << shift;
+        shift += 7;
+    } while (byte & 0x80);
+
+    if ((shift < (8 * sizeof(unw_word_t))) && ((byte & 0x40) != 0)) {
+        value |= ((unw_word_t)-1) << shift;
+    }
+
+    *valp = value;
+    return true;
+}
+
+static bool 
+ReadEncodedPointer(const libunwindInfo* info, unw_word_t* addr, unsigned char encoding, unw_word_t funcRel, unw_word_t* valp)
+{
+    unw_word_t initialAddr = *addr;
+    uint16_t value16;
+    uint32_t value32;
+    uint64_t value64;
+    unw_word_t value;
+
+    if (encoding == DW_EH_PE_omit)
+    {
+        *valp = 0;
+        return true;
+    }
+    else if (encoding == DW_EH_PE_aligned)
+    {
+        int size = sizeof(unw_word_t);
+        *addr = (initialAddr + size - 1) & -size;
+        return ReadPointer(info, addr, valp);
+    }
+
+    switch (encoding & DW_EH_PE_FORMAT_MASK)
+    {
+    case DW_EH_PE_ptr:
+        if (!ReadPointer(info, addr, &value)) {
+            return false;
+        }
+        break;
+
+    case DW_EH_PE_uleb128:
+        if (!ReadULEB128(info, addr, &value)) {
+            return false;
+        }
+        break;
+
+    case DW_EH_PE_sleb128:
+        if (!ReadSLEB128(info, addr, &value)) {
+            return false;
+        }
+        break;
+
+    case DW_EH_PE_udata2:
+        if (!ReadValue16(info, addr, &value16)) {
+            return false;
+        }
+        value = value16;
+        break;
+
+    case DW_EH_PE_udata4:
+        if (!ReadValue32(info, addr, &value32)) {
+            return false;
+        }
+        value = value32;
+        break;
+
+    case DW_EH_PE_udata8:
+        if (!ReadValue64(info, addr, &value64)) {
+            return false;
+        }
+        value = value64;
+        break;
+
+    case DW_EH_PE_sdata2:
+        if (!ReadValue16(info, addr, &value16)) {
+            return false;
+        }
+        value = (int16_t)value16;
+        break;
+
+    case DW_EH_PE_sdata4:
+        if (!ReadValue32(info, addr, &value32)) {
+            return false;
+        }
+        value = (int32_t)value32;
+        break;
+
+    case DW_EH_PE_sdata8:
+        if (!ReadValue64(info, addr, &value64)) {
+            return false;
+        }
+        value = (int64_t)value64;
+        break;
+
+    default:
+        ASSERT("ReadEncodedPointer: invalid encoding format %x\n", encoding);
+        return false;
+    }
+
+    // 0 is a special value and always absolute
+    if (value == 0) {
+        *valp = 0;
+        return true;
+    }
+
+    switch (encoding & DW_EH_PE_APPL_MASK)
+    {
+    case DW_EH_PE_absptr:
+        break;
+
+    case DW_EH_PE_pcrel:
+        value += initialAddr;
+        break;
+
+    case DW_EH_PE_funcrel:
+        _ASSERTE(funcRel != UINTPTR_MAX);
+        value += funcRel;
+        break;
+
+    case DW_EH_PE_textrel:
+    case DW_EH_PE_datarel:
+    default:
+        ASSERT("ReadEncodedPointer: invalid application type %x\n", encoding);
+        return false;
+    }
+
+    if (encoding & DW_EH_PE_indirect)
+    {
+        unw_word_t indirect_addr = value;
+        if (!ReadPointer(info, &indirect_addr, &value)) {
+            return false;
+        }
+    }
+
+    *valp = value;
+    return true;
+}
+
+static bool 
+LookupTableEntry(const libunwindInfo* info, int32_t ip, unw_word_t tableAddr, size_t tableCount, table_entry* entry, bool* found)
+{
+    size_t low, high, mid;
+    unw_word_t addr;
+    int32_t start_ip;
+
+    *found = false;
+
+    // do a binary search on table
+    for (low = 0, high = tableCount; low < high;)
+    {
+        mid = (low + high) / 2;
+        addr = tableAddr + (mid * sizeof(table_entry));
+
+        if (!ReadValue32(info, &addr, (uint32_t*)&start_ip)) {
+            return false;
+        }
+        if (ip < start_ip) {
+            high = mid;
+        }
+        else {
+            low = mid + 1;
+        }
+    }
+
+    if (high > 0) {
+        addr = tableAddr + ((high - 1) * sizeof(table_entry));
+        // Assumes that the table_entry is two 32 bit values
+        _ASSERTE(sizeof(*entry) == sizeof(uint64_t));
+        if (!ReadValue64(info, &addr, (uint64_t*)entry)) {
+            return false;
+        }
+        *found = true;
+    }
+
+    return true;
+}
+
+static bool
+ParseCie(const libunwindInfo* info, unw_word_t addr, dwarf_cie_info_t* dci)
+{
+    uint8_t ch, version, fdeEncoding, handlerEncoding;
+    unw_word_t cieLength, cieEndAddr;
+    uint32_t value32;
+    uint64_t value64;
+
+    memset(dci, 0, sizeof (*dci));
+
+    // Pick appropriate default for FDE-encoding. DWARF spec says
+    // start-IP (initial_location) and the code-size (address_range) are
+    // "address-unit sized constants".  The `R' augmentation can be used
+    // to override this, but by default, we pick an address-sized unit
+    // for fde_encoding.
+#if BIT64
+    fdeEncoding = DW_EH_PE_udata8;
+#else
+    fdeEncoding = DW_EH_PE_udata4;
+#endif
+
+    dci->lsda_encoding = DW_EH_PE_omit;
+    dci->handler = 0;
+
+    if (!ReadValue32(info, &addr, &value32)) {
+        return false;
+    }
+
+    if (value32 != 0xffffffff)
+    {
+        // The CIE is in the 32-bit DWARF format
+        uint32_t cieId;
+
+        // DWARF says CIE id should be 0xffffffff, but in .eh_frame, it's 0
+        const uint32_t expectedId = 0;
+
+        cieLength = value32;
+        cieEndAddr = addr + cieLength;
+
+        if (!ReadValue32(info, &addr, &cieId)) {
+            return false;
+        }
+        if (cieId != expectedId) {
+            ASSERT("ParseCie: unexpected cie id %x\n", cieId);
+            return false;
+        }
+    }
+    else
+    {
+        // The CIE is in the 64-bit DWARF format
+        uint64_t cieId;
+
+        // DWARF says CIE id should be 0xffffffffffffffff, but in .eh_frame, it's 0
+        const uint64_t expectedId = 0;
+
+        if (!ReadValue64(info, &addr, &value64)) {
+            return false;
+        }
+        cieLength = value64;
+        cieEndAddr = addr + cieLength;
+
+        if (!ReadValue64(info, &addr, &cieId)) {
+            return false;
+        }
+        if (cieId != expectedId) {
+            ASSERT("ParseCie: unexpected cie id %lx\n", cieId);
+            return false;
+        }
+    }
+    dci->cie_instr_end = cieEndAddr;
+
+    if (!ReadValue8(info, &addr, &version)) {
+        return false;
+    }
+    if (version != 1 && version != DWARF_CIE_VERSION) {
+        ASSERT("ParseCie: invalid cie version %x\n", version);
+        return false;
+    }
+
+    // Read the augmentation string
+    uint8_t augmentationString[8];
+    memset(augmentationString, 0, sizeof(augmentationString));
+
+    for (int i = 0; i < sizeof(augmentationString); i++)
+    {
+        if (!ReadValue8(info, &addr, &ch)) {
+            return false;
+        }
+        if (ch == 0) {
+            break;
+        }
+        augmentationString[i] = ch;
+    }
+
+    // Read the code and data alignment
+    if (!ReadULEB128(info, &addr, &dci->code_align)) {
+        return false;
+    }
+    if (!ReadSLEB128(info, &addr, &dci->data_align)) {
+        return false;
+    }
+
+    // Read the return-address column either as a u8 or as a uleb128
+    if (version == 1)
+    {
+        if (!ReadValue8(info, &addr, &ch)) {
+            return false;
+        }
+        dci->ret_addr_column = ch;
+    }
+    else
+    {
+        if (!ReadULEB128(info, &addr, &dci->ret_addr_column)) {
+            return false;
+        }
+    }
+
+    // Parse the augmentation string
+    for (int i = 0; i < sizeof(augmentationString); i++)
+    {
+        bool done = false;
+        unw_word_t augmentationSize;
+
+        switch (augmentationString[i])
+        {
+        case '\0':
+            done = true;
+            break;
+
+        case 'z':
+            dci->sized_augmentation = 1;
+            if (!ReadULEB128(info, &addr, &augmentationSize)) {
+                return false;
+            }
+            break;
+
+        case 'L':
+            // read the LSDA pointer-encoding format
+            if (!ReadValue8(info, &addr, &ch)) {
+                return false;
+            }
+            dci->lsda_encoding = ch;
+            break;
+
+        case 'R':
+            // read the FDE pointer-encoding format
+            if (!ReadValue8(info, &addr, &fdeEncoding)) {
+                return false;
+            }
+            break;
+
+        case 'P':
+            // read the personality-routine pointer-encoding format
+            if (!ReadValue8(info, &addr, &handlerEncoding)) {
+                return false;
+            }
+            if (!ReadEncodedPointer(info, &addr, handlerEncoding, UINTPTR_MAX, &dci->handler)) {
+                return false;
+            }
+            break;
+
+        case 'S':
+           // This is a signal frame
+           dci->signal_frame = 1;
+
+           // Temporarily set it to one so dwarf_parse_fde() knows that
+           // it should fetch the actual ABI/TAG pair from the FDE.
+           dci->have_abi_marker = 1;
+           break;
+
+        default:
+            if (dci->sized_augmentation) {
+                // If we have the size of the augmentation body, we can skip
+                // over the parts that we don't understand, so we're OK
+                done = true;
+                break;
+            }
+            ASSERT("ParseCie: unexpected argumentation string '%s'\n", augmentationString[i]);
+            return false;
+        }
+
+        if (done) {
+            break;
+        }
+    }
+    dci->fde_encoding = fdeEncoding;
+    dci->cie_instr_start = addr;
+    return true;
+}
+
+static bool
+ExtractProcInfoFromFde(const libunwindInfo* info, unw_word_t* addrp, unw_proc_info_t *pip, int need_unwind_info)
+{
+    unw_word_t addr = *addrp, fdeEndAddr, cieOffsetAddr, cieAddr;
+    uint32_t value32;
+    uint64_t value64;
+
+    if (!ReadValue32(info, &addr, &value32)) {
+        return false;
+    }
+    if (value32 != 0xffffffff)
+    {
+        int32_t cieOffset = 0;
+
+        // In some configurations, an FDE with a 0 length indicates the end of the FDE-table
+        if (value32 == 0) {
+            return false;
+        }
+        // the FDE is in the 32-bit DWARF format */
+        *addrp = fdeEndAddr = addr + value32;
+        cieOffsetAddr = addr;
+
+        if (!ReadValue32(info, &addr, (uint32_t*)&cieOffset)) {
+            return false;
+        }
+        // Ignore CIEs (happens during linear search)
+        if (cieOffset == 0) {
+            return true;
+        }
+        // DWARF says that the CIE_pointer in the FDE is a .debug_frame-relative offset, 
+        // but the GCC-generated .eh_frame sections instead store a "pcrelative" offset, 
+        // which is just as fine as it's self-contained
+        cieAddr = cieOffsetAddr - cieOffset;
+    }
+    else 
+    {
+        int64_t cieOffset = 0;
+
+        // the FDE is in the 64-bit DWARF format */
+        if (!ReadValue64(info, &addr, (uint64_t*)&value64)) {
+            return false;
+        }
+        *addrp = fdeEndAddr = addr + value64;
+        cieOffsetAddr = addr;
+
+        if (!ReadValue64(info, &addr, (uint64_t*)&cieOffset)) {
+            return false;
+        }
+        // Ignore CIEs (happens during linear search)
+        if (cieOffset == 0) {
+            return true;
+        }
+        // DWARF says that the CIE_pointer in the FDE is a .debug_frame-relative offset, 
+        // but the GCC-generated .eh_frame sections instead store a "pcrelative" offset, 
+        // which is just as fine as it's self-contained
+        cieAddr = (unw_word_t)((uint64_t)cieOffsetAddr - cieOffset);
+    }
+
+    dwarf_cie_info_t dci;
+    if (!ParseCie(info, cieAddr, &dci)) {
+        return false;
+    }
+
+    unw_word_t ipStart, ipRange;
+    if (!ReadEncodedPointer(info, &addr, dci.fde_encoding, UINTPTR_MAX, &ipStart)) {
+        return false;
+    }
+
+    // IP-range has same encoding as FDE pointers, except that it's always an absolute value
+    uint8_t ipRangeEncoding = dci.fde_encoding & DW_EH_PE_FORMAT_MASK;
+    if (!ReadEncodedPointer(info, &addr, ipRangeEncoding, UINTPTR_MAX, &ipRange)) {
+        return false;
+    }
+    pip->start_ip = ipStart;
+    pip->end_ip = ipStart + ipRange;
+    pip->handler = dci.handler;
+
+    unw_word_t augmentationSize, augmentationEndAddr;
+    if (dci.sized_augmentation) {
+        if (!ReadULEB128(info, &addr, &augmentationSize)) {
+            return false;
+        }
+        augmentationEndAddr = addr + augmentationSize;
+    }
+
+    // Read language specific data area address
+    if (!ReadEncodedPointer(info, &addr, dci.lsda_encoding, pip->start_ip, &pip->lsda)) {
+        return false;
+    }
+
+    // Now fill out the proc info if requested
+    if (need_unwind_info)
+    {
+        if (dci.have_abi_marker)
+        {
+            if (!ReadValue16(info, &addr, &dci.abi)) {
+                return false;
+            }
+            if (!ReadValue16(info, &addr, &dci.tag)) {
+                return false;
+            }
+        }
+        if (dci.sized_augmentation) {
+            dci.fde_instr_start = augmentationEndAddr;
+        }
+        else {
+            dci.fde_instr_start = addr;
+        }
+        dci.fde_instr_end = fdeEndAddr;
+
+        pip->format = UNW_INFO_FORMAT_TABLE;
+        pip->unwind_info_size = sizeof(dci);
+        pip->unwind_info = malloc(sizeof(dci));
+        if (pip->unwind_info == nullptr) {
+            return -UNW_ENOMEM;
+        }
+        memcpy(pip->unwind_info, &dci, sizeof(dci));
+    }
+
+    return true;
+}
+
+
+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\n");
+        return -UNW_EINVAL;
+    }
+    const auto *info = (libunwindInfo*)arg;
+
+    if (info->ReadMemory((PVOID)addr, valp, sizeof(*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\n");
+        return -UNW_EREADONLYREG;
+    }
+
+    const auto *info = (libunwindInfo*)arg;
+    CONTEXT *winContext = info->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\n");
+        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\n");
+    return -UNW_EINVAL;
+}
+
+static int 
+resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg)
+{
+    ASSERT("Not supposed to be ever called\n");
+    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\n");
+    return -UNW_EINVAL;
+}
+
+static int 
+find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pip, int need_unwind_info, void *arg)
+{
+    const auto *info = (libunwindInfo*)arg;
+    memset(pip, 0, sizeof(*pip));
+
+    Ehdr ehdr;
+    if (!info->ReadMemory((void*)info->BaseAddress, &ehdr, sizeof(ehdr))) {
+        ERROR("ELF: reading ehdr %p\n", info->BaseAddress);
+        return -UNW_EINVAL;
+    }
+    Phdr* phdrAddr = reinterpret_cast<Phdr*>(info->BaseAddress + ehdr.e_phoff);
+    int phnum = ehdr.e_phnum;
+    TRACE("ELF: base %p ip %p e_type %d e_phnum %d e_phoff %p\n", info->BaseAddress, ip, ehdr.e_type, ehdr.e_phnum, ehdr.e_phoff);
+
+    // The eh_frame header
+    Phdr ehPhdr;
+    memset(&ehPhdr, 0, sizeof(ehPhdr));
+
+    // Search for the module's dynamic header and unwind frames
+    Dyn* dynamicAddr = nullptr;
+
+    for (int i = 0; i < phnum; i++, phdrAddr++)
+    {
+        Phdr ph;
+        if (!info->ReadMemory(phdrAddr, &ph, sizeof(ph))) {
+            ERROR("ELF: reading phdrAddr %p\n", phdrAddr);
+            return -UNW_EINVAL;
+        }
+        TRACE("ELF: phdr %p type %d (%x) vaddr %p memsz %016llx paddr %p filesz %016llx offset %p align %016llx\n",
+            phdrAddr, ph.p_type, ph.p_type, ph.p_vaddr, ph.p_memsz, ph.p_paddr, ph.p_filesz, ph.p_offset, ph.p_align);
+
+        switch (ph.p_type)
+        {
+        case PT_DYNAMIC:
+            if (ehdr.e_type == ET_EXEC) {
+                dynamicAddr = reinterpret_cast<Dyn*>(ph.p_vaddr);
+            }
+            if (ehdr.e_type == ET_DYN) {
+                dynamicAddr = reinterpret_cast<Dyn*>(ph.p_vaddr + info->BaseAddress);
+            }
+            break;
+
+        case PT_GNU_EH_FRAME:
+            ehPhdr = ph;
+            break;
+        }
+    }
+
+    if (dynamicAddr != nullptr)
+    {
+        for (;;)
+        {
+            Dyn dyn;
+            if (!info->ReadMemory(dynamicAddr, &dyn, sizeof(dyn))) {
+                ERROR("ELF: reading dynamicAddr %p\n", dynamicAddr);
+                return -UNW_EINVAL;
+            }
+            if (dyn.d_tag == DT_PLTGOT) {
+                TRACE("ELF: dyn %p tag %d (%x) d_ptr %p\n", dynamicAddr, dyn.d_tag, dyn.d_tag, dyn.d_un.d_ptr);
+                pip->gp = dyn.d_un.d_ptr;
+                break;
+            }
+            else if (dyn.d_tag == DT_NULL) {
+                break;
+            }
+            dynamicAddr++;
+        }
+    }
+    unw_word_t ehFrameHdrAddr = ehPhdr.p_offset + info->BaseAddress;
+    eh_frame_hdr ehFrameHdr;
+
+    if (!info->ReadMemory((PVOID)ehFrameHdrAddr, &ehFrameHdr, sizeof(eh_frame_hdr))) {
+        ERROR("ELF: reading ehFrameHdrAddr %p\n", ehFrameHdrAddr);
+        return -UNW_EINVAL;
+    }
+    TRACE("ehFrameHdrAddr %p version %d eh_frame_ptr_enc %d fde_count_enc %d table_enc %d\n", 
+        ehFrameHdrAddr, ehFrameHdr.version, ehFrameHdr.eh_frame_ptr_enc, ehFrameHdr.fde_count_enc, ehFrameHdr.table_enc);
+
+    if (ehFrameHdr.version != DW_EH_VERSION) {
+        ASSERT("ehFrameHdr version %x not supported\n", ehFrameHdr.version);
+        return -UNW_EBADVERSION;
+    }
+    unw_word_t addr = ehFrameHdrAddr + sizeof(eh_frame_hdr);
+    unw_word_t ehFrameStart;
+    unw_word_t fdeCount;
+
+    // Decode the eh_frame_hdr info
+    if (!ReadEncodedPointer(info, &addr, ehFrameHdr.eh_frame_ptr_enc, UINTPTR_MAX, &ehFrameStart)) {
+        ERROR("decoding eh_frame_ptr\n");
+        return -UNW_EINVAL;
+    }
+    if (!ReadEncodedPointer(info, &addr, ehFrameHdr.fde_count_enc, UINTPTR_MAX, &fdeCount)) {
+        ERROR("decoding fde_count_enc\n");
+        return -UNW_EINVAL;
+    }
+    TRACE("ehFrameStart %p fdeCount %p ip offset %08x\n", ehFrameStart, fdeCount, (int32_t)(ip - ehFrameHdrAddr));
+
+    // LookupTableEntry assumes this encoding
+    if (ehFrameHdr.table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4)) {
+        ASSERT("Table encoding not supported %x\n", ehFrameHdr.table_enc);
+        return -UNW_EINVAL;
+    }
+    // Find the fde using a binary search on the frame table
+    table_entry entry;
+    bool found;
+    if (!LookupTableEntry(info, ip - ehFrameHdrAddr, addr, fdeCount, &entry, &found)) {
+        ERROR("LookupTableEntry\n");
+        return -UNW_EINVAL;
+    }
+    unw_word_t fdeAddr = entry.fde_offset + ehFrameHdrAddr;
+    TRACE("start_ip %08x fde_offset %08x fdeAddr %p found %d\n", entry.start_ip, entry.fde_offset, fdeAddr, found);
+
+    // Unwind info not found
+    if (!found) {
+        return -UNW_ENOINFO;
+    }
+
+    // Now get the unwind info
+    if (!ExtractProcInfoFromFde(info, &fdeAddr, pip, need_unwind_info)) {
+        ERROR("ExtractProcInfoFromFde\n");
+        return -UNW_EINVAL;
+    }
+
+    _ASSERTE(ip >= pip->start_ip && ip <= pip->end_ip);
+    return UNW_ESUCCESS;
+}
+
+static void 
+put_unwind_info(unw_addr_space_t as, unw_proc_info_t *pip, void *arg)
+{
+    if (pip->unwind_info != nullptr) {
+        free(pip->unwind_info);
+        pip->unwind_info = nullptr;
+    }
+}
+
+static unw_accessors_t unwind_accessors =
+{
+    .find_proc_info = find_proc_info,
+    .put_unwind_info = 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
+};
+
+/*++
+Function:
+    PAL_VirtualUnwindOutOfProc
+
+    Unwind the stack given the context for a "remote" target using the
+    provided read memory callback.
+
+    Assumes the IP is in the module of the base address provided (coreclr).
+
+Parameters:
+    context - the start context in the target
+    contextPointers - the context of the next frame
+    baseAddress - base address of the module to find the unwind info
+    readMemoryCallback - reads memory from the target
+--*/
+BOOL
+PALAPI
+PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback)
+{
+    unw_addr_space_t addrSpace = 0;
+    unw_cursor_t cursor;
+    libunwindInfo info;
+    BOOL result = FALSE;
+    int st;
+
+    info.BaseAddress = baseAddress;
+    info.Context = context;
+    info.ReadMemory = readMemoryCallback;
+
+    addrSpace = unw_create_addr_space(&unwind_accessors, 0);
+
+    st = unw_init_remote(&cursor, addrSpace, &info);
+    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, NULL, contextPointers);
+    }
+    result = TRUE;
+
+exit:
+    if (addrSpace != 0)
+    {
+        unw_destroy_addr_space(addrSpace);
+    }
+    return result;
+}
+
+#else
+
+BOOL
+PALAPI
+PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback)
+{
+    return FALSE;
+}
+
+#endif // defined(_AMD64_) && defined(HAVE_UNW_GET_ACCESSORS)
index f1f25b0..b15ac34 100644 (file)
@@ -32,14 +32,8 @@ Abstract:
 #define UNW_LOCAL_ONLY
 #endif // !__linux__       
 #include <libunwind.h>
-#ifdef __linux__
-#ifdef HAVE_LIBUNWIND_PTRACE
-#include <libunwind-ptrace.h>
-#endif // HAVE_LIBUNWIND_PTRACE
-#endif // __linux__    
 #endif // HAVE_LIBUNWIND_H
 
-
 //----------------------------------------------------------------------
 // Virtual Unwinding
 //----------------------------------------------------------------------
@@ -141,7 +135,7 @@ static void WinContextToUnwindCursor(CONTEXT *winContext, unw_cursor_t *cursor)
 }
 #endif
 
-static void UnwindContextToWinContext(unw_cursor_t *cursor, CONTEXT *winContext)
+void UnwindContextToWinContext(unw_cursor_t *cursor, CONTEXT *winContext)
 {
 #if defined(_AMD64_)
     unw_get_reg(cursor, UNW_REG_IP, (unw_word_t *) &winContext->Rip);
@@ -209,7 +203,7 @@ static void GetContextPointer(unw_cursor_t *cursor, unw_context_t *unwContext, i
 #endif
 }
 
-static void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, KNONVOLATILE_CONTEXT_POINTERS *contextPointers)
+void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, KNONVOLATILE_CONTEXT_POINTERS *contextPointers)
 {
 #if defined(_AMD64_)
     GetContextPointer(cursor, unwContext, UNW_X86_64_RBP, &contextPointers->Rbp);
@@ -357,248 +351,6 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP
 #error don't know how to unwind on this platform
 #endif
 
-// These methods are only used on the AMD64 build
-#ifdef _AMD64_
-#ifdef HAVE_UNW_GET_ACCESSORS
-
-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;  
-}
-
-int find_proc_info(unw_addr_space_t as, 
-                   unw_word_t ip, unw_proc_info_t *pip,
-                   int need_unwind_info, void *arg)
-{
-#ifdef HAVE_LIBUNWIND_PTRACE
-    // UNIXTODO: libunwind RPM package on Fedora/CentOS/RedHat doesn't have libunwind-ptrace.so 
-    // and we can't use it from a shared library like libmscordaccore.so.
-    // That's why all calls to ptrace parts of libunwind ifdeffed out for now.
-    return _UPT_find_proc_info(as, ip, pip, need_unwind_info, arg);
-#else    
-    return -UNW_EINVAL;
-#endif    
-}
-
-void put_unwind_info(unw_addr_space_t as, unw_proc_info_t *pip, void *arg)
-{
-#ifdef HAVE_LIBUNWIND_PTRACE    
-    return _UPT_put_unwind_info(as, pip, arg);
-#endif    
-}
-
-static unw_accessors_t unwind_accessors =
-{
-    .find_proc_info = find_proc_info,
-    .put_unwind_info = 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_cursor_t cursor;
-    unw_addr_space_t addrSpace = 0;
-    void *libunwindUptPtr = NULL;
-    BOOL result = FALSE;
-
-    LibunwindCallbacksInfo.Context = context;
-    LibunwindCallbacksInfo.readMemCallback = readMemCallback;
-
-    addrSpace = unw_create_addr_space(&unwind_accessors, 0);
-#ifdef HAVE_LIBUNWIND_PTRACE    
-    libunwindUptPtr = _UPT_create(pid);
-#endif    
-    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, NULL, contextPointers);
-    }
-    result = TRUE;
-
-Exit:
-#ifdef HAVE_LIBUNWIND_PTRACE
-    if (libunwindUptPtr != NULL) 
-    {
-        _UPT_destroy(libunwindUptPtr);
-    }
-#endif    
-    if (addrSpace != 0) 
-    {
-        unw_destroy_addr_space(addrSpace);
-    }    
-    return result;
-}
-#else // HAVE_UNW_GET_ACCESSORS
-
-BOOL PAL_VirtualUnwindOutOfProc(CONTEXT *context, 
-                                KNONVOLATILE_CONTEXT_POINTERS *contextPointers, 
-                                DWORD pid, 
-                                ReadMemoryWordCallback readMemCallback)
-{
-    //UNIXTODO: Implement for Mac flavor of libunwind
-    return FALSE;
-}
-
-#endif // !HAVE_UNW_GET_ACCESSORS
-#endif // _AMD64_
-
 struct ExceptionRecords
 {
     CONTEXT ContextRecord;