/*++ 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; }
BOOL PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, DWORD pid, ReadMemoryWordCallback readMemCallback) { // This function can be executed only by one thread at a time. // The reason for this is that we need to pass context and read mem function to libunwind callbacks // but "arg" is already used by the pointer returned from _UPT_create(). // So we resort to using global variables and a lock. struct Lock { CRITICAL_SECTION cs; Lock() { // ctor of a static variable is a thread-safe way to initialize critical section exactly once (clang,gcc) InitializeCriticalSection(&cs); } }; struct LockHolder { CRITICAL_SECTION *cs; LockHolder(CRITICAL_SECTION *cs) { this->cs = cs; EnterCriticalSection(cs); } ~LockHolder() { LeaveCriticalSection(cs); cs = NULL; } }; static Lock lock; LockHolder lockHolder(&lock.cs); int st; unw_context_t unwContext; unw_cursor_t cursor; unw_addr_space_t addrSpace = 0; void *libunwindUptPtr = NULL; BOOL result = FALSE; LibunwindCallbacksInfo.Context = context; LibunwindCallbacksInfo.readMemCallback = readMemCallback; WinContextToUnwindContext(context, &unwContext); addrSpace = unw_create_addr_space(&unwind_accessors, 0); libunwindUptPtr = _UPT_create(pid); st = unw_init_remote(&cursor, addrSpace, libunwindUptPtr); if (st < 0) { result = FALSE; goto Exit; } st = unw_step(&cursor); if (st < 0) { result = FALSE; goto Exit; } UnwindContextToWinContext(&cursor, context); if (contextPointers != NULL) { GetContextPointers(&cursor, &unwContext, contextPointers); } result = TRUE; Exit: if (libunwindUptPtr != nullptr) { _UPT_destroy(libunwindUptPtr); } if (addrSpace != 0) { unw_destroy_addr_space(addrSpace); } return result; }
BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers) { int st; unw_context_t unwContext; unw_cursor_t cursor; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) DWORD64 curPc; #endif if ((context->ContextFlags & CONTEXT_EXCEPTION_ACTIVE) != 0) { // The current frame is a source of hardware exception. Due to the fact that // we use the low level unwinder to unwind just one frame a time, the // unwinder doesn't have the signal_frame flag set. So it doesn't // know that it should not decrement the PC before looking up the unwind info. // So we compensate it by incrementing the PC before passing it to the unwinder. // Without it, the unwinder would not find unwind info if the hardware exception // happened in the first instruction of a function. SetPc(context, GetPc(context) + 1); } #if UNWIND_CONTEXT_IS_UCONTEXT_T WinContextToUnwindContext(context, &unwContext); #else st = unw_getcontext(&unwContext); if (st < 0) { return FALSE; } #endif st = unw_init_local(&cursor, &unwContext); if (st < 0) { return FALSE; } #if !UNWIND_CONTEXT_IS_UCONTEXT_T // Set the unwind context to the specified windows context WinContextToUnwindCursor(context, &cursor); #endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) // OSX and FreeBSD appear to do two different things when unwinding // 1: If it reaches where it cannot unwind anymore, say a // managed frame. It wil return 0, but also update the $pc // 2: If it unwinds all the way to _start it will return // 0 from the step, but $pc will stay the same. // The behaviour of libunwind from nongnu.org is to null the PC // So we bank the original PC here, so we can compare it after // the step curPc = GetPc(context); #endif st = unw_step(&cursor); if (st < 0) { return FALSE; } // Check if the frame we have unwound to is a frame that caused // synchronous signal, like a hardware exception and record it // in the context flags. st = unw_is_signal_frame(&cursor); if (st > 0) { context->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE; } else { context->ContextFlags &= ~CONTEXT_EXCEPTION_ACTIVE; } // Update the passed in windows context to reflect the unwind // UnwindContextToWinContext(&cursor, context); #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) if (st == 0 && GetPc(context) == curPc) { SetPc(context, 0); } #endif if (contextPointers != NULL) { GetContextPointers(&cursor, &unwContext, contextPointers); } return TRUE; }
BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers) { int st; unw_context_t unwContext; unw_cursor_t cursor; DWORD64 curPc = CONTEXTGetPC(context); #ifndef __APPLE__ // Check if the PC is the return address from the SEHProcessException in the common_signal_handler. // If that's the case, extract its local variable containing the windows style context of the hardware // exception and return that. This skips the hardware signal handler trampoline that the libunwind // cannot cross on some systems. if ((void*)curPc == g_SEHProcessExceptionReturnAddress) { CONTEXT* signalContext = (CONTEXT*)(CONTEXTGetFP(context) + g_common_signal_handler_context_locvar_offset); memcpy_s(context, sizeof(CONTEXT), signalContext, sizeof(CONTEXT)); return TRUE; } #endif if ((context->ContextFlags & CONTEXT_EXCEPTION_ACTIVE) != 0) { // The current frame is a source of hardware exception. Due to the fact that // we use the low level unwinder to unwind just one frame a time, the // unwinder doesn't have the signal_frame flag set. So it doesn't // know that it should not decrement the PC before looking up the unwind info. // So we compensate it by incrementing the PC before passing it to the unwinder. // Without it, the unwinder would not find unwind info if the hardware exception // happened in the first instruction of a function. CONTEXTSetPC(context, curPc + 1); } #if !UNWIND_CONTEXT_IS_UCONTEXT_T st = unw_getcontext(&unwContext); if (st < 0) { return FALSE; } #endif WinContextToUnwindContext(context, &unwContext); st = unw_init_local(&cursor, &unwContext); if (st < 0) { return FALSE; } #if !UNWIND_CONTEXT_IS_UCONTEXT_T // Set the unwind context to the specified windows context WinContextToUnwindCursor(context, &cursor); #endif st = unw_step(&cursor); if (st < 0) { return FALSE; } // Check if the frame we have unwound to is a frame that caused // synchronous signal, like a hardware exception and record it // in the context flags. if (unw_is_signal_frame(&cursor) > 0) { context->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE; #if defined(_ARM_) || defined(_ARM64_) || defined(_X86_) context->ContextFlags &= ~CONTEXT_UNWOUND_TO_CALL; #endif // _ARM_ || _ARM64_ } else { context->ContextFlags &= ~CONTEXT_EXCEPTION_ACTIVE; #if defined(_ARM_) || defined(_ARM64_) || defined(_X86_) context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL; #endif // _ARM_ || _ARM64_ } // Update the passed in windows context to reflect the unwind // UnwindContextToWinContext(&cursor, context); // FreeBSD, NetBSD, OSX and Alpine appear to do two different things when unwinding // 1: If it reaches where it cannot unwind anymore, say a // managed frame. It will return 0, but also update the $pc // 2: If it unwinds all the way to _start it will return // 0 from the step, but $pc will stay the same. // So we detect that here and set the $pc to NULL in that case. // This is the default behavior of the libunwind on Linux. if (st == 0 && CONTEXTGetPC(context) == curPc) { CONTEXTSetPC(context, 0); } if (contextPointers != NULL) { GetContextPointers(&cursor, &unwContext, contextPointers); } return TRUE; }
BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers) { int st; unw_context_t unwContext; unw_cursor_t cursor; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) DWORD64 curPc; #endif #if UNWIND_CONTEXT_IS_UCONTEXT_T WinContextToUnwindContext(context, &unwContext); #else st = unw_getcontext(&unwContext); if (st < 0) { return FALSE; } #endif st = unw_init_local(&cursor, &unwContext); if (st < 0) { return FALSE; } #if !UNWIND_CONTEXT_IS_UCONTEXT_T // Set the unwind context to the specified windows context WinContextToUnwindCursor(context, &cursor); #endif #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) // OSX and FreeBSD appear to do two different things when unwinding // 1: If it reaches where it cannot unwind anymore, say a // managed frame. It wil return 0, but also update the $pc // 2: If it unwinds all the way to _start it will return // 0 from the step, but $pc will stay the same. // The behaviour of libunwind from nongnu.org is to null the PC // So we bank the original PC here, so we can compare it after // the step curPc = GetPc(context); #endif st = unw_step(&cursor); if (st < 0) { return FALSE; } // Update the passed in windows context to reflect the unwind // UnwindContextToWinContext(&cursor, context); #if defined(__APPLE__) || defined(__FreeBSD__) || defined(_ARM64_) if (st == 0 && GetPc(context) == curPc) { SetPc(context, 0); } #endif if (contextPointers != NULL) { GetContextPointers(&cursor, &unwContext, contextPointers); } return TRUE; }