diff options
Diffstat (limited to 'src/pal/src')
-rw-r--r-- | src/pal/src/CMakeLists.txt | 40 | ||||
-rw-r--r-- | src/pal/src/arch/amd64/callsignalhandlerwrapper.S | 31 | ||||
-rw-r--r-- | src/pal/src/arch/amd64/signalhandlerhelper.cpp | 70 | ||||
-rw-r--r-- | src/pal/src/arch/arm/callsignalhandlerwrapper.S | 32 | ||||
-rw-r--r-- | src/pal/src/arch/arm/signalhandlerhelper.cpp | 70 | ||||
-rw-r--r-- | src/pal/src/arch/arm64/callsignalhandlerwrapper.S | 30 | ||||
-rw-r--r-- | src/pal/src/arch/arm64/signalhandlerhelper.cpp | 69 | ||||
-rw-r--r-- | src/pal/src/arch/i386/callsignalhandlerwrapper.S | 47 | ||||
-rw-r--r-- | src/pal/src/arch/i386/signalhandlerhelper.cpp | 77 | ||||
-rw-r--r-- | src/pal/src/exception/seh.cpp | 2 | ||||
-rw-r--r-- | src/pal/src/exception/signal.cpp | 165 | ||||
-rw-r--r-- | src/pal/src/exception/signal.hpp | 52 | ||||
-rw-r--r-- | src/pal/src/include/pal/context.h | 15 | ||||
-rw-r--r-- | src/pal/src/include/pal/signal.hpp | 141 | ||||
-rw-r--r-- | src/pal/src/init/sxs.cpp | 15 | ||||
-rw-r--r-- | src/pal/src/thread/context.cpp | 29 | ||||
-rw-r--r-- | src/pal/src/thread/thread.cpp | 13 |
17 files changed, 808 insertions, 90 deletions
diff --git a/src/pal/src/CMakeLists.txt b/src/pal/src/CMakeLists.txt index 16c9d8bd6f..dae0f3fe3f 100644 --- a/src/pal/src/CMakeLists.txt +++ b/src/pal/src/CMakeLists.txt @@ -101,35 +101,19 @@ set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -Wl,--no add_compile_options(-fPIC) -if(PAL_CMAKE_PLATFORM_ARCH_AMD64) - set(ARCH_SOURCES - arch/amd64/context2.S - arch/amd64/debugbreak.S - arch/amd64/exceptionhelper.S - arch/amd64/processor.cpp - ) -elseif(PAL_CMAKE_PLATFORM_ARCH_ARM) - set(ARCH_SOURCES - arch/arm/context2.S - arch/arm/debugbreak.S - arch/arm/exceptionhelper.S - arch/arm/processor.cpp - ) -elseif(PAL_CMAKE_PLATFORM_ARCH_ARM64) - set(ARCH_SOURCES - arch/arm64/context2.S - arch/arm64/debugbreak.S - arch/arm64/exceptionhelper.S - arch/arm64/processor.cpp - ) -elseif(PAL_CMAKE_PLATFORM_ARCH_I386) - set(ARCH_SOURCES - arch/i386/context2.S - arch/i386/debugbreak.S - arch/i386/exceptionhelper.S - arch/i386/processor.cpp +set(ARCH_SOURCES + arch/${ARCH_SOURCES_DIR}/context2.S + arch/${ARCH_SOURCES_DIR}/debugbreak.S + arch/${ARCH_SOURCES_DIR}/exceptionhelper.S + arch/${ARCH_SOURCES_DIR}/processor.cpp +) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) + list(APPEND PLATFORM_SOURCES + arch/${ARCH_SOURCES_DIR}/callsignalhandlerwrapper.S + arch/${ARCH_SOURCES_DIR}/signalhandlerhelper.cpp ) -endif() +endif(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) if(PAL_CMAKE_PLATFORM_ARCH_ARM) set_source_files_properties(exception/seh.cpp PROPERTIES COMPILE_FLAGS -Wno-error=inline-asm) diff --git a/src/pal/src/arch/amd64/callsignalhandlerwrapper.S b/src/pal/src/arch/amd64/callsignalhandlerwrapper.S new file mode 100644 index 0000000000..8260591c30 --- /dev/null +++ b/src/pal/src/arch/amd64/callsignalhandlerwrapper.S @@ -0,0 +1,31 @@ +// 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. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.macro CALL_SIGNAL_HANDLER_WRAPPER Alignment + +.globl C_FUNC(SignalHandlerWorkerReturnOffset\Alignment) +C_FUNC(SignalHandlerWorkerReturnOffset\Alignment): + .int LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment)-C_FUNC(CallSignalHandlerWrapper\Alignment) + +// This function is never called, only a fake stack frame will be setup to have a return +// address set to SignalHandlerWorkerReturn during SIGSEGV handling. +// It enables the unwinder to unwind stack from the handling code to the actual failure site. +NESTED_ENTRY CallSignalHandlerWrapper\Alignment, _TEXT, NoHandler + .cfi_def_cfa_offset (128 + 8 + \Alignment) // red zone + return address + alignment + .cfi_offset rip, -(128 + 8 + \Alignment) + push_nonvol_reg rbp + call EXTERNAL_C_FUNC(signal_handler_worker) +LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment): + pop rbp + ret +NESTED_END CallSignalHandlerWrapper\Alignment, _TEXT + +.endm + +CALL_SIGNAL_HANDLER_WRAPPER 0 +CALL_SIGNAL_HANDLER_WRAPPER 8 diff --git a/src/pal/src/arch/amd64/signalhandlerhelper.cpp b/src/pal/src/arch/amd64/signalhandlerhelper.cpp new file mode 100644 index 0000000000..5e37583a9f --- /dev/null +++ b/src/pal/src/arch/amd64/signalhandlerhelper.cpp @@ -0,0 +1,70 @@ +// 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. + +#include "pal/dbgmsg.h" +SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first + +#include "pal/palinternal.h" +#include "pal/context.h" +#include "pal/signal.hpp" +#include "pal/utils.h" +#include <sys/ucontext.h> + +/*++ +Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint) +{ + ucontext_t *ucontext = (ucontext_t *)context; + size_t faultSp = (size_t)MCREG_Rsp(ucontext->uc_mcontext); + + _ASSERTE(IS_ALIGNED(faultSp, 8)); + + size_t fakeFrameReturnAddress; + + if (IS_ALIGNED(faultSp, 16)) + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset0 + (size_t)CallSignalHandlerWrapper0; + } + else + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset8 + (size_t)CallSignalHandlerWrapper8; + } + + // preserve 128 bytes long red zone and align stack pointer + size_t* sp = (size_t*)ALIGN_DOWN(faultSp - 128, 16); + + // Build fake stack frame to enable the stack unwinder to unwind from signal_handler_worker to the faulting instruction + *--sp = (size_t)MCREG_Rip(ucontext->uc_mcontext); + *--sp = (size_t)MCREG_Rbp(ucontext->uc_mcontext); + size_t fp = (size_t)sp; + *--sp = fakeFrameReturnAddress; + + // Switch the current context to the signal_handler_worker and the original stack + ucontext_t ucontext2; + getcontext(&ucontext2); + + // We don't care about the other registers state since the stack unwinding restores + // them for the target frame directly from the signal context. + MCREG_Rsp(ucontext2.uc_mcontext) = (size_t)sp; + MCREG_Rbx(ucontext2.uc_mcontext) = (size_t)faultSp; + MCREG_Rbp(ucontext2.uc_mcontext) = (size_t)fp; + MCREG_Rip(ucontext2.uc_mcontext) = (size_t)signal_handler_worker; + MCREG_Rdi(ucontext2.uc_mcontext) = code; + MCREG_Rsi(ucontext2.uc_mcontext) = (size_t)siginfo; + MCREG_Rdx(ucontext2.uc_mcontext) = (size_t)context; + MCREG_Rcx(ucontext2.uc_mcontext) = (size_t)returnPoint; + + setcontext(&ucontext2); +} diff --git a/src/pal/src/arch/arm/callsignalhandlerwrapper.S b/src/pal/src/arch/arm/callsignalhandlerwrapper.S new file mode 100644 index 0000000000..266e4fdfe9 --- /dev/null +++ b/src/pal/src/arch/arm/callsignalhandlerwrapper.S @@ -0,0 +1,32 @@ +// 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. + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.syntax unified +.thumb + +.macro CALL_SIGNAL_HANDLER_WRAPPER Alignment + +.globl C_FUNC(SignalHandlerWorkerReturnOffset\Alignment) +C_FUNC(SignalHandlerWorkerReturnOffset\Alignment): + .int LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment)-C_FUNC(CallSignalHandlerWrapper\Alignment) + +// This function is never called, only a fake stack frame will be setup to have a return +// address set to SignalHandlerWorkerReturn during SIGSEGV handling. +// It enables the unwinder to unwind stack from the handling code to the actual failure site. +NESTED_ENTRY CallSignalHandlerWrapper\Alignment, _TEXT, NoHandler + sub sp, sp, #(8 + \Alignment) // red zone + alignment + stmfd sp!, {r7, lr} + bl EXTERNAL_C_FUNC(signal_handler_worker) +LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment): + ldmfd sp!, {r7, lr} + bx lr +NESTED_END CallSignalHandlerWrapper\Alignment, _TEXT + +.endm + +CALL_SIGNAL_HANDLER_WRAPPER 0 +CALL_SIGNAL_HANDLER_WRAPPER 4 diff --git a/src/pal/src/arch/arm/signalhandlerhelper.cpp b/src/pal/src/arch/arm/signalhandlerhelper.cpp new file mode 100644 index 0000000000..beda0b441e --- /dev/null +++ b/src/pal/src/arch/arm/signalhandlerhelper.cpp @@ -0,0 +1,70 @@ +// 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. + +#include "pal/dbgmsg.h" +SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first + +#include "pal/palinternal.h" +#include "pal/context.h" +#include "pal/signal.hpp" +#include "pal/utils.h" +#include <sys/ucontext.h> + +/*++ +Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint) +{ + ucontext_t *ucontext = (ucontext_t *)context; + size_t faultSp = (size_t)MCREG_Sp(ucontext->uc_mcontext); + + _ASSERTE(IS_ALIGNED(faultSp, 4)); + + size_t fakeFrameReturnAddress; + + if (IS_ALIGNED(faultSp, 8)) + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset0 + (size_t)CallSignalHandlerWrapper0; + } + else + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset4 + (size_t)CallSignalHandlerWrapper4; + } + + // preserve 8 bytes long red zone and align stack pointer + size_t* sp = (size_t*)ALIGN_DOWN(faultSp - 8, 8); + + // Build fake stack frame to enable the stack unwinder to unwind from signal_handler_worker to the faulting instruction + // pushed LR + *--sp = (size_t)MCREG_Pc(ucontext->uc_mcontext); + // pushed frame pointer + *--sp = (size_t)MCREG_R7(ucontext->uc_mcontext); + + // Switch the current context to the signal_handler_worker and the original stack + ucontext_t ucontext2; + getcontext(&ucontext2); + + // We don't care about the other registers state since the stack unwinding restores + // them for the target frame directly from the signal context. + MCREG_Sp(ucontext2.uc_mcontext) = (size_t)sp; + MCREG_R7(ucontext2.uc_mcontext) = (size_t)sp; // Fp and Sp are the same + MCREG_Lr(ucontext2.uc_mcontext) = fakeFrameReturnAddress; + MCREG_Pc(ucontext2.uc_mcontext) = (size_t)signal_handler_worker; + MCREG_R0(ucontext2.uc_mcontext) = code; + MCREG_R1(ucontext2.uc_mcontext) = (size_t)siginfo; + MCREG_R2(ucontext2.uc_mcontext) = (size_t)context; + MCREG_R3(ucontext2.uc_mcontext) = (size_t)returnPoint; + + setcontext(&ucontext2); +} diff --git a/src/pal/src/arch/arm64/callsignalhandlerwrapper.S b/src/pal/src/arch/arm64/callsignalhandlerwrapper.S new file mode 100644 index 0000000000..6bff23b7be --- /dev/null +++ b/src/pal/src/arch/arm64/callsignalhandlerwrapper.S @@ -0,0 +1,30 @@ +// 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. + +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.macro CALL_SIGNAL_HANDLER_WRAPPER Alignment + +.globl C_FUNC(SignalHandlerWorkerReturnOffset\Alignment) +C_FUNC(SignalHandlerWorkerReturnOffset\Alignment): + .int LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment)-C_FUNC(CallSignalHandlerWrapper\Alignment) + +// This function is never called, only a fake stack frame will be setup to have a return +// address set to SignalHandlerWorkerReturn during SIGSEGV handling. +// It enables the unwinder to unwind stack from the handling code to the actual failure site. +NESTED_ENTRY CallSignalHandlerWrapper\Alignment, _TEXT, NoHandler + PROLOG_STACK_ALLOC (128 + 8 + 8 + \Alignment) // red zone + fp + lr + alignment + PROLOG_SAVE_REG_PAIR fp, lr, 0 + bl EXTERNAL_C_FUNC(signal_handler_worker) +LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment): + EPILOG_RESTORE_REG_PAIR fp, lr, 0 + EPILOG_STACK_FREE (128 + 8 + 8 + \Alignment) + ret +NESTED_END CallSignalHandlerWrapper\Alignment, _TEXT + +.endm + +CALL_SIGNAL_HANDLER_WRAPPER 0 +CALL_SIGNAL_HANDLER_WRAPPER 8 diff --git a/src/pal/src/arch/arm64/signalhandlerhelper.cpp b/src/pal/src/arch/arm64/signalhandlerhelper.cpp new file mode 100644 index 0000000000..3891b9ea7f --- /dev/null +++ b/src/pal/src/arch/arm64/signalhandlerhelper.cpp @@ -0,0 +1,69 @@ +// 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. + +#include "pal/dbgmsg.h" +SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first + +#include "pal/palinternal.h" +#include "pal/context.h" +#include "pal/signal.hpp" +#include "pal/utils.h" +#include <sys/ucontext.h> + +/*++ +Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint) +{ + ucontext_t *ucontext = (ucontext_t *)context; + size_t faultSp = (size_t)MCREG_Sp(ucontext->uc_mcontext); + _ASSERTE(IS_ALIGNED(faultSp, 8)); + + size_t fakeFrameReturnAddress; + + if (IS_ALIGNED(faultSp, 16)) + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset0 + (size_t)CallSignalHandlerWrapper0; + } + else + { + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset8 + (size_t)CallSignalHandlerWrapper8; + } + + // preserve 128 bytes long red zone and align stack pointer + size_t* sp = (size_t*)ALIGN_DOWN(faultSp - 128, 16); + + // Build fake stack frame to enable the stack unwinder to unwind from signal_handler_worker to the faulting instruction + // pushed LR + *--sp = (size_t)MCREG_Pc(ucontext->uc_mcontext); + // pushed frame pointer + *--sp = (size_t)MCREG_Fp(ucontext->uc_mcontext); + + // Switch the current context to the signal_handler_worker and the original stack + ucontext_t ucontext2; + getcontext(&ucontext2); + + // We don't care about the other registers state since the stack unwinding restores + // them for the target frame directly from the signal context. + MCREG_Sp(ucontext2.uc_mcontext) = (size_t)sp; + MCREG_Fp(ucontext2.uc_mcontext) = (size_t)sp; // Fp and Sp are the same + MCREG_Lr(ucontext2.uc_mcontext) = fakeFrameReturnAddress; + MCREG_Pc(ucontext2.uc_mcontext) = (size_t)signal_handler_worker; + MCREG_X0(ucontext2.uc_mcontext) = code; + MCREG_X1(ucontext2.uc_mcontext) = (size_t)siginfo; + MCREG_X2(ucontext2.uc_mcontext) = (size_t)context; + MCREG_X3(ucontext2.uc_mcontext) = (size_t)returnPoint; + + setcontext(&ucontext2); +} diff --git a/src/pal/src/arch/i386/callsignalhandlerwrapper.S b/src/pal/src/arch/i386/callsignalhandlerwrapper.S new file mode 100644 index 0000000000..26f06d9886 --- /dev/null +++ b/src/pal/src/arch/i386/callsignalhandlerwrapper.S @@ -0,0 +1,47 @@ +// 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. + +.intel_syntax noprefix +#include "unixasmmacros.inc" +#include "asmconstants.h" + +.macro CALL_SIGNAL_HANDLER_WRAPPER Alignment + +.globl C_FUNC(SignalHandlerWorkerReturnOffset\Alignment) +C_FUNC(SignalHandlerWorkerReturnOffset\Alignment): + .int LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment)-C_FUNC(CallSignalHandlerWrapper\Alignment) + +// This function is never called, only a fake stack frame will be setup to have a return +// address set to SignalHandlerWorkerReturn during SIGSEGV handling. +// It enables the unwinder to unwind stack from the handling code to the actual failure site. +NESTED_ENTRY CallSignalHandlerWrapper\Alignment, _TEXT, NoHandler + + .cfi_def_cfa_offset (4 + \Alignment) // return address + stack alignment + .cfi_offset eip, -(4 + \Alignment) + push ebp + .cfi_adjust_cfa_offset 4 + .cfi_rel_offset ebp, 0 + mov ebp, esp + .cfi_def_cfa_register ebp + // Align stack + sub esp, 8 + // Simulate arguments pushing + push eax + push eax + push eax + push eax + call EXTERNAL_C_FUNC(signal_handler_worker) +LOCAL_LABEL(SignalHandlerWorkerReturn\Alignment): + add esp, 4 * 4 + 8 + pop ebp + ret + +NESTED_END CallSignalHandlerWrapper\Alignment, _TEXT + +.endm + +CALL_SIGNAL_HANDLER_WRAPPER 0 +CALL_SIGNAL_HANDLER_WRAPPER 4 +CALL_SIGNAL_HANDLER_WRAPPER 8 +CALL_SIGNAL_HANDLER_WRAPPER 12 diff --git a/src/pal/src/arch/i386/signalhandlerhelper.cpp b/src/pal/src/arch/i386/signalhandlerhelper.cpp new file mode 100644 index 0000000000..5e7333ad3d --- /dev/null +++ b/src/pal/src/arch/i386/signalhandlerhelper.cpp @@ -0,0 +1,77 @@ +// 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. + +#include "pal/dbgmsg.h" +SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do this first + +#include "pal/palinternal.h" +#include "pal/context.h" +#include "pal/signal.hpp" +#include "pal/utils.h" +#include <sys/ucontext.h> + +/*++ +Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint) +{ + ucontext_t *ucontext = (ucontext_t *)context; + size_t faultSp = (size_t)MCREG_Esp(ucontext->uc_mcontext); + + _ASSERTE(IS_ALIGNED(faultSp, 4)); + + size_t fakeFrameReturnAddress; + + switch (faultSp & 0xc) + { + case 0x0: + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset0 + (size_t)CallSignalHandlerWrapper0; + break; + case 0x4: + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset4 + (size_t)CallSignalHandlerWrapper4; + break; + case 0x8: + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset8 + (size_t)CallSignalHandlerWrapper8; + break; + case 0xc: + fakeFrameReturnAddress = (size_t)SignalHandlerWorkerReturnOffset12 + (size_t)CallSignalHandlerWrapper12; + break; + } + + size_t* sp = (size_t*)ALIGN_DOWN(faultSp, 16); + + // Build fake stack frame to enable the stack unwinder to unwind from signal_handler_worker to the faulting instruction + *--sp = (size_t)MCREG_Eip(ucontext->uc_mcontext); + *--sp = (size_t)MCREG_Ebp(ucontext->uc_mcontext); + size_t fp = (size_t)sp; + // Align stack + sp -= 2; + *--sp = (size_t)returnPoint; + *--sp = (size_t)context; + *--sp = (size_t)siginfo; + *--sp = code; + *--sp = fakeFrameReturnAddress; + + // Switch the current context to the signal_handler_worker and the original stack + ucontext_t ucontext2; + getcontext(&ucontext2); + + // We don't care about the other registers state since the stack unwinding restores + // them for the target frame directly from the signal context. + MCREG_Esp(ucontext2.uc_mcontext) = (size_t)sp; + MCREG_Ebp(ucontext2.uc_mcontext) = (size_t)fp; + MCREG_Eip(ucontext2.uc_mcontext) = (size_t)signal_handler_worker; + + setcontext(&ucontext2); +} diff --git a/src/pal/src/exception/seh.cpp b/src/pal/src/exception/seh.cpp index ad09e02884..7682e9e6e6 100644 --- a/src/pal/src/exception/seh.cpp +++ b/src/pal/src/exception/seh.cpp @@ -27,7 +27,7 @@ Abstract: #include "pal/init.h" #include "pal/process.h" #include "pal/malloc.hpp" -#include "signal.hpp" +#include "pal/signal.hpp" #if HAVE_MACH_EXCEPTIONS #include "machexception.h" diff --git a/src/pal/src/exception/signal.cpp b/src/pal/src/exception/signal.cpp index 26e2a012c5..d442afaf4c 100644 --- a/src/pal/src/exception/signal.cpp +++ b/src/pal/src/exception/signal.cpp @@ -27,12 +27,15 @@ SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do #include "pal/threadinfo.hpp" #include "pal/threadsusp.hpp" #include "pal/seh.hpp" +#include "pal/signal.hpp" #include "pal/palinternal.h" #if !HAVE_MACH_EXCEPTIONS #include "pal/init.h" #include "pal/process.h" #include "pal/debug.h" +#include "pal/virtual.h" +#include "pal/utils.h" #include <signal.h> #include <errno.h> @@ -40,6 +43,7 @@ SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do #include <sys/ucontext.h> #include <sys/utsname.h> #include <unistd.h> +#include <sys/mman.h> #include "pal/context.h" @@ -63,6 +67,13 @@ typedef void *siginfo_t; #endif /* !HAVE_SIGINFO_T */ typedef void (*SIGFUNC)(int, siginfo_t *, void *); +// Return context and status for the signal_handler_worker. +struct SignalHandlerWorkerReturnPoint +{ + bool returnFromHandler; + ucontext_t context; +}; + /* internal function declarations *********************************************/ static void sigill_handler(int code, siginfo_t *siginfo, void *context); @@ -80,7 +91,7 @@ static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext static void inject_activation_handler(int code, siginfo_t *siginfo, void *context); #endif -static void handle_signal(int signal_id, SIGFUNC sigfunc, struct sigaction *previousAction); +static void handle_signal(int signal_id, SIGFUNC sigfunc, struct sigaction *previousAction, int additionalFlags = 0); static void restore_signal(int signal_id, struct sigaction *previousAction); /* internal data declarations *********************************************/ @@ -108,6 +119,88 @@ int g_common_signal_handler_context_locvar_offset = 0; /*++ Function : + EnsureSignalAlternateStack + + Ensure that alternate stack for signal handling is allocated for the current thread + +Parameters : + None + +Return : + TRUE in case of a success, FALSE otherwise +--*/ +BOOL EnsureSignalAlternateStack() +{ + stack_t oss; + + // Query the current alternate signal stack + int st = sigaltstack(NULL, &oss); + + if ((st == 0) && (oss.ss_flags == SS_DISABLE)) + { + // There is no alternate stack for SIGSEGV handling installed yet so allocate one + + // We include the size of the SignalHandlerWorkerReturnPoint in the alternate stack size since the + // context contained in it is large and the SIGSTKSZ was not sufficient on ARM64 during testing. + int altStackSize = SIGSTKSZ + ALIGN_UP(sizeof(SignalHandlerWorkerReturnPoint), 16) + VIRTUAL_PAGE_SIZE; + void* altStack; + int st = posix_memalign(&altStack, VIRTUAL_PAGE_SIZE, altStackSize); + if (st == 0) + { + // create a guard page for the alternate stack + st = mprotect(altStack, VIRTUAL_PAGE_SIZE, PROT_NONE); + if (st == 0) + { + stack_t ss; + ss.ss_sp = (char*)altStack; + ss.ss_size = altStackSize; + ss.ss_flags = 0; + st = sigaltstack(&ss, NULL); + if (st != 0) + { + // Installation of the alternate stack failed, so revert the guard page protection + int st2 = mprotect(altStack, VIRTUAL_PAGE_SIZE, PROT_READ | PROT_WRITE); + _ASSERTE(st2 == 0); + } + } + + if (st != 0) + { + free(altStack); + } + } + } + + return (st == 0); +} + +/*++ +Function : + FreeSignalAlternateStack + + Free alternate stack for signal handling + +Parameters : + None + +Return : + None +--*/ +void FreeSignalAlternateStack() +{ + stack_t ss, oss; + ss.ss_flags = SS_DISABLE; + int st = sigaltstack(&ss, &oss); + if ((st == 0) && (oss.ss_flags != SS_DISABLE)) + { + int st = mprotect(oss.ss_sp, VIRTUAL_PAGE_SIZE, PROT_READ | PROT_WRITE); + _ASSERTE(st == 0); + free(oss.ss_sp); + } +} + +/*++ +Function : SEHInitializeSignals Set up signal handlers to catch signals and translate them to exceptions @@ -139,10 +232,16 @@ BOOL SEHInitializeSignals(DWORD flags) handle_signal(SIGTRAP, sigtrap_handler, &g_previous_sigtrap); handle_signal(SIGFPE, sigfpe_handler, &g_previous_sigfpe); handle_signal(SIGBUS, sigbus_handler, &g_previous_sigbus); - handle_signal(SIGSEGV, sigsegv_handler, &g_previous_sigsegv); + // SIGSEGV handler runs on a separate stack so that we can handle stack overflow + handle_signal(SIGSEGV, sigsegv_handler, &g_previous_sigsegv, SA_ONSTACK); handle_signal(SIGINT, sigint_handler, &g_previous_sigint); handle_signal(SIGQUIT, sigquit_handler, &g_previous_sigquit); + if (!EnsureSignalAlternateStack()) + { + return FALSE; + } + if (flags & PAL_INITIALIZE_REGISTER_SIGTERM_HANDLER) { handle_signal(SIGTERM, sigterm_handler, &g_previous_sigterm); @@ -276,6 +375,28 @@ static void sigfpe_handler(int code, siginfo_t *siginfo, void *context) /*++ Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +extern "C" void signal_handler_worker(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint) +{ + // TODO: First variable parameter says whether a read (0) or write (non-0) caused the + // fault. We must disassemble the instruction at record.ExceptionAddress + // to correctly fill in this value. + returnPoint->returnFromHandler = common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr); + setcontext(&returnPoint->context); +} + +/*++ +Function : sigsegv_handler handle SIGSEGV signal (EXCEPTION_ACCESS_VIOLATION, others) @@ -289,10 +410,38 @@ static void sigsegv_handler(int code, siginfo_t *siginfo, void *context) { if (PALIsInitialized()) { - // TODO: First variable parameter says whether a read (0) or write (non-0) caused the - // fault. We must disassemble the instruction at record.ExceptionAddress - // to correctly fill in this value. - if (common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr)) + // First check if we have a stack overflow + size_t sp = (size_t)GetNativeContextSP((native_context_t *)context); + size_t failureAddress = (size_t)siginfo->si_addr; + + // If the failure address is at most one page above or below the stack pointer, + // we have a stack overflow. + if ((failureAddress - (sp - VIRTUAL_PAGE_SIZE)) < 2 * VIRTUAL_PAGE_SIZE) + { + (void)write(STDERR_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); + PROCAbort(); + } + + // Now that we know the SIGSEGV didn't happen due to a stack overflow, execute the common + // hardware signal handler on the original stack. + + // Establish a return point in case the common_signal_handler returns + + volatile bool contextInitialization = true; + + SignalHandlerWorkerReturnPoint returnPoint; + getcontext(&returnPoint.context); + + // When the signal handler worker completes, it uses setcontext to return to this point + + if (contextInitialization) + { + contextInitialization = false; + ExecuteHandlerOnOriginalStack(code, siginfo, context, &returnPoint); + _ASSERTE(FALSE); // The ExecuteHandlerOnOriginalStack should never return + } + + if (returnPoint.returnFromHandler) { return; } @@ -666,11 +815,11 @@ Parameters : note : if sigfunc is NULL, the default signal handler is restored --*/ -void handle_signal(int signal_id, SIGFUNC sigfunc, struct sigaction *previousAction) +void handle_signal(int signal_id, SIGFUNC sigfunc, struct sigaction *previousAction, int additionalFlags) { struct sigaction newAction; - newAction.sa_flags = SA_RESTART; + newAction.sa_flags = SA_RESTART | additionalFlags; #if HAVE_SIGINFO_T newAction.sa_handler = NULL; newAction.sa_sigaction = sigfunc; diff --git a/src/pal/src/exception/signal.hpp b/src/pal/src/exception/signal.hpp deleted file mode 100644 index cd019e676b..0000000000 --- a/src/pal/src/exception/signal.hpp +++ /dev/null @@ -1,52 +0,0 @@ -// 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: - - exception/signal.hpp - -Abstract: - Private signal handling utilities for SEH - - - ---*/ - -#ifndef _PAL_SIGNAL_HPP_ -#define _PAL_SIGNAL_HPP_ - -#if !HAVE_MACH_EXCEPTIONS - -/*++ -Function : - SEHInitializeSignals - - Set-up signal handlers to catch signals and translate them to exceptions - -Parameters : - flags: PAL initialization flags - -Return : - TRUE in case of a success, FALSE otherwise ---*/ -BOOL SEHInitializeSignals(DWORD flags); - -/*++ -Function : - SEHCleanupSignals - - Restore default signal handlers - - (no parameters, no return value) ---*/ -void SEHCleanupSignals(); - -#endif // !HAVE_MACH_EXCEPTIONS - -#endif /* _PAL_SIGNAL_HPP_ */ - diff --git a/src/pal/src/include/pal/context.h b/src/pal/src/include/pal/context.h index 08fa05da7d..db6d69579a 100644 --- a/src/pal/src/include/pal/context.h +++ b/src/pal/src/include/pal/context.h @@ -639,6 +639,21 @@ LPVOID GetNativeContextPC(const native_context_t *context); /*++ Function : + GetNativeContextSP + + Returns the stack pointer from the native context. + +Parameters : + const native_context_t *native : native context + +Return value : + The stack pointer from the native context. + +--*/ +LPVOID GetNativeContextSP(const native_context_t *context); + +/*++ +Function : CONTEXTGetExceptionCodeForSignal Translates signal and context information to a Win32 exception code. diff --git a/src/pal/src/include/pal/signal.hpp b/src/pal/src/include/pal/signal.hpp new file mode 100644 index 0000000000..f9afd49b33 --- /dev/null +++ b/src/pal/src/include/pal/signal.hpp @@ -0,0 +1,141 @@ +// 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: + + include/pal/signal.hpp + +Abstract: + Private signal handling utilities for SEH + + + +--*/ + +#ifndef _PAL_SIGNAL_HPP_ +#define _PAL_SIGNAL_HPP_ + +#if !HAVE_MACH_EXCEPTIONS + +struct SignalHandlerWorkerReturnPoint; + +/*++ +Function : + CallSignalHandlerWrapperX + + These functions are never called, only a fake stack frame will be setup to have a return + address set to SignalHandlerWorkerReturnX during SIGSEGV handling. + It enables the unwinder to unwind stack from the handling code to the actual failure site. + + There are four variants of this function based on what stack alignment needs to be done + to ensure properly aligned stack pointer at the call site of the signal_handler_worker. + +Parameters : + none + + (no return value) +--*/ +extern "C" void CallSignalHandlerWrapper0(); +extern "C" void CallSignalHandlerWrapper4(); +extern "C" void CallSignalHandlerWrapper8(); +extern "C" void CallSignalHandlerWrapper12(); + +// Offset of the return address from the signal_handler_worker in the CallSignalHandlerWrapperX +// relative to the start of the function. +// There are four offsets matching the stack alignments as described in the function header above. +extern "C" int SignalHandlerWorkerReturnOffset0; +extern "C" int SignalHandlerWorkerReturnOffset4; +extern "C" int SignalHandlerWorkerReturnOffset8; +extern "C" int SignalHandlerWorkerReturnOffset12; + +/*++ +Function : + signal_handler_worker + + Handles signal on the original stack where the signal occured. + Invoked via setcontext. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +extern "C" void signal_handler_worker(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint); + +/*++ +Function : + ExecuteHandlerOnOriginalStack + + Executes signal_handler_worker on the original stack where the signal occured. + It installs fake stack frame to enable stack unwinding to the signal source location. + +Parameters : + POSIX signal handler parameter list ("man sigaction" for details) + returnPoint - context to which the function returns if the common_signal_handler returns + + (no return value) +--*/ +void ExecuteHandlerOnOriginalStack(int code, siginfo_t *siginfo, void *context, SignalHandlerWorkerReturnPoint* returnPoint); + +/*++ +Function : + EnsureSignalAlternateStack + + Ensure that alternate stack for signal handling is allocated for the current thread + +Parameters : + None + +Return : + TRUE in case of a success, FALSE otherwise +--*/ +BOOL EnsureSignalAlternateStack(); + +/*++ +Function : + FreeSignalAlternateStack + + Free alternate stack for signal handling + +Parameters : + None + +Return : + None +--*/ +void FreeSignalAlternateStack(); + +/*++ +Function : + SEHInitializeSignals + + Set-up signal handlers to catch signals and translate them to exceptions + +Parameters : + flags: PAL initialization flags + +Return : + TRUE in case of a success, FALSE otherwise +--*/ +BOOL SEHInitializeSignals(DWORD flags); + +/*++ +Function : + SEHCleanupSignals + + Restore default signal handlers + + (no parameters, no return value) +--*/ +void SEHCleanupSignals(); + +#endif // !HAVE_MACH_EXCEPTIONS + +#endif /* _PAL_SIGNAL_HPP_ */ + diff --git a/src/pal/src/init/sxs.cpp b/src/pal/src/init/sxs.cpp index 225f91684b..3f323c6a6e 100644 --- a/src/pal/src/init/sxs.cpp +++ b/src/pal/src/init/sxs.cpp @@ -16,6 +16,7 @@ #include "pal/module.h" #include "pal/process.h" #include "pal/seh.hpp" +#include "pal/signal.hpp" using namespace CorUnix; @@ -106,8 +107,20 @@ PAL_ERROR AllocatePalThread(CPalThread **ppThread) { CPalThread *pThread = NULL; + PAL_ERROR palError; - PAL_ERROR palError = CreateThreadData(&pThread); +#if !HAVE_MACH_EXCEPTIONS + // Ensure alternate stack for SIGSEGV handling. Our SIGSEGV handler is set to + // run on an alternate stack and the stack needs to be allocated per thread. + if (!EnsureSignalAlternateStack()) + { + ERROR("Cannot allocate alternate stack for SIGSEGV handler!\n"); + palError = ERROR_NOT_ENOUGH_MEMORY; + goto exit; + } +#endif // !HAVE_MACH_EXCEPTIONS + + palError = CreateThreadData(&pThread); if (NO_ERROR != palError) { goto exit; diff --git a/src/pal/src/thread/context.cpp b/src/pal/src/thread/context.cpp index 98867c9554..04a6fe5aaf 100644 --- a/src/pal/src/thread/context.cpp +++ b/src/pal/src/thread/context.cpp @@ -617,6 +617,35 @@ LPVOID GetNativeContextPC(const native_context_t *context) /*++ Function : + GetNativeContextSP + + Returns the stack pointer from the native context. + +Parameters : + const native_context_t *native : native context + +Return value : + The stack pointer from the native context. + +--*/ +LPVOID GetNativeContextSP(const native_context_t *context) +{ +#ifdef _AMD64_ + return (LPVOID)MCREG_Rsp(context->uc_mcontext); +#elif defined(_X86_) + return (LPVOID) MCREG_Esp(context->uc_mcontext); +#elif defined(_ARM_) + return (LPVOID) MCREG_Sp(context->uc_mcontext); +#elif defined(_ARM64_) + return (LPVOID) MCREG_Sp(context->uc_mcontext); +#else +# error implement me for this architecture +#endif +} + + +/*++ +Function : CONTEXTGetExceptionCodeForSignal Translates signal and context information to a Win32 exception code. diff --git a/src/pal/src/thread/thread.cpp b/src/pal/src/thread/thread.cpp index 53283320c5..df42ebcc96 100644 --- a/src/pal/src/thread/thread.cpp +++ b/src/pal/src/thread/thread.cpp @@ -28,6 +28,7 @@ SET_DEFAULT_DEBUG_CHANNEL(THREAD); // some headers have code with asserts, so do #include "pal/handlemgr.hpp" #include "pal/cs.hpp" #include "pal/seh.hpp" +#include "pal/signal.hpp" #include "procprivate.hpp" #include "pal/process.h" @@ -177,6 +178,10 @@ static void InternalEndCurrentThreadWrapper(void *arg) // in InternalEndCurrentThread. InternalEndCurrentThread(pThread); pthread_setspecific(thObjKey, NULL); + +#if !HAVE_MACH_EXCEPTIONS + FreeSignalAlternateStack(); +#endif // !HAVE_MACH_EXCEPTIONS } /*++ @@ -1659,6 +1664,14 @@ CPalThread::ThreadEntry( goto fail; } +#if !HAVE_MACH_EXCEPTIONS + if (!EnsureSignalAlternateStack()) + { + ASSERT("Cannot allocate alternate stack for SIGSEGV!\n"); + goto fail; + } +#endif // !HAVE_MACH_EXCEPTIONS + #if defined(FEATURE_PAL_SXS) && defined(_DEBUG) // We cannot assert yet, as we haven't set in this thread into the TLS, and so __ASSERT_ENTER // will fail if the assert fails and we'll crash. |