diff options
author | Corinna Vinschen <corinna@vinschen.de> | 2015-07-04 22:49:30 +0200 |
---|---|---|
committer | Corinna Vinschen <corinna@vinschen.de> | 2015-07-04 22:49:30 +0200 |
commit | 2cd7eb7f60208b0ffd51a9e117a8846c33b4ad41 (patch) | |
tree | 0ebf0d1b51a4bd5642c288b0572939399df962a4 /winsup/cygwin/exceptions.cc | |
parent | 757c0871f74c3a2d682398490bcae8873d1fafd4 (diff) | |
download | cygnal-2cd7eb7f60208b0ffd51a9e117a8846c33b4ad41.tar.gz cygnal-2cd7eb7f60208b0ffd51a9e117a8846c33b4ad41.tar.bz2 cygnal-2cd7eb7f60208b0ffd51a9e117a8846c33b4ad41.zip |
Fix original stack when running signal handler on alternate stack
* autoload.cc (SetThreadStackGuarantee): Import.
* cygtls.h (struct _cygtls): Replace thread_context with a ucontext_t
called context.
* exceptions.cc (exception::handle): Exit from process via signal_exit
in case sig_send returns from handling a stack overflow SIGSEGV.
Explain why.
(dumpstack_overflow_wrapper): Thread wrapper to create a stackdump
from another thread.
(signal_exit): Fix argument list to reflect three-arg signal handler.
In case we have to create a stackdump for a stack overflow condition,
do so from a separate thread. Explain why.
(sigpacket::process): Don't run signal_exit on alternate stack.
(altstack_wrapper): Wrapper function to do stack correction when
calling the signal handler on an alternate stack to handle a stack
overflow. Make sure to have lots of comments.
(_cygtls::call_signal_handler): Drop local context variable to reduce
stack pressure. Use this->context instead. Change inline assembler
to call altstack_wrapper.
(_cygtls::signal_debugger): Accommodate aforementioned change to
struct _cygtls.
* tlsoffset.h: Regenerate.
* tlsoffset64.h: Regenerate.
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
Diffstat (limited to 'winsup/cygwin/exceptions.cc')
-rw-r--r-- | winsup/cygwin/exceptions.cc | 142 |
1 files changed, 121 insertions, 21 deletions
diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc index 90a8ff25d..0ce22d9b3 100644 --- a/winsup/cygwin/exceptions.cc +++ b/winsup/cygwin/exceptions.cc @@ -800,6 +800,19 @@ exception::handle (EXCEPTION_RECORD *e, exception_list *frame, CONTEXT *in, ? (void *) e->ExceptionInformation[1] : (void *) in->_GR(ip); me.incyg++; sig_send (NULL, si, &me); /* Signal myself */ + if ((NTSTATUS) e->ExceptionCode == STATUS_STACK_OVERFLOW) + { + /* If we catched a stack overflow, and if the signal handler didn't exit + or longjmp, we're back here and about to continue, supposed to run the + offending instruction again. That works on Linux, but not on Windows. + In case of a stack overflow we're not immediately returning to the + system exception handler, but to NTDLL::__stkchk. __stkchk will then + terminate the applicaton. So what we do here is to signal our current + process again, but this time with SIG_DFL action. This creates a + stackdump and then exits through our own means. */ + global_sigs[SIGSEGV].sa_handler = SIG_DFL; + sig_send (NULL, si, &me); + } me.incyg--; e->ExceptionFlags = 0; return ExceptionContinueExecution; @@ -1237,10 +1250,20 @@ set_signal_mask (sigset_t& setmask, sigset_t newmask) sig_dispatch_pending (true); } + +DWORD WINAPI +dumpstack_overflow_wrapper (PVOID arg) +{ + cygwin_exception *exc = (cygwin_exception *) arg; + + exc->dumpstack (); + return 0; +} + /* Exit due to a signal. Should only be called from the signal thread. */ extern "C" { static void -signal_exit (int sig, siginfo_t *si) +signal_exit (int sig, siginfo_t *si, void *) { debug_printf ("exiting due to signal %d", sig); exit_state = ES_SIGNAL_EXIT; @@ -1262,7 +1285,27 @@ signal_exit (int sig, siginfo_t *si) if (try_to_debug ()) break; if (si->si_code != SI_USER && si->si_cyg) - ((cygwin_exception *) si->si_cyg)->dumpstack (); + { + cygwin_exception *exc = (cygwin_exception *) si->si_cyg; + if ((NTSTATUS) exc->exception_record ()->ExceptionCode + == STATUS_STACK_OVERFLOW) + { + /* We're handling a stack overflow so we're running low + on stack (surprise!) The dumpstack method needs lots + of stack for buffers. So what we do here is to run + dumpstack in another thread with its own stack. */ + HANDLE thread = CreateThread (&sec_none_nih, 0, + dumpstack_overflow_wrapper, + exc, 0, NULL); + if (thread) + { + WaitForSingleObject (thread, INFINITE); + CloseHandle (thread); + } + } + else + ((cygwin_exception *) si->si_cyg)->dumpstack (); + } else { CONTEXT c; @@ -1470,6 +1513,8 @@ stop: exit_sig: handler = (void *) signal_exit; thissig.sa_flags |= SA_SIGINFO; + /* Don't run signal_exit on alternate stack. */ + thissig.sa_flags &= ~SA_ONSTACK; dosig: if (have_execed) @@ -1488,6 +1533,58 @@ done: } +static void +altstack_wrapper (int sig, siginfo_t *siginfo, ucontext_t *sigctx, + void (*handler) (int, siginfo_t *, void *)) +{ + siginfo_t si = *siginfo; + ULONG guard_size = 0; + DWORD old_prot = (DWORD) -1; + PTEB teb = NtCurrentTeb (); + PVOID old_limit = NULL; + + /* Check if we're just handling a stack overflow. If so... */ + if (sig == SIGSEGV && si.si_cyg + && ((cygwin_exception *) si.si_cyg)->exception_record ()->ExceptionCode + == (DWORD) STATUS_STACK_OVERFLOW) + { + /* ...restore guard pages in original stack as if MSVCRT::_resetstkovlw + has been called. + + Compute size of guard pages. If SetThreadStackGuarantee isn't + supported, or if it returns 0, use the default guard page size. */ + if (wincap.has_set_thread_stack_guarantee ()) + SetThreadStackGuarantee (&guard_size); + if (!guard_size) + guard_size = wincap.def_guard_page_size (); + else + guard_size += wincap.page_size (); + old_limit = teb->Tib.StackLimit; + /* Amazing but true: This VirtualProtect call automatically fixes the + value of teb->Tib.StackLimit on some systems.*/ + if (VirtualProtect (teb->Tib.StackLimit, guard_size, + PAGE_READWRITE | PAGE_GUARD, &old_prot) + && old_limit == teb->Tib.StackLimit) + teb->Tib.StackLimit = (caddr_t) old_limit + guard_size; + } + handler (sig, &si, sigctx); + if (old_prot != (DWORD) -1) + { + /* Typically the handler would exit or at least perform a siglongjmp + trying to overcome a SEGV condition. However, if we return from a + segv handler after a stack overflow, we're dead. While on Linux the + process returns to the offending code and thus the handler is called + ad infinitum, on Windows the NTDLL::__stkchk function will simply kill + the process. So what we do here is to remove the guard pages again so + we can return to exception::handle. exception::handle will then call + sig_send again, this time with SIG_DFL action, so at least we get a + stackdump. */ + if (VirtualProtect ((caddr_t) teb->Tib.StackLimit - guard_size, + guard_size, old_prot, &old_prot)) + teb->Tib.StackLimit = old_limit; + } +} + int _cygtls::call_signal_handler () { @@ -1516,7 +1613,6 @@ _cygtls::call_signal_handler () siginfo_t thissi = infodata; void (*thisfunc) (int, siginfo_t *, void *) = func; - ucontext_t context; ucontext_t *thiscontext = NULL; /* Only make a context for SA_SIGINFO handlers */ @@ -1596,9 +1692,8 @@ _cygtls::call_signal_handler () /* Compute new stackbase. We start from the high address, aligned to 16 byte. */ - uintptr_t new_sp = (uintptr_t) _my_tls.altstack.ss_sp - + _my_tls.altstack.ss_size; - new_sp &= ~0xf; + uintptr_t new_sp = ((uintptr_t) _my_tls.altstack.ss_sp + + _my_tls.altstack.ss_size) & ~0xf; /* In assembler: Save regs on new stack, move to alternate stack, call thisfunc, revert stack regs. */ #ifdef __x86_64__ @@ -1620,8 +1715,9 @@ _cygtls::call_signal_handler () leaq %[SI], %%rdx # &thissi to 2nd arg reg \n\ movq %[CTX], %%r8 # thiscontext to 3rd arg reg \n\ movq %[FUNC], %%r9 # thisfunc to r9 \n\ + leaq %[WRAPPER], %%r10 # wrapper address to r10 \n\ movq %%rax, %%rsp # Move alt stack into rsp \n\ - call *%%r9 # Call thisfunc \n\ + call *%%r10 # Call wrapper \n\ movq %%rsp, %%rax # Restore clobbered regs \n\ movq 0x58(%%rax), %%rsp \n\ movq 0x50(%%rax), %%rbp \n\ @@ -1635,38 +1731,42 @@ _cygtls::call_signal_handler () [SIG] "o" (thissig), [SI] "o" (thissi), [CTX] "o" (thiscontext), - [FUNC] "o" (thisfunc) + [FUNC] "o" (thisfunc), + [WRAPPER] "o" (altstack_wrapper) : "memory"); #else /* Clobbered regs: ecx, edx, ebp, esp */ __asm__ ("\n\ movl %[NEW_SP], %%eax # Load alt stack into eax \n\ - subl $28, %%eax # Make room on alt stack for \n\ + subl $32, %%eax # Make room on alt stack for \n\ # clobbered regs and args to \n\ # signal handler \n\ - movl %%ecx, 12(%%eax) # Save other clobbered regs \n\ - movl %%edx, 16(%%eax) \n\ - movl %%ebp, 20(%%eax) \n\ - movl %%esp, 24(%%eax) \n\ + movl %%ecx, 16(%%eax) # Save clobbered regs \n\ + movl %%edx, 20(%%eax) \n\ + movl %%ebp, 24(%%eax) \n\ + movl %%esp, 28(%%eax) \n\ movl %[SIG], %%ecx # thissig to 1st arg slot \n\ movl %%ecx, (%%eax) \n\ leal %[SI], %%ecx # &thissi to 2nd arg slot \n\ movl %%ecx, 4(%%eax) \n\ movl %[CTX], %%ecx # thiscontext to 3rd arg slot\n\ movl %%ecx, 8(%%eax) \n\ - movl %[FUNC], %%ecx # thisfunc to ecx \n\ + movl %[FUNC], %%ecx # thisfunc to 4th arg slot \n\ + movl %%ecx, 12(%%eax) \n\ + leal %[WRAPPER], %%ecx # thisfunc to ecx \n\ movl %%eax, %%esp # Move alt stack into esp \n\ call *%%ecx # Call thisfunc \n\ movl %%esp, %%eax # Restore clobbered regs \n\ - movl 24(%%eax), %%esp \n\ - movl 20(%%eax), %%ebp \n\ - movl 16(%%eax), %%edx \n\ - movl 12(%%eax), %%eax \n" + movl 28(%%eax), %%esp \n\ + movl 24(%%eax), %%ebp \n\ + movl 20(%%eax), %%edx \n\ + movl 16(%%eax), %%eax \n" : : [NEW_SP] "o" (new_sp), [SIG] "o" (thissig), [SI] "o" (thissi), [CTX] "o" (thiscontext), - [FUNC] "o" (thisfunc) + [FUNC] "o" (thisfunc), + [WRAPPER] "o" (altstack_wrapper) : "memory"); #endif } @@ -1708,12 +1808,12 @@ _cygtls::signal_debugger (siginfo_t& si) #else c.Eip = retaddr (); #endif - memcpy (&thread_context, &c, sizeof (CONTEXT)); + memcpy (&context.uc_mcontext, &c, sizeof (CONTEXT)); /* Enough space for 32/64 bit addresses */ char sigmsg[2 * sizeof (_CYGWIN_SIGNAL_STRING " ffffffff ffffffffffffffff")]; __small_sprintf (sigmsg, _CYGWIN_SIGNAL_STRING " %d %y %p", - si.si_signo, thread_id, &thread_context); + si.si_signo, thread_id, &context.uc_mcontext); OutputDebugString (sigmsg); } ResumeThread (th); |