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>
This commit is contained in:
Corinna Vinschen
2015-07-04 22:49:30 +02:00
parent 757c0871f7
commit 2cd7eb7f60
8 changed files with 291 additions and 134 deletions

View File

@ -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);