Ensure that signal handlers see the C stack, not the Scheme stack.
authorTaylor R Campbell <campbell@mumble.net>
Thu, 5 Aug 2010 17:15:17 +0000 (17:15 +0000)
committerTaylor R Campbell <campbell@mumble.net>
Thu, 5 Aug 2010 17:15:17 +0000 (17:15 +0000)
Do this by wrapping all the signal handlers in stubs that call an
assembly hook to make the stack pointer point into the C stack rather
than the Scheme stack if necessary.  To indicate that this is not
necessary, define SIGNAL_HANDLERS_CAN_USE_SCHEME_STACK.  For now, I'm
leaving that undefined by default, because it is the safer option.

This solves a problem on operating systems such as NetBSD that store
the current pthread identifier in the stack pointer.  When Scheme's
signal handler calls routines that are pthread cancellation points,
such as waitpid, they try to find the current pthread identifier in a
stack pointer that points off into oblivion (into Scheme's stack) and
promptly crash -- or, worse, trigger SIGSEGV, to be handled by a
signal handler while the stack pointer still points into Scheme's
stack, with the same problem.

I am told that this will be fixed in NetBSD 6 (since it interferes
not just with Scheme but also with sigaltstack, makecontext, and
anything else that wants to mess with the stack pointer), but only on
i386 and amd64 for certain, and in any case, this workaround will
work on any other systems that try to use the same trick to store the
current pthread identifier, of which I believe there may be several.
(E.g., older versions of GNU/Linux with LinuxThreads.)

src/microcode/cmpauxmd/i386.m4
src/microcode/cmpauxmd/x86-64.m4
src/microcode/cmpintmd/i386.h
src/microcode/cmpintmd/x86-64.h
src/microcode/uxsig.h

index c1c61e0d97acd49d7ab3c7a2dd15ceebd362e3e8..6572857581e1d3e805028c0f67f95c7b01d59415 100644 (file)
@@ -518,6 +518,45 @@ done_setting_up_cpuid:
 no_cpuid_instr:
        leave
        ret
+\f
+# Call a function (esp[1]) with an argument (esp[2]) and a stack
+# pointer and frame pointer from inside C.  When it returns, restore
+# the original stack pointer.  This kludge is necessary for operating
+# system libraries (notably NetBSD's libpthread) that store important
+# information in the stack pointer, and get confused when they are
+# called in a signal handler for a signal delivered while Scheme has
+# set esp to something funny.
+
+define_c_label(within_c_stack)
+       OP(mov,l)       TW(EVR(C_Stack_Pointer),REG(eax))
+       # Are we currently in C, signalled by having no saved C stack pointer?
+       OP(cmp,l)       TW(IMM(0),REG(eax))
+       # Yes: just call the function without messing with esp.
+       je              within_c_stack_from_c
+       # No: we have to switch esp to point into the C stack.
+       OP(push,l)      REG(ebp)                        # Save frame pointer
+       OP(mov,l)       TW(REG(esp),REG(ebp))
+       OP(mov,l)       TW(REG(eax),REG(esp))           # Switch to C stack
+       OP(push,l)      REG(ebp)                        # Save stack pointer
+       OP(push,l)      LOF(HEX(c),REG(ebp))            # Push argument
+       call            IJMP(LOF(8,REG(ebp)))           # Call function
+
+define_debugging_label(within_c_stack_restore)
+       OP(pop,l)       REG(eax)                        # Pop argument
+       OP(pop,l)       REG(esp)                        # Restore stack pointer
+                                                       #   and switch back to
+                                                       #   Scheme stack
+       OP(pop,l)       REG(ebp)                        # Restore frame pointer
+       ret
+
+define_debugging_label(within_c_stack_from_c)
+       OP(push,l)      REG(ebp)                        # Save a frame pointer,
+       OP(mov,l)       TW(REG(esp),REG(ebp))           #   for debuggers.
+       OP(push,l)      LOF(HEX(c),REG(ebp))            # Push argument
+       call            IJMP(LOF(8,REG(ebp)))
+       leave
+       ret
+
 
 define_c_label(C_to_interface)
        OP(push,l)      REG(ebp)                        # Link according
@@ -574,6 +613,9 @@ scheme_to_interface_proceed:
        OP(mov,l)       TW(EVR(C_Stack_Pointer),REG(esp))
        OP(mov,l)       TW(EVR(C_Frame_Pointer),REG(ebp))
 
+       # Signal to within_c_stack that we are now in C land.
+       OP(mov,l)       TW(IMM(0),EVR(C_Stack_Pointer))
+
        OP(sub,l)       TW(IMM(8),REG(esp))     # alloc struct return
 
        OP(push,l)      LOF(REGBLOCK_UTILITY_ARG4(),regs) # push utility args
@@ -614,6 +656,9 @@ interface_to_scheme_proceed:
        OP(mov,l)       TW(LOF(REGBLOCK_VAL(),regs),REG(eax)) # Value/dynamic link
        OP(mov,l)       TW(IMM(ADDRESS_MASK),rmask)     # = %ebp
 
+       # Restore the C stack pointer, which we zeroed back in
+       # scheme_to_interface, for within_c_stack.
+       OP(mov,l)       TW(REG(esp),EVR(C_Stack_Pointer))
        OP(mov,l)       TW(EVR(stack_pointer),REG(esp))
        OP(mov,l)       TW(REG(eax),REG(ecx))           # Preserve if used
        OP(and,l)       TW(rmask,REG(ecx))              # Restore potential dynamic link
index 6d5a523c19b3de9ba39989789eba0d9a8cae17e9..fedb3e2ea0f7621cefab4c8f9f11ce56de55bffc 100644 (file)
@@ -374,6 +374,41 @@ define_c_label(x86_64_fpe_reset_traps)
        leave
        ret
 
+# Call a function (rdi) with an argument (rsi) and a stack pointer and
+# frame pointer from inside C.  When it returns, restore the original
+# stack pointer.  This kludge is necessary for operating system
+# libraries (notably NetBSD's libpthread) that store important
+# information in the stack pointer, and get confused when they are
+# called in a signal handler for a signal delivered while Scheme has
+# set esp to something they consider funny.
+
+define_c_label(within_c_stack)
+       OP(mov,q)       TW(ABS(EVR(C_Stack_Pointer)),REG(rax))
+       # Are we currently in C, signalled by having no saved C stack pointer?
+       OP(cmp,q)       TW(IMM(0),REG(rax))
+       # Yes: just call the function without messing with rsp.
+       je              within_c_stack_from_c
+       # No: we have to switch rsp to point into the C stack.
+       OP(push,q)      REG(rbp)                        # Save frame pointer
+       OP(mov,q)       TW(REG(rsp),REG(rbp))
+       OP(mov,q)       TW(REG(rax),REG(rsp))           # Switch to C stack
+       OP(push,q)      REG(rbp)                        # Save stack pointer
+       OP(mov,q)       TW(REG(rdi),REG(rax))           # arg1 (fn) -> rax
+       OP(mov,q)       TW(REG(rsi),REG(rdi))           # arg2 (arg) -> arg1
+       call            IJMP(REG(rax))                  # call fn(arg)
+
+define_debugging_label(within_c_stack_restore)
+       OP(pop,q)       REG(rsp)                        # Restore stack pointer
+                                                       #   and switch back to
+                                                       #   Scheme stack
+       OP(pop,q)       REG(rbp)                        # Restore frame pointer
+       ret
+
+define_debugging_label(within_c_stack_from_c)
+       OP(mov,q)       TW(REG(rdi),REG(rax))           # arg1 (fn) -> rax
+       OP(mov,q)       TW(REG(rsi),REG(rdi))           # arg2 (arg) -> arg1
+       jmp             IJMP(REG(rax))                  # tail-call fn(arg)
+\f
 # C_to_interface passes control from C into Scheme.  To C it is a
 # unary procedure; its one argument is passed in rdi.  It saves the
 # state of the C world (the C frame pointer and stack pointer) and
@@ -433,6 +468,9 @@ define_debugging_label(scheme_to_interface)
        OP(mov,q)       TW(ABS(EVR(C_Stack_Pointer)),REG(rsp))
        OP(mov,q)       TW(ABS(EVR(C_Frame_Pointer)),REG(rbp))
 
+       # Signal to within_c_stack that we are now in C land.
+       OP(mov,q)       TW(IMM(0),ABS(EVR(C_Stack_Pointer)))
+
        OP(sub,q)       TW(IMM(16),REG(rsp))    # alloc struct return
        OP(mov,q)       TW(REG(rsp),REG(rdi))   # Structure is first argument.
        OP(mov,q)       TW(REG(rbx),REG(rsi))   # rbx -> second argument.
@@ -460,6 +498,9 @@ ifdef(`WIN32',                                              # Register block = %rsi
        OP(mov,q)       TW(ABS(EVR(Free)),rfree)        # Free pointer = %rdi
        OP(mov,q)       TW(QOF(REGBLOCK_VAL(),regs),REG(rax)) # Value/dynamic link
        OP(mov,q)       TW(IMM(ADDRESS_MASK),rmask)     # = %rbp
+       # Restore the C stack pointer, which we zeroed back in
+       # scheme_to_interface, for within_c_stack.
+       OP(mov,q)       TW(REG(rsp),ABS(EVR(C_Stack_Pointer)))
        OP(mov,q)       TW(ABS(EVR(stack_pointer)),REG(rsp))
        OP(mov,q)       TW(REG(rax),REG(rcx))           # Preserve if used
        OP(and,q)       TW(rmask,REG(rcx))              # Restore potential dynamic link
index b51d28a29d39f284e514279552450d343e339fc4..5044e2970c22928c34a5d75f9b78bcd75621f57d 100644 (file)
@@ -263,6 +263,7 @@ typedef struct
 #endif
 
 extern int ASM_ENTRY_POINT (i386_interface_initialize) (void);
+extern void ASM_ENTRY_POINT (within_c_stack) (void (*) (void *), void *);
 
 extern void asm_assignment_trap (void);
 extern void asm_dont_serialize_cache (void);
index ec6e0e8af4cace24ccc663c6b4fedb431a1fb2d7..55090c9538ad683b1f3a7fd1f78740ab259e06d7 100644 (file)
@@ -175,6 +175,7 @@ typedef byte_t insn_t;
 #endif
 
 extern void ASM_ENTRY_POINT (x86_64_fpe_reset_traps) (void);
+extern void ASM_ENTRY_POINT (within_c_stack) (void (*) (void *), void *);
 
 extern void asm_assignment_trap (void);
 extern void asm_dont_serialize_cache (void);
index 516782a370b7d699b12034c7faeb0196b08a6fd2..4c1c6ae240bdd402d12be4f9b9e03911b466136e 100644 (file)
@@ -44,9 +44,46 @@ USA.
 #  endif
 #endif
 \f
+#if defined(CC_IS_NATIVE) && !defined(SIGNAL_HANDLERS_CAN_USE_SCHEME_STACK)
+
+struct signal_instance
+{
+  int signo;
+  SIGINFO_T info;
+  SIGCONTEXT_ARG_T * pscp;
+};
+
+#  define DEFUN_STD_HANDLER(name, statement)                   \
+                                                               \
+DEFUN_STD_HANDLER_ (name##_body, statement)                    \
+                                                               \
+void                                                           \
+name##_wrapper (void *context)                                 \
+{                                                              \
+  struct signal_instance *i = context;                         \
+  (void) name##_body ((i->signo), (i->info), (i->pscp));       \
+}                                                              \
+                                                               \
+Tsignal_handler_result                                         \
+name (int signo, SIGINFO_T info, SIGCONTEXT_ARG_T * pscp)      \
+{                                                              \
+  struct signal_instance i;                                    \
+  (i.signo) = signo;                                           \
+  (i.info) = info;                                             \
+  (i.pscp) = pscp;                                             \
+  within_c_stack ((&name##_wrapper), (&i));                    \
+  SIGNAL_HANDLER_RETURN ();                                    \
+}
+
+#else
+
+#  define DEFUN_STD_HANDLER DEFUN_STD_HANDLER_
+
+#endif /* CC_SUPPORT_P && !SIGNAL_HANDLERS_CAN_USE_SCHEME_STACK */
+
 #ifndef NEED_HANDLER_TRANSACTION
 
-#define DEFUN_STD_HANDLER(name, statement)                             \
+#define DEFUN_STD_HANDLER_(name, statement)                            \
 Tsignal_handler_result                                                 \
 name (int signo,                                                       \
       SIGINFO_T info,                                                  \
@@ -70,7 +107,7 @@ struct handler_record
   Tsignal_handler handler;
 };
 
-#define DEFUN_STD_HANDLER(name, statement)                             \
+#define DEFUN_STD_HANDLER_(name, statement)                            \
 Tsignal_handler_result                                                 \
 name (int signo,                                                       \
       SIGINFO_T info,                                                  \