summaryrefslogtreecommitdiffstats
path: root/winsup/cygwin/exceptions.cc
diff options
context:
space:
mode:
authorCorinna Vinschen <corinna@vinschen.de>2015-07-04 22:49:30 +0200
committerCorinna Vinschen <corinna@vinschen.de>2015-07-04 22:49:30 +0200
commit2cd7eb7f60208b0ffd51a9e117a8846c33b4ad41 (patch)
tree0ebf0d1b51a4bd5642c288b0572939399df962a4 /winsup/cygwin/exceptions.cc
parent757c0871f74c3a2d682398490bcae8873d1fafd4 (diff)
downloadcygnal-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.cc142
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);