Improve stack overflow reporting (#9650)
authorJan Vorlicek <janvorli@microsoft.com>
Wed, 22 Feb 2017 22:18:01 +0000 (23:18 +0100)
committerGitHub <noreply@github.com>
Wed, 22 Feb 2017 22:18:01 +0000 (23:18 +0100)
* Improve stack overflow reporting

This change modifies the SIGSEGV handling to use an alternate stack so that
we can safely detect and report stack overflow even in case when we are
really out of stack. Before, we were able to detect stack overflow and report
it only when JIT inserted stack probes (for functions with frames larger than
4kB) and so there was still space on the stack to run the sigsegv handler.
It brings in some additional complexity, since we need to switch to the original
stack of the thread once we figure out the sigsegv is not a stack overflow and
if we return from the hardware exception handler, we need to switch back to the
alternate stack before returning from the sigsegv handler.
Also, the alternate stack is created per thread and so we need to correctly destroy
it when a thread terminates and also install it on foreign threads that enter PAL.
This also requires creating fake stack frames to enable the libunwind to walk
the stack from the exception handler to the sigsegv location.

* Fix stack unwinding in CallDescrWorkerInternal

While testing the change to enable stack overflow handling, I've noticed that
the PROLOG_SAVE_REG_PAIR and PROLOG_SAVE_REG_PAIR_INDEXED macros are missing
.cfi_def_cfa_register fp. That resulted in inability to unwind through the
CallDescrWorkerInternal, since this function dynamically allocates stack slots
and so the default sp based frame doesn't work.

18 files changed:
src/pal/inc/unixasmmacrosarm64.inc
src/pal/src/CMakeLists.txt
src/pal/src/arch/amd64/callsignalhandlerwrapper.S [new file with mode: 0644]
src/pal/src/arch/amd64/signalhandlerhelper.cpp [new file with mode: 0644]
src/pal/src/arch/arm/callsignalhandlerwrapper.S [new file with mode: 0644]
src/pal/src/arch/arm/signalhandlerhelper.cpp [new file with mode: 0644]
src/pal/src/arch/arm64/callsignalhandlerwrapper.S [new file with mode: 0644]
src/pal/src/arch/arm64/signalhandlerhelper.cpp [new file with mode: 0644]
src/pal/src/arch/i386/callsignalhandlerwrapper.S [new file with mode: 0644]
src/pal/src/arch/i386/signalhandlerhelper.cpp [new file with mode: 0644]
src/pal/src/exception/seh.cpp
src/pal/src/exception/signal.cpp
src/pal/src/exception/signal.hpp [deleted file]
src/pal/src/include/pal/context.h
src/pal/src/include/pal/signal.hpp [new file with mode: 0644]
src/pal/src/init/sxs.cpp
src/pal/src/thread/context.cpp
src/pal/src/thread/thread.cpp

index ed73748..f391513 100644 (file)
@@ -67,6 +67,7 @@ C_FUNC(\Name\()_End):
         .cfi_rel_offset \reg2, \ofs + 8
         .ifc \reg1, fp
         mov fp, sp
+        .cfi_def_cfa_register fp
         .endif
 .endm
 
@@ -77,6 +78,7 @@ C_FUNC(\Name\()_End):
         .cfi_rel_offset \reg2, 8
         .ifc \reg1, fp
         mov fp, sp
+        .cfi_def_cfa_register fp
         .endif
 .endm
 
index 16c9d8b..dae0f3f 100644 (file)
@@ -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 (file)
index 0000000..8260591
--- /dev/null
@@ -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 (file)
index 0000000..5e37583
--- /dev/null
@@ -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 (file)
index 0000000..266e4fd
--- /dev/null
@@ -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 (file)
index 0000000..beda0b4
--- /dev/null
@@ -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 (file)
index 0000000..6bff23b
--- /dev/null
@@ -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 (file)
index 0000000..3891b9e
--- /dev/null
@@ -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 (file)
index 0000000..26f06d9
--- /dev/null
@@ -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 (file)
index 0000000..5e7333a
--- /dev/null
@@ -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);
+}
index ad09e02..7682e9e 100644 (file)
@@ -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"
index 26e2a01..d442afa 100644 (file)
@@ -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 (file)
index cd019e6..0000000
+++ /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_ */
-
index 08fa05d..db6d695 100644 (file)
@@ -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 (file)
index 0000000..f9afd49
--- /dev/null
@@ -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_ */
+
index 225f916..3f323c6 100644 (file)
@@ -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;
index 98867c9..04a6fe5 100644 (file)
@@ -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.
index 5328332..df42ebc 100644 (file)
@@ -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.