From: Taylor R Campbell Date: Thu, 5 Aug 2010 17:15:17 +0000 (+0000) Subject: Ensure that signal handlers see the C stack, not the Scheme stack. X-Git-Tag: 20101212-Gtk~123 X-Git-Url: https://birchwood-abbey.net/git?a=commitdiff_plain;h=58ebfe2cf949468ce5d14d2372462e13f223989d;p=mit-scheme.git Ensure that signal handlers see the C stack, not the Scheme stack. 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.) --- diff --git a/src/microcode/cmpauxmd/i386.m4 b/src/microcode/cmpauxmd/i386.m4 index c1c61e0d9..657285758 100644 --- a/src/microcode/cmpauxmd/i386.m4 +++ b/src/microcode/cmpauxmd/i386.m4 @@ -518,6 +518,45 @@ done_setting_up_cpuid: no_cpuid_instr: leave ret + +# 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 diff --git a/src/microcode/cmpauxmd/x86-64.m4 b/src/microcode/cmpauxmd/x86-64.m4 index 6d5a523c1..fedb3e2ea 100644 --- a/src/microcode/cmpauxmd/x86-64.m4 +++ b/src/microcode/cmpauxmd/x86-64.m4 @@ -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) + # 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 diff --git a/src/microcode/cmpintmd/i386.h b/src/microcode/cmpintmd/i386.h index b51d28a29..5044e2970 100644 --- a/src/microcode/cmpintmd/i386.h +++ b/src/microcode/cmpintmd/i386.h @@ -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); diff --git a/src/microcode/cmpintmd/x86-64.h b/src/microcode/cmpintmd/x86-64.h index ec6e0e8af..55090c953 100644 --- a/src/microcode/cmpintmd/x86-64.h +++ b/src/microcode/cmpintmd/x86-64.h @@ -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); diff --git a/src/microcode/uxsig.h b/src/microcode/uxsig.h index 516782a37..4c1c6ae24 100644 --- a/src/microcode/uxsig.h +++ b/src/microcode/uxsig.h @@ -44,9 +44,46 @@ USA. # endif #endif +#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, \