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(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) 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. CONTEXTSetPC(context, CONTEXTGetPC(context) + 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 #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) // FreeBSD, NetBSD and OSX 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 = CONTEXTGetPC(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. if (unw_is_signal_frame(&cursor) > 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(__NetBSD__) || defined(_ARM64_) || defined(_ARM_) if (st == 0 && CONTEXTGetPC(context) == curPc) { CONTEXTSetPC(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; }