From 52e949043ae6d6fd8d643232f2794e15a564a6a5 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Tue, 15 Aug 2017 17:33:04 -0700 Subject: Implement out of context stack unwinder (#13302) 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 | 24 + clrdefinitions.cmake | 1 + src/ToolBox/SOS/Strike/strike.cpp | 163 ++++- src/debug/daccess/dacfn.cpp | 25 +- src/dlls/mscordac/mscordac_unixexports.src | 1 + src/pal/inc/pal.h | 7 +- src/pal/src/CMakeLists.txt | 1 + src/pal/src/exception/remote-unwind.cpp | 1086 ++++++++++++++++++++++++++++ src/pal/src/exception/seh-unwind.cpp | 252 +------ 9 files changed, 1291 insertions(+), 269 deletions(-) create mode 100644 src/pal/src/exception/remote-unwind.cpp diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 03d4d3cc65..d2ccf3bcab 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -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 + +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. diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake index b1814753b3..f6b8de4461 100644 --- a/clrdefinitions.cmake +++ b/clrdefinitions.cmake @@ -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) diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 7e01635100..f9d02c5f48 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -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 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 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_ diff --git a/src/debug/daccess/dacfn.cpp b/src/debug/daccess/dacfn.cpp index 2f7a98de1a..78a7b9f2f5 100644 --- a/src/debug/daccess/dacfn.cpp +++ b/src/debug/daccess/dacfn.cpp @@ -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 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 diff --git a/src/dlls/mscordac/mscordac_unixexports.src b/src/dlls/mscordac/mscordac_unixexports.src index 5ec7bfe6ab..99ed375ce7 100644 --- a/src/dlls/mscordac/mscordac_unixexports.src +++ b/src/dlls/mscordac/mscordac_unixexports.src @@ -41,6 +41,7 @@ PAL__wcstoui64 PAL_wcstoul PAL_iswprint PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange +PAL_VirtualUnwindOutOfProc PAL_wcslen PAL_wcsncmp PAL_wcsrchr diff --git a/src/pal/inc/pal.h b/src/pal/inc/pal.h index c749e77b0e..09ad199933 100644 --- a/src/pal/inc/pal.h +++ b/src/pal/inc/pal.h @@ -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 diff --git a/src/pal/src/CMakeLists.txt b/src/pal/src/CMakeLists.txt index b8a9fe9e46..578b1169bb 100644 --- a/src/pal/src/CMakeLists.txt +++ b/src/pal/src/CMakeLists.txt @@ -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 index 0000000000..18733ded52 --- /dev/null +++ b/src/pal/src/exception/remote-unwind.cpp @@ -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 + +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 + +#if HAVE_LIBUNWIND_H +#ifndef __linux__ +#define UNW_LOCAL_ONLY +#endif // !__linux__ +#include +#endif // HAVE_LIBUNWIND_H + +SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); + +// Only used on the AMD64 build +#if defined(_AMD64_) && defined(HAVE_UNW_GET_ACCESSORS) + +#include +#include + +#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(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(ph.p_vaddr); + } + if (ehdr.e_type == ET_DYN) { + dynamicAddr = reinterpret_cast(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) diff --git a/src/pal/src/exception/seh-unwind.cpp b/src/pal/src/exception/seh-unwind.cpp index f1f25b01e8..b15ac34c24 100644 --- a/src/pal/src/exception/seh-unwind.cpp +++ b/src/pal/src/exception/seh-unwind.cpp @@ -32,14 +32,8 @@ Abstract: #define UNW_LOCAL_ONLY #endif // !__linux__ #include -#ifdef __linux__ -#ifdef HAVE_LIBUNWIND_PTRACE -#include -#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; -- cgit v1.2.3