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:
@ -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);
|
||||
|
Reference in New Issue
Block a user