1915 lines
55 KiB
C++
1915 lines
55 KiB
C++
/* exceptions.cc
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#define CYGTLS_HANDLE
|
|
#include "winsup.h"
|
|
#include "miscfuncs.h"
|
|
#include <imagehlp.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <syslog.h>
|
|
#include <wchar.h>
|
|
|
|
#include "cygtls.h"
|
|
#include "pinfo.h"
|
|
#include "sigproc.h"
|
|
#include "shared_info.h"
|
|
#include "perprocess.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "child_info.h"
|
|
#include "ntdll.h"
|
|
#include "exception.h"
|
|
#include "posix_timer.h"
|
|
#include "gcc_seh.h"
|
|
|
|
/* Define macros for CPU-agnostic register access. The _CX_foo
|
|
macros are for access into CONTEXT, the _MC_foo ones for access into
|
|
mcontext. The idea is to access the registers in terms of their job,
|
|
not in terms of their name on the given target. */
|
|
#ifdef __x86_64__
|
|
#define _CX_instPtr Rip
|
|
#define _CX_stackPtr Rsp
|
|
#define _CX_framePtr Rbp
|
|
/* For special register access inside mcontext. */
|
|
#define _MC_retReg rax
|
|
#define _MC_instPtr rip
|
|
#define _MC_stackPtr rsp
|
|
#define _MC_uclinkReg rbx /* MUST be callee-saved reg */
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
|
|
#define CALL_HANDLER_RETRY_OUTER 10
|
|
#define CALL_HANDLER_RETRY_INNER 10
|
|
#define DUMPSTACK_FRAME_LIMIT 32
|
|
|
|
PWCHAR debugger_command;
|
|
extern uint8_t _sigbe;
|
|
extern uint8_t _sigdelayed_end;
|
|
|
|
static BOOL ctrl_c_handler (DWORD);
|
|
|
|
static const struct
|
|
{
|
|
NTSTATUS code;
|
|
const char *name;
|
|
} status_info[] =
|
|
{
|
|
#define X(s) s, #s
|
|
{ X (STATUS_ABANDONED_WAIT_0) },
|
|
{ X (STATUS_ACCESS_VIOLATION) },
|
|
{ X (STATUS_ARRAY_BOUNDS_EXCEEDED) },
|
|
{ X (STATUS_BREAKPOINT) },
|
|
{ X (STATUS_CONTROL_C_EXIT) },
|
|
{ X (STATUS_DATATYPE_MISALIGNMENT) },
|
|
{ X (STATUS_FLOAT_DENORMAL_OPERAND) },
|
|
{ X (STATUS_FLOAT_DIVIDE_BY_ZERO) },
|
|
{ X (STATUS_FLOAT_INEXACT_RESULT) },
|
|
{ X (STATUS_FLOAT_INVALID_OPERATION) },
|
|
{ X (STATUS_FLOAT_OVERFLOW) },
|
|
{ X (STATUS_FLOAT_STACK_CHECK) },
|
|
{ X (STATUS_FLOAT_UNDERFLOW) },
|
|
{ X (STATUS_GUARD_PAGE_VIOLATION) },
|
|
{ X (STATUS_ILLEGAL_INSTRUCTION) },
|
|
{ X (STATUS_INTEGER_DIVIDE_BY_ZERO) },
|
|
{ X (STATUS_INTEGER_OVERFLOW) },
|
|
{ X (STATUS_INVALID_DISPOSITION) },
|
|
{ X (STATUS_IN_PAGE_ERROR) },
|
|
{ X (STATUS_NONCONTINUABLE_EXCEPTION) },
|
|
{ X (STATUS_NO_MEMORY) },
|
|
{ X (STATUS_PENDING) },
|
|
{ X (STATUS_PRIVILEGED_INSTRUCTION) },
|
|
{ X (STATUS_SINGLE_STEP) },
|
|
{ X (STATUS_STACK_OVERFLOW) },
|
|
{ X (STATUS_TIMEOUT) },
|
|
{ X (STATUS_USER_APC) },
|
|
{ X (STATUS_WAIT_0) },
|
|
{ 0, 0 }
|
|
#undef X
|
|
};
|
|
|
|
/* Initialization code. */
|
|
|
|
void
|
|
init_console_handler (bool install_handler)
|
|
{
|
|
BOOL res;
|
|
|
|
SetConsoleCtrlHandler (ctrl_c_handler, FALSE);
|
|
SetConsoleCtrlHandler (NULL, FALSE);
|
|
if (install_handler)
|
|
res = SetConsoleCtrlHandler (ctrl_c_handler, TRUE);
|
|
else
|
|
res = SetConsoleCtrlHandler (NULL, TRUE);
|
|
if (!res)
|
|
system_printf ("SetConsoleCtrlHandler failed, %E");
|
|
}
|
|
|
|
extern "C" void
|
|
error_start_init (const char *buf)
|
|
{
|
|
if (!buf || !*buf)
|
|
return;
|
|
if (!debugger_command &&
|
|
!(debugger_command = (PWCHAR) malloc ((2 * NT_MAX_PATH + 20)
|
|
* sizeof (WCHAR))))
|
|
return;
|
|
|
|
PWCHAR cp = debugger_command
|
|
+ sys_mbstowcs (debugger_command, NT_MAX_PATH, buf) - 1;
|
|
cp = wcpcpy (cp, L" \"");
|
|
wcpcpy (cp, global_progname);
|
|
for (PWCHAR p = wcschr (cp, L'\\'); p; p = wcschr (p, L'\\'))
|
|
*p = L'/';
|
|
wcscat (cp, L"\"");
|
|
}
|
|
|
|
void
|
|
cygwin_exception::open_stackdumpfile ()
|
|
{
|
|
/* If we have no executable name, or if the CWD handle is NULL,
|
|
which means, the CWD is a virtual path, don't even try to open
|
|
a stackdump file. */
|
|
if (myself->progname[0] && cygheap->cwd.get_handle ())
|
|
{
|
|
const WCHAR *p;
|
|
/* write to progname.stackdump if possible */
|
|
if (!myself->progname[0])
|
|
p = L"unknown";
|
|
else if ((p = wcsrchr (myself->progname, L'\\')))
|
|
p++;
|
|
else
|
|
p = myself->progname;
|
|
|
|
WCHAR corefile[wcslen (p) + sizeof (".stackdump")];
|
|
wcpcpy (wcpcpy(corefile, p), L".stackdump");
|
|
UNICODE_STRING ucore;
|
|
OBJECT_ATTRIBUTES attr;
|
|
/* Create the UNICODE variation of <progname>.stackdump. */
|
|
RtlInitUnicodeString (&ucore, corefile);
|
|
/* Create an object attribute which refers to <progname>.stackdump
|
|
in Cygwin's cwd. Stick to caseinsensitivity. */
|
|
InitializeObjectAttributes (&attr, &ucore, OBJ_CASE_INSENSITIVE,
|
|
cygheap->cwd.get_handle (), NULL);
|
|
IO_STATUS_BLOCK io;
|
|
NTSTATUS status;
|
|
/* Try to open it to dump the stack in it. */
|
|
status = NtCreateFile (&h, GENERIC_WRITE | SYNCHRONIZE, &attr, &io,
|
|
NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
|
|
FILE_SYNCHRONOUS_IO_NONALERT
|
|
| FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0);
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
if (!myself->cygstarted)
|
|
system_printf ("Dumping stack trace to %S", &ucore);
|
|
else
|
|
debug_printf ("Dumping stack trace to %S", &ucore);
|
|
SetStdHandle (STD_ERROR_HANDLE, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Utilities for dumping the stack, etc. */
|
|
|
|
void
|
|
cygwin_exception::dump_exception ()
|
|
{
|
|
const char *exception_name = NULL;
|
|
|
|
for (int i = 0; status_info[i].name; i++)
|
|
{
|
|
if (status_info[i].code == (NTSTATUS) e->ExceptionCode)
|
|
{
|
|
exception_name = status_info[i].name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef __x86_64__
|
|
if (exception_name)
|
|
small_printf ("Exception: %s at rip=%011X\r\n", exception_name, ctx->Rip);
|
|
else
|
|
small_printf ("Signal %d at rip=%011X\r\n", e->ExceptionCode, ctx->Rip);
|
|
small_printf ("rax=%016X rbx=%016X rcx=%016X\r\n",
|
|
ctx->Rax, ctx->Rbx, ctx->Rcx);
|
|
small_printf ("rdx=%016X rsi=%016X rdi=%016X\r\n",
|
|
ctx->Rdx, ctx->Rsi, ctx->Rdi);
|
|
small_printf ("r8 =%016X r9 =%016X r10=%016X\r\n",
|
|
ctx->R8, ctx->R9, ctx->R10);
|
|
small_printf ("r11=%016X r12=%016X r13=%016X\r\n",
|
|
ctx->R11, ctx->R12, ctx->R13);
|
|
small_printf ("r14=%016X r15=%016X\r\n", ctx->R14, ctx->R15);
|
|
small_printf ("rbp=%016X rsp=%016X\r\n", ctx->Rbp, ctx->Rsp);
|
|
small_printf ("program=%W, pid %u, thread %s\r\n",
|
|
myself->progname, myself->pid, mythreadname ());
|
|
small_printf ("cs=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\r\n",
|
|
ctx->SegCs, ctx->SegDs, ctx->SegEs, ctx->SegFs,
|
|
ctx->SegGs, ctx->SegSs);
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
}
|
|
|
|
/* A class for manipulating the stack. */
|
|
class stack_info
|
|
{
|
|
int walk (); /* Uses the "old" method */
|
|
char *next_offset () {return *((char **) sf.AddrFrame.Offset);}
|
|
bool needargs;
|
|
PUINT_PTR dummy_frame;
|
|
CONTEXT c;
|
|
UNWIND_HISTORY_TABLE hist;
|
|
__tlsstack_t *sigstackptr;
|
|
public:
|
|
STACKFRAME sf; /* For storing the stack information */
|
|
void init (PUINT_PTR, bool, PCONTEXT); /* Called the first time that stack info is needed */
|
|
|
|
/* Postfix ++ iterates over the stack, returning zero when nothing is left. */
|
|
int operator ++(int) { return walk (); }
|
|
};
|
|
|
|
/* The number of parameters used in STACKFRAME */
|
|
#define NPARAMS (sizeof (thestack.sf.Params) / sizeof (thestack.sf.Params[0]))
|
|
|
|
/* This is the main stack frame info for this process. */
|
|
static NO_COPY stack_info thestack;
|
|
|
|
/* Initialize everything needed to start iterating. */
|
|
void
|
|
stack_info::init (PUINT_PTR framep, bool wantargs, PCONTEXT ctx)
|
|
{
|
|
memset (&hist, 0, sizeof hist);
|
|
if (ctx)
|
|
memcpy (&c, ctx, sizeof c);
|
|
else
|
|
{
|
|
memset (&c, 0, sizeof c);
|
|
c.ContextFlags = CONTEXT_ALL;
|
|
}
|
|
sigstackptr = _my_tls.stackptr;
|
|
memset (&sf, 0, sizeof (sf));
|
|
if (ctx)
|
|
sf.AddrFrame.Offset = (UINT_PTR) framep;
|
|
else
|
|
{
|
|
dummy_frame = framep;
|
|
sf.AddrFrame.Offset = (UINT_PTR) &dummy_frame;
|
|
}
|
|
if (framep)
|
|
sf.AddrReturn.Offset = framep[1];
|
|
sf.AddrFrame.Mode = AddrModeFlat;
|
|
needargs = wantargs;
|
|
}
|
|
|
|
extern "C" void _cygwin_exit_return ();
|
|
|
|
static inline void
|
|
__unwind_single_frame (PCONTEXT ctx)
|
|
{
|
|
PRUNTIME_FUNCTION f;
|
|
ULONG64 imagebase;
|
|
UNWIND_HISTORY_TABLE hist = {0};
|
|
DWORD64 establisher;
|
|
PVOID hdl;
|
|
|
|
f = RtlLookupFunctionEntry (ctx->_CX_instPtr, &imagebase, &hist);
|
|
if (f)
|
|
RtlVirtualUnwind (0, imagebase, ctx->_CX_instPtr, f, ctx, &hdl,
|
|
&establisher, NULL);
|
|
else
|
|
{
|
|
ctx->_CX_instPtr = *(ULONG_PTR *) ctx->_CX_stackPtr;
|
|
ctx->_CX_stackPtr += 8;
|
|
}
|
|
}
|
|
|
|
/* Walk the stack.
|
|
|
|
On 32 bit we're doing this by looking at successive stored 'ebp' frames.
|
|
This is not foolproof. */
|
|
int
|
|
stack_info::walk ()
|
|
{
|
|
if (!c._CX_instPtr)
|
|
return 0;
|
|
|
|
sf.AddrPC.Offset = c._CX_instPtr;
|
|
sf.AddrStack.Offset = c._CX_stackPtr;
|
|
sf.AddrFrame.Offset = c._CX_framePtr;
|
|
|
|
if ((c._CX_instPtr >= (DWORD64)&_sigbe)
|
|
&& (c._CX_instPtr < (DWORD64)&_sigdelayed_end))
|
|
{
|
|
/* _sigbe and sigdelayed don't have SEH unwinding data, so virtually
|
|
unwind the tls sigstack */
|
|
c._CX_instPtr = sigstackptr[-1];
|
|
sigstackptr--;
|
|
return 1;
|
|
}
|
|
__unwind_single_frame (&c);
|
|
if (needargs && c._CX_instPtr)
|
|
{
|
|
PULONG_PTR p = (PULONG_PTR) c._CX_stackPtr;
|
|
for (unsigned i = 0; i < NPARAMS; ++i)
|
|
sf.Params[i] = p[i + 1];
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
cygwin_exception::dumpstack ()
|
|
{
|
|
static bool already_dumped;
|
|
|
|
__try
|
|
{
|
|
if (already_dumped || cygheap->rlim_core == 0Ul)
|
|
return;
|
|
already_dumped = true;
|
|
open_stackdumpfile ();
|
|
|
|
if (e)
|
|
dump_exception ();
|
|
|
|
int i;
|
|
|
|
thestack.init (framep, 1, ctx); /* Initialize from the input CONTEXT */
|
|
small_printf ("Stack trace:\r\nFrame Function Args\r\n");
|
|
for (i = 0; i < DUMPSTACK_FRAME_LIMIT && thestack++; i++)
|
|
{
|
|
small_printf ("%011X %011X", thestack.sf.AddrFrame.Offset,
|
|
thestack.sf.AddrPC.Offset);
|
|
for (unsigned j = 0; j < NPARAMS; j++)
|
|
small_printf ("%s%011X", j == 0 ? " (" : ", ",
|
|
thestack.sf.Params[j]);
|
|
small_printf (")\r\n");
|
|
}
|
|
small_printf ("End of stack trace%s\n",
|
|
i == DUMPSTACK_FRAME_LIMIT ?
|
|
" (more stack frames may be present)" : "");
|
|
if (h)
|
|
NtClose (h);
|
|
}
|
|
__except (NO_ERROR) {}
|
|
__endtry
|
|
}
|
|
|
|
bool
|
|
_cygtls::inside_kernel (CONTEXT *cx)
|
|
{
|
|
int res;
|
|
MEMORY_BASIC_INFORMATION m;
|
|
|
|
if (!isinitialized ())
|
|
return true;
|
|
|
|
memset (&m, 0, sizeof m);
|
|
if (!VirtualQuery ((LPCVOID) cx->_CX_instPtr, &m, sizeof m))
|
|
sigproc_printf ("couldn't get memory info, pc %p, %E", cx->_CX_instPtr);
|
|
|
|
size_t size = (windows_system_directory_length + 6) * sizeof (WCHAR);
|
|
PWCHAR checkdir = (PWCHAR) alloca (size);
|
|
memset (checkdir, 0, size);
|
|
|
|
# define h ((HMODULE) m.AllocationBase)
|
|
if (!h || m.State != MEM_COMMIT) /* Be defensive */
|
|
res = true;
|
|
else if (h == hntdll)
|
|
res = true; /* Calling GetModuleFilename on ntdll.dll
|
|
can hang */
|
|
else if (h == user_data->hmodule)
|
|
res = false;
|
|
else if (!GetModuleFileNameW (h, checkdir,
|
|
windows_system_directory_length + 6))
|
|
res = false;
|
|
else
|
|
{
|
|
/* Skip potential long path prefix. */
|
|
if (!wcsncmp (checkdir, L"\\\\?\\", 4))
|
|
checkdir += 4;
|
|
res = wcsncasecmp (windows_system_directory, checkdir,
|
|
windows_system_directory_length) == 0;
|
|
}
|
|
sigproc_printf ("pc %p, h %p, inside_kernel %d", cx->_CX_instPtr, h, res);
|
|
# undef h
|
|
return res;
|
|
}
|
|
|
|
/* Temporary (?) function for external callers to get a stack dump */
|
|
extern "C" void
|
|
cygwin_stackdump ()
|
|
{
|
|
CONTEXT c;
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
RtlCaptureContext (&c);
|
|
cygwin_exception exc ((PUINT_PTR) c._CX_framePtr, &c);
|
|
exc.dumpstack ();
|
|
}
|
|
|
|
extern "C" int
|
|
try_to_debug ()
|
|
{
|
|
if (!debugger_command)
|
|
return 0;
|
|
debug_printf ("debugger_command '%W'", debugger_command);
|
|
if (being_debugged ())
|
|
{
|
|
extern void break_here ();
|
|
break_here ();
|
|
return 0;
|
|
}
|
|
|
|
PWCHAR dbg_end = wcschr (debugger_command, L'\0');
|
|
__small_swprintf (dbg_end, L" %u", GetCurrentProcessId ());
|
|
|
|
LONG prio = GetThreadPriority (GetCurrentThread ());
|
|
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST);
|
|
PROCESS_INFORMATION pi = {NULL, 0, 0, 0};
|
|
|
|
STARTUPINFOW si = {0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, NULL};
|
|
si.lpReserved = NULL;
|
|
si.lpDesktop = NULL;
|
|
si.dwFlags = 0;
|
|
si.cb = sizeof (si);
|
|
|
|
/* FIXME: need to know handles of all running threads to
|
|
suspend_all_threads_except (current_thread_id);
|
|
*/
|
|
|
|
/* If the tty mutex is owned, we will fail to start any cygwin app
|
|
until the trapped app exits. However, this will only release any
|
|
the mutex if it is owned by this thread so that may be problematic. */
|
|
|
|
lock_ttys::release ();
|
|
|
|
/* prevent recursive exception handling */
|
|
PWCHAR rawenv = GetEnvironmentStringsW () ;
|
|
for (PWCHAR p = rawenv; *p != L'\0'; p = wcschr (p, L'\0') + 1)
|
|
{
|
|
if (wcsncmp (p, L"CYGWIN=", wcslen (L"CYGWIN=")) == 0)
|
|
{
|
|
PWCHAR q = wcsstr (p, L"error_start") ;
|
|
/* replace 'error_start=...' with '_rror_start=...' */
|
|
if (q)
|
|
{
|
|
*q = L'_' ;
|
|
SetEnvironmentVariableW (L"CYGWIN", p + wcslen (L"CYGWIN=")) ;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
FreeEnvironmentStringsW (rawenv);
|
|
|
|
console_printf ("*** starting debugger for pid %u, tid %u\n",
|
|
cygwin_pid (GetCurrentProcessId ()), GetCurrentThreadId ());
|
|
BOOL dbg;
|
|
dbg = CreateProcessW (NULL,
|
|
debugger_command,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
|
|
NULL,
|
|
NULL,
|
|
&si,
|
|
&pi);
|
|
|
|
/* We want to stop here and wait until the error_start process attaches. But
|
|
we can't wait here for the error_start process to exit, as if it's a
|
|
debugger, it might want to continue this thread. So we busy wait until a
|
|
debugger attaches, which stops this process, after which it can decide if
|
|
we continue or not. */
|
|
|
|
*dbg_end = L'\0';
|
|
if (!dbg)
|
|
system_printf ("Failed to start debugger, %E");
|
|
else
|
|
{
|
|
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_IDLE);
|
|
while (!being_debugged ())
|
|
Sleep (1);
|
|
Sleep (2000);
|
|
}
|
|
|
|
console_printf ("*** continuing pid %u from debugger call (%d)\n",
|
|
cygwin_pid (GetCurrentProcessId ()), dbg);
|
|
|
|
SetThreadPriority (GetCurrentThread (), prio);
|
|
return dbg;
|
|
}
|
|
|
|
/* myfault exception handler. */
|
|
EXCEPTION_DISPOSITION
|
|
exception::myfault (EXCEPTION_RECORD *e, exception_list *frame, CONTEXT *in,
|
|
PDISPATCHER_CONTEXT dispatch)
|
|
{
|
|
PSCOPE_TABLE table = (PSCOPE_TABLE) dispatch->HandlerData;
|
|
RtlUnwindEx (frame,
|
|
(char *) dispatch->ImageBase + table->ScopeRecord[0].JumpTarget,
|
|
e, 0, in, dispatch->HistoryTable);
|
|
/* NOTREACHED, make gcc happy. */
|
|
return ExceptionContinueSearch;
|
|
}
|
|
|
|
/* If another exception occurs while running a signal handler on an alternate
|
|
signal stack, the normal SEH handlers are skipped, because the OS exception
|
|
handling considers the current (alternate) stack "broken". However, it
|
|
still calls vectored exception handlers.
|
|
|
|
TODO: What we do here is to handle only __try/__except blocks in Cygwin.
|
|
"Normal" exceptions will simply exit the process. Still, better
|
|
than nothing... */
|
|
LONG
|
|
myfault_altstack_handler (EXCEPTION_POINTERS *exc)
|
|
{
|
|
_cygtls& me = _my_tls;
|
|
|
|
if (me.andreas)
|
|
{
|
|
CONTEXT *c = exc->ContextRecord;
|
|
|
|
/* Unwind the stack manually and call RtlRestoreContext. This
|
|
is necessary because RtlUnwindEx checks the stack for validity,
|
|
which, as outlined above, fails for the alternate stack. */
|
|
while (c->_CX_stackPtr < me.andreas->frame)
|
|
__unwind_single_frame (c);
|
|
c->_CX_instPtr = me.andreas->ret;
|
|
RtlRestoreContext (c, NULL);
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
/* Main exception handler. */
|
|
EXCEPTION_DISPOSITION
|
|
exception::handle (EXCEPTION_RECORD *e, exception_list *frame, CONTEXT *in,
|
|
PDISPATCHER_CONTEXT dispatch)
|
|
{
|
|
static int NO_COPY debugging = 0;
|
|
_cygtls& me = _my_tls;
|
|
|
|
if (debugging && ++debugging < 500000)
|
|
{
|
|
SetThreadPriority (hMainThread, THREAD_PRIORITY_NORMAL);
|
|
return ExceptionContinueExecution;
|
|
}
|
|
|
|
/* If we're exiting, tell Windows to keep looking for an
|
|
exception handler. */
|
|
if (exit_state || e->ExceptionFlags)
|
|
return ExceptionContinueSearch;
|
|
|
|
siginfo_t si = {};
|
|
si.si_code = SI_KERNEL;
|
|
/* Coerce win32 value to posix value. */
|
|
switch (e->ExceptionCode)
|
|
{
|
|
case STATUS_FLOAT_DIVIDE_BY_ZERO:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTDIV;
|
|
break;
|
|
case STATUS_FLOAT_DENORMAL_OPERAND:
|
|
case STATUS_FLOAT_INVALID_OPERATION:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTINV;
|
|
break;
|
|
case STATUS_FLOAT_STACK_CHECK:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTSUB;
|
|
break;
|
|
case STATUS_FLOAT_INEXACT_RESULT:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTRES;
|
|
break;
|
|
case STATUS_FLOAT_OVERFLOW:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTOVF;
|
|
break;
|
|
case STATUS_FLOAT_UNDERFLOW:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_FLTUND;
|
|
break;
|
|
case STATUS_INTEGER_DIVIDE_BY_ZERO:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_INTDIV;
|
|
break;
|
|
case STATUS_INTEGER_OVERFLOW:
|
|
si.si_signo = SIGFPE;
|
|
si.si_code = FPE_INTOVF;
|
|
break;
|
|
|
|
case STATUS_ILLEGAL_INSTRUCTION:
|
|
si.si_signo = SIGILL;
|
|
si.si_code = ILL_ILLOPC;
|
|
break;
|
|
|
|
case STATUS_PRIVILEGED_INSTRUCTION:
|
|
si.si_signo = SIGILL;
|
|
si.si_code = ILL_PRVOPC;
|
|
break;
|
|
|
|
case STATUS_NONCONTINUABLE_EXCEPTION:
|
|
si.si_signo = SIGILL;
|
|
si.si_code = ILL_ILLADR;
|
|
break;
|
|
|
|
case STATUS_TIMEOUT:
|
|
si.si_signo = SIGALRM;
|
|
break;
|
|
|
|
case STATUS_GUARD_PAGE_VIOLATION:
|
|
si.si_signo = SIGBUS;
|
|
si.si_code = BUS_OBJERR;
|
|
break;
|
|
|
|
case STATUS_DATATYPE_MISALIGNMENT:
|
|
si.si_signo = SIGBUS;
|
|
si.si_code = BUS_ADRALN;
|
|
break;
|
|
|
|
case STATUS_ACCESS_VIOLATION:
|
|
switch (mmap_is_attached_or_noreserve ((void *)e->ExceptionInformation[1],
|
|
1))
|
|
{
|
|
case MMAP_NORESERVE_COMMITED:
|
|
return ExceptionContinueExecution;
|
|
case MMAP_RAISE_SIGBUS: /* MAP_NORESERVE page, commit failed, or
|
|
access to mmap page beyond EOF. */
|
|
si.si_signo = SIGBUS;
|
|
si.si_code = BUS_OBJERR;
|
|
break;
|
|
default:
|
|
MEMORY_BASIC_INFORMATION m;
|
|
VirtualQuery ((PVOID) e->ExceptionInformation[1], &m, sizeof m);
|
|
si.si_signo = SIGSEGV;
|
|
si.si_code = m.State == MEM_FREE ? SEGV_MAPERR : SEGV_ACCERR;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STATUS_STACK_OVERFLOW:
|
|
/* If we encounter a stack overflow, and if the thread has no alternate
|
|
stack, don't even try to call a signal handler. This is in line with
|
|
Linux behaviour and also makes a lot of sense on Windows. */
|
|
if (me.altstack.ss_flags)
|
|
global_sigs[SIGSEGV].sa_handler = SIG_DFL;
|
|
fallthrough;
|
|
case STATUS_ARRAY_BOUNDS_EXCEEDED:
|
|
case STATUS_IN_PAGE_ERROR:
|
|
case STATUS_NO_MEMORY:
|
|
case STATUS_INVALID_DISPOSITION:
|
|
si.si_signo = SIGSEGV;
|
|
si.si_code = SEGV_MAPERR;
|
|
break;
|
|
|
|
case STATUS_CONTROL_C_EXIT:
|
|
si.si_signo = SIGINT;
|
|
break;
|
|
|
|
case STATUS_INVALID_HANDLE:
|
|
/* CloseHandle will throw this exception if it is given an
|
|
invalid handle. We don't care about the exception; we just
|
|
want CloseHandle to return an error. This can be revisited
|
|
if gcc ever supports Windows style structured exception
|
|
handling. */
|
|
return ExceptionContinueExecution;
|
|
|
|
case STATUS_GCC_THROW:
|
|
case STATUS_GCC_UNWIND:
|
|
case STATUS_GCC_FORCED:
|
|
/* According to a comment in the GCC function
|
|
_Unwind_RaiseException(), GCC expects us to continue all the
|
|
(continuable) GCC exceptions that reach us. */
|
|
return ExceptionContinueExecution;
|
|
|
|
default:
|
|
/* If we don't recognize the exception, we have to assume that
|
|
we are doing structured exception handling, and we let
|
|
something else handle it. */
|
|
return ExceptionContinueSearch;
|
|
}
|
|
|
|
debug_printf ("In cygwin_except_handler exception %y at %p sp %p",
|
|
e->ExceptionCode, in->_CX_instPtr, in->_CX_stackPtr);
|
|
debug_printf ("In cygwin_except_handler signal %d at %p",
|
|
si.si_signo, in->_CX_instPtr);
|
|
|
|
PUINT_PTR framep = (PUINT_PTR) in->_CX_framePtr;
|
|
/* Sometimes, when a stack is screwed up, the frame pointer tends to be NULL.
|
|
In that case, base the stacktrace on the stack pointer. In most cases,
|
|
it allows to generate useful stack trace. */
|
|
if (!framep)
|
|
framep = (PUINT_PTR) in->_CX_stackPtr;
|
|
|
|
if (exit_state >= ES_SIGNAL_EXIT
|
|
&& (NTSTATUS) e->ExceptionCode != STATUS_CONTROL_C_EXIT)
|
|
api_fatal ("Exception during process exit");
|
|
else if (try_to_debug ())
|
|
{
|
|
debugging = 1;
|
|
return ExceptionContinueExecution;
|
|
}
|
|
|
|
cygwin_exception exc (framep, in, e);
|
|
si.si_cyg = (void *) &exc;
|
|
/* POSIX requires that for SIGSEGV and SIGBUS, si_addr should be set to the
|
|
address of faulting memory reference. For SIGILL and SIGFPE these should
|
|
be the address of the faulting instruction. Other signals are apparently
|
|
undefined so we just set those to the faulting instruction too. */
|
|
si.si_addr = (si.si_signo == SIGSEGV || si.si_signo == SIGBUS)
|
|
? (void *) e->ExceptionInformation[1] : (void *) in->_CX_instPtr;
|
|
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;
|
|
}
|
|
|
|
/* Utilities to call a user supplied exception handler. */
|
|
|
|
#define SIG_NONMASKABLE (SIGTOMASK (SIGKILL) | SIGTOMASK (SIGSTOP))
|
|
|
|
/* Non-raceable sigsuspend
|
|
Note: This implementation is based on the Single UNIX Specification
|
|
man page. This indicates that sigsuspend always returns -1 and that
|
|
attempts to block unblockable signals will be silently ignored.
|
|
This is counter to what appears to be documented in some UNIX
|
|
man pages, e.g. Linux. */
|
|
int
|
|
handle_sigsuspend (sigset_t tempmask)
|
|
{
|
|
sigset_t oldmask = _my_tls.sigmask; // Remember for restoration
|
|
|
|
set_signal_mask (_my_tls.sigmask, tempmask);
|
|
sigproc_printf ("oldmask %ly, newmask %ly", oldmask, tempmask);
|
|
|
|
pthread_testcancel ();
|
|
cygwait (NULL, cw_infinite, cw_cancel | cw_cancel_self | cw_sig_eintr);
|
|
|
|
set_sig_errno (EINTR); // Per POSIX
|
|
|
|
/* A signal dispatch function will have been added to our stack and will
|
|
be hit eventually. Set the old mask to be restored when the signal
|
|
handler returns and indicate its presence by modifying deltamask. */
|
|
|
|
_my_tls.deltamask |= SIG_NONMASKABLE;
|
|
_my_tls.oldmask = oldmask; // Will be restored by signal handler
|
|
return -1;
|
|
}
|
|
|
|
extern DWORD exec_exit; // Possible exit value for exec
|
|
|
|
extern "C" {
|
|
static void
|
|
sig_handle_tty_stop (int sig, siginfo_t *, void *)
|
|
{
|
|
/* Silently ignore attempts to suspend if there is no accommodating
|
|
cygwin parent to deal with this behavior. */
|
|
if (!myself->cygstarted)
|
|
myself->process_state &= ~PID_STOPPED;
|
|
else
|
|
{
|
|
_my_tls.incyg = 1;
|
|
myself->stopsig = sig;
|
|
myself->alert_parent (sig);
|
|
sigproc_printf ("process %d stopped by signal %d", myself->pid, sig);
|
|
/* FIXME! This does nothing to suspend anything other than the main
|
|
thread. */
|
|
/* Use special cygwait parameter to handle SIGCONT. _main_tls.sig will
|
|
be cleared under lock when SIGCONT is detected. */
|
|
pthread::suspend_all_except_self ();
|
|
DWORD res = cygwait (NULL, cw_infinite, cw_sig_cont);
|
|
pthread::resume_all ();
|
|
switch (res)
|
|
{
|
|
case WAIT_SIGNALED:
|
|
myself->stopsig = SIGCONT;
|
|
myself->alert_parent (SIGCONT);
|
|
break;
|
|
default:
|
|
api_fatal ("WaitSingleObject returned %d", res);
|
|
break;
|
|
}
|
|
_my_tls.incyg = 0;
|
|
}
|
|
}
|
|
} /* end extern "C" */
|
|
|
|
bool
|
|
_cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler,
|
|
struct sigaction& siga)
|
|
{
|
|
bool interrupted;
|
|
|
|
/* Delay the interrupt if we are
|
|
1) somehow inside the DLL
|
|
2) in _sigfe (spinning is true) and about to enter cygwin DLL
|
|
3) in a Windows DLL. */
|
|
if (incyg || spinning || inside_kernel (cx))
|
|
interrupted = false;
|
|
else
|
|
{
|
|
DWORD64 &ip = cx->_CX_instPtr;
|
|
push (ip);
|
|
interrupt_setup (si, handler, siga);
|
|
ip = pop ();
|
|
SetThreadContext (*this, cx); /* Restart the thread in a new location */
|
|
interrupted = true;
|
|
}
|
|
return interrupted;
|
|
}
|
|
|
|
void
|
|
_cygtls::interrupt_setup (siginfo_t& si, void *handler, struct sigaction& siga)
|
|
{
|
|
push ((__tlsstack_t) sigdelayed);
|
|
deltamask = siga.sa_mask & ~SIG_NONMASKABLE;
|
|
sa_flags = siga.sa_flags;
|
|
func = (void (*) (int, siginfo_t *, void *)) handler;
|
|
if (siga.sa_flags & SA_RESETHAND)
|
|
siga.sa_handler = SIG_DFL;
|
|
saved_errno = -1; // Flag: no errno to save
|
|
if (handler == sig_handle_tty_stop)
|
|
{
|
|
myself->stopsig = 0;
|
|
myself->process_state |= PID_STOPPED;
|
|
}
|
|
|
|
infodata = si;
|
|
this->sig = si.si_signo; /* Should always be last thing set to avoid race */
|
|
|
|
if (incyg)
|
|
set_signal_arrived ();
|
|
|
|
if (!have_execed && !(myself->exec_sendsig && !ch_spawn.iscygwin ()))
|
|
proc_subproc (PROC_CLEARWAIT, 1);
|
|
sigproc_printf ("armed signal_arrived %p, signal %d",
|
|
signal_arrived, si.si_signo);
|
|
}
|
|
|
|
extern "C" void
|
|
set_sig_errno (int e)
|
|
{
|
|
*_my_tls.errno_addr = e;
|
|
_my_tls.saved_errno = e;
|
|
}
|
|
|
|
int
|
|
sigpacket::setup_handler (void *handler, struct sigaction& siga, _cygtls *tls)
|
|
{
|
|
CONTEXT cx;
|
|
bool interrupted = false;
|
|
|
|
if (tls->sig)
|
|
{
|
|
sigproc_printf ("trying to send signal %d but signal %d already armed",
|
|
si.si_signo, tls->sig);
|
|
goto out;
|
|
}
|
|
|
|
for (int n = 0; n < CALL_HANDLER_RETRY_OUTER; n++)
|
|
{
|
|
for (int i = 0; i < CALL_HANDLER_RETRY_INNER; i++)
|
|
{
|
|
tls->lock ();
|
|
if (tls->incyg)
|
|
{
|
|
sigproc_printf ("controlled interrupt. stackptr %p, stack %p, "
|
|
"stackptr[-1] %p",
|
|
tls->stackptr, tls->stack, tls->stackptr[-1]);
|
|
tls->interrupt_setup (si, handler, siga);
|
|
interrupted = true;
|
|
tls->unlock ();
|
|
goto out;
|
|
}
|
|
|
|
DWORD res;
|
|
HANDLE hth = (HANDLE) *tls;
|
|
if (!hth)
|
|
{
|
|
tls->unlock ();
|
|
sigproc_printf ("thread handle NULL, not set up yet?");
|
|
}
|
|
else
|
|
{
|
|
/* Suspend the thread which will receive the signal.
|
|
If one of these conditions is not true we loop.
|
|
If the thread is already suspended (which can occur when a
|
|
program has called SuspendThread on itself) then just queue
|
|
the signal. */
|
|
sigproc_printf ("suspending thread, tls %p, _main_tls %p",
|
|
tls, _main_tls);
|
|
res = SuspendThread (hth);
|
|
/* Just set pending if thread is already suspended */
|
|
if (res)
|
|
{
|
|
tls->unlock ();
|
|
ResumeThread (hth);
|
|
goto out;
|
|
}
|
|
cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
|
|
if (!GetThreadContext (hth, &cx))
|
|
sigproc_printf ("couldn't get context of thread, %E");
|
|
else
|
|
interrupted = tls->interrupt_now (&cx, si, handler, siga);
|
|
|
|
tls->unlock ();
|
|
ResumeThread (hth);
|
|
if (interrupted)
|
|
goto out;
|
|
}
|
|
|
|
sigproc_printf ("couldn't interrupt. trying again.");
|
|
yield ();
|
|
}
|
|
/* Hit here if we couldn't deliver the signal. Take a more drastic
|
|
action before trying again. */
|
|
Sleep (1);
|
|
}
|
|
|
|
out:
|
|
sigproc_printf ("signal %d %sdelivered", si.si_signo,
|
|
interrupted ? "" : "not ");
|
|
return interrupted;
|
|
}
|
|
|
|
static inline bool
|
|
has_visible_window_station ()
|
|
{
|
|
HWINSTA station_hdl;
|
|
USEROBJECTFLAGS uof;
|
|
DWORD len;
|
|
|
|
/* Check if the process is associated with a visible window station.
|
|
These are processes running on the local desktop as well as processes
|
|
running in terminal server sessions.
|
|
Processes running in a service session not explicitely associated
|
|
with the desktop (using the "Allow service to interact with desktop"
|
|
property) are running in an invisible window station. */
|
|
if ((station_hdl = GetProcessWindowStation ())
|
|
&& GetUserObjectInformationW (station_hdl, UOI_FLAGS, &uof,
|
|
sizeof uof, &len)
|
|
&& (uof.dwFlags & WSF_VISIBLE))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Keyboard interrupt handler. */
|
|
static BOOL
|
|
ctrl_c_handler (DWORD type)
|
|
{
|
|
static bool saw_close;
|
|
|
|
/* Remove early or we could overthrow the threadlist in cygheap.
|
|
Deleting this line causes ash to SEGV if CTRL-C is hit repeatedly.
|
|
I am not exactly sure why that is. Maybe it's just because this
|
|
adds some early serialization to ctrl_c_handler which prevents
|
|
multiple simultaneous calls? */
|
|
_my_tls.remove (INFINITE);
|
|
|
|
#if 0
|
|
if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT)
|
|
proc_subproc (PROC_KILLFORKED, 0);
|
|
#endif
|
|
|
|
/* Return FALSE to prevent an "End task" dialog box from appearing
|
|
for each Cygwin process window that's open when the computer
|
|
is shut down or console window is closed. */
|
|
|
|
if (type == CTRL_SHUTDOWN_EVENT)
|
|
{
|
|
#if 0
|
|
/* Don't send a signal. Only NT service applications and their child
|
|
processes will receive this event and the services typically already
|
|
handle the shutdown action when getting the SERVICE_CONTROL_SHUTDOWN
|
|
control message. */
|
|
sig_send (NULL, SIGTERM);
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
|
|
if (myself->ctty != -1)
|
|
{
|
|
if (type == CTRL_CLOSE_EVENT)
|
|
{
|
|
sig_send (NULL, SIGHUP);
|
|
saw_close = true;
|
|
return FALSE;
|
|
}
|
|
if (!saw_close && type == CTRL_LOGOFF_EVENT)
|
|
{
|
|
/* The CTRL_LOGOFF_EVENT is sent when *any* user logs off.
|
|
The below code sends a SIGHUP only if it is not performing the
|
|
default activity for SIGHUP. Note that it is possible for two
|
|
SIGHUP signals to arrive if a process group leader is exiting
|
|
too. Getting this 100% right is saved for a future cygwin mailing
|
|
list goad. */
|
|
if (global_sigs[SIGHUP].sa_handler != SIG_DFL)
|
|
{
|
|
sig_send (myself, SIGHUP);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (ch_spawn.set_saw_ctrl_c ())
|
|
{
|
|
if (myself->process_state & PID_NOTCYGWIN)
|
|
sigExeced = SIGINT;
|
|
return TRUE;
|
|
}
|
|
|
|
/* We're only the process group leader when we have a valid pinfo structure.
|
|
If we don't have one, then the parent "stub" will handle the signal. */
|
|
if (!pinfo (cygwin_pid (GetCurrentProcessId ())))
|
|
return TRUE;
|
|
|
|
if (type == CTRL_C_EVENT && ::cygheap->ctty
|
|
&& !cygheap->ctty->need_console_handler ())
|
|
/* Ctrl-C is handled in fhandler_console::cons_master_thread(). */
|
|
return TRUE;
|
|
|
|
tty_min *t = cygwin_shared->tty.get_cttyp ();
|
|
|
|
if (!t)
|
|
return TRUE;
|
|
|
|
/* If process group leader is non-cygwin process or not exist,
|
|
send signal to myself. */
|
|
pinfo pi (t->getpgid ());
|
|
if ((!pi || (pi->process_state & PID_NOTCYGWIN))
|
|
&& (!have_execed || have_execed_cygwin)
|
|
&& t->getpgid () == myself->pgid
|
|
&& type == CTRL_C_EVENT)
|
|
{
|
|
t->output_stopped = false;
|
|
sig_send(myself, SIGINT);
|
|
}
|
|
|
|
/* Ignore this if we're not the process group leader since it should be
|
|
handled *by* the process group leader. */
|
|
if (t && (!have_execed || have_execed_cygwin)
|
|
&& t->getpgid () == myself->pid &&
|
|
(GetTickCount64 () - t->last_ctrl_c) >= MIN_CTRL_C_SLOP)
|
|
/* Otherwise we just send a SIGINT to the process group and return TRUE
|
|
(to indicate that we have handled the signal). At this point, type
|
|
should be a CTRL_C_EVENT or CTRL_BREAK_EVENT. */
|
|
{
|
|
int sig = SIGINT;
|
|
/* If intr and quit are both mapped to ^C, send SIGQUIT on ^BREAK */
|
|
if (type == CTRL_BREAK_EVENT
|
|
&& t->ti.c_cc[VINTR] == 3 && t->ti.c_cc[VQUIT] == 3)
|
|
sig = SIGQUIT;
|
|
t->last_ctrl_c = GetTickCount64 ();
|
|
t->kill_pgrp (sig);
|
|
t->output_stopped = false;
|
|
t->last_ctrl_c = GetTickCount64 ();
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Function used by low level sig wrappers. */
|
|
extern "C" void
|
|
set_process_mask (sigset_t newmask)
|
|
{
|
|
set_signal_mask (_my_tls.sigmask, newmask);
|
|
}
|
|
|
|
extern "C" int
|
|
sighold (int sig)
|
|
{
|
|
/* check that sig is in right range */
|
|
if (sig < 0 || sig >= _NSIG)
|
|
{
|
|
set_errno (EINVAL);
|
|
syscall_printf ("signal %d out of range", sig);
|
|
return -1;
|
|
}
|
|
sigset_t mask = _my_tls.sigmask;
|
|
sigaddset (&mask, sig);
|
|
set_signal_mask (_my_tls.sigmask, mask);
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
sigrelse (int sig)
|
|
{
|
|
/* check that sig is in right range */
|
|
if (sig < 0 || sig >= _NSIG)
|
|
{
|
|
set_errno (EINVAL);
|
|
syscall_printf ("signal %d out of range", sig);
|
|
return -1;
|
|
}
|
|
sigset_t mask = _my_tls.sigmask;
|
|
sigdelset (&mask, sig);
|
|
set_signal_mask (_my_tls.sigmask, mask);
|
|
return 0;
|
|
}
|
|
|
|
extern "C" _sig_func_ptr
|
|
sigset (int sig, _sig_func_ptr func)
|
|
{
|
|
sig_dispatch_pending ();
|
|
_sig_func_ptr prev;
|
|
|
|
/* check that sig is in right range */
|
|
if (sig < 0 || sig >= _NSIG || sig == SIGKILL || sig == SIGSTOP)
|
|
{
|
|
set_errno (EINVAL);
|
|
syscall_printf ("SIG_ERR = sigset (%d, %p)", sig, func);
|
|
return (_sig_func_ptr) SIG_ERR;
|
|
}
|
|
|
|
sigset_t mask = _my_tls.sigmask;
|
|
/* If sig was in the signal mask return SIG_HOLD, otherwise return the
|
|
previous disposition. */
|
|
if (sigismember (&mask, sig))
|
|
prev = SIG_HOLD;
|
|
else
|
|
prev = global_sigs[sig].sa_handler;
|
|
/* If func is SIG_HOLD, add sig to the signal mask, otherwise set the
|
|
disposition to func and remove sig from the signal mask. */
|
|
if (func == SIG_HOLD)
|
|
sigaddset (&mask, sig);
|
|
else
|
|
{
|
|
/* No error checking. The test which could return SIG_ERR has already
|
|
been made above. */
|
|
signal (sig, func);
|
|
sigdelset (&mask, sig);
|
|
}
|
|
set_signal_mask (_my_tls.sigmask, mask);
|
|
return prev;
|
|
}
|
|
|
|
extern "C" int
|
|
sigignore (int sig)
|
|
{
|
|
return sigset (sig, SIG_IGN) == SIG_ERR ? -1 : 0;
|
|
}
|
|
|
|
/* Update the signal mask for this process and return the old mask.
|
|
Called from call_signal_handler */
|
|
extern "C" sigset_t
|
|
set_process_mask_delta ()
|
|
{
|
|
sigset_t newmask, oldmask;
|
|
|
|
if (_my_tls.deltamask & SIG_NONMASKABLE)
|
|
oldmask = _my_tls.oldmask; /* from handle_sigsuspend */
|
|
else
|
|
oldmask = _my_tls.sigmask;
|
|
newmask = (oldmask | _my_tls.deltamask) & ~SIG_NONMASKABLE;
|
|
sigproc_printf ("oldmask %lx, newmask %lx, deltamask %lx", oldmask, newmask,
|
|
_my_tls.deltamask);
|
|
_my_tls.sigmask = newmask;
|
|
return oldmask;
|
|
}
|
|
|
|
/* Set the signal mask for this process.
|
|
Note that some signals are unmaskable, as in UNIX. */
|
|
|
|
void
|
|
set_signal_mask (sigset_t& setmask, sigset_t newmask)
|
|
{
|
|
newmask &= ~SIG_NONMASKABLE;
|
|
sigset_t mask_bits = setmask & ~newmask;
|
|
sigproc_printf ("setmask %lx, newmask %lx, mask_bits %lx", setmask, newmask,
|
|
mask_bits);
|
|
setmask = newmask;
|
|
if (mask_bits)
|
|
sig_dispatch_pending (true);
|
|
}
|
|
|
|
|
|
DWORD
|
|
dumpstack_overflow_wrapper (PVOID arg)
|
|
{
|
|
cygwin_exception *exc = (cygwin_exception *) arg;
|
|
SetThreadName (GetCurrentThreadId (), "__dumpstack_overflow");
|
|
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, void *)
|
|
{
|
|
debug_printf ("exiting due to signal %d", sig);
|
|
exit_state = ES_SIGNAL_EXIT;
|
|
|
|
if (cygheap->rlim_core > 0UL)
|
|
switch (sig)
|
|
{
|
|
case SIGABRT:
|
|
case SIGBUS:
|
|
case SIGFPE:
|
|
case SIGILL:
|
|
case SIGQUIT:
|
|
case SIGSEGV:
|
|
case SIGSYS:
|
|
case SIGTRAP:
|
|
case SIGXCPU:
|
|
case SIGXFSZ:
|
|
sig |= 0x80; /* Flag that we've "dumped core" */
|
|
if (try_to_debug ())
|
|
break;
|
|
if (si->si_code != SI_USER && si->si_cyg)
|
|
{
|
|
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;
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
RtlCaptureContext (&c);
|
|
cygwin_exception exc ((PUINT_PTR) __builtin_frame_address (0), &c);
|
|
exc.dumpstack ();
|
|
}
|
|
break;
|
|
}
|
|
|
|
lock_process until_exit (true);
|
|
|
|
if (have_execed || exit_state > ES_PROCESS_LOCKED)
|
|
{
|
|
debug_printf ("recursive exit?");
|
|
myself.exit (sig);
|
|
}
|
|
|
|
/* Starve other threads in a vain attempt to stop them from doing something
|
|
stupid. */
|
|
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
|
|
|
|
sigproc_printf ("about to call do_exit (%x)", sig);
|
|
do_exit (sig);
|
|
}
|
|
} /* extern "C" */
|
|
|
|
/* Attempt to carefully handle SIGCONT when we are stopped. */
|
|
void
|
|
_cygtls::handle_SIGCONT ()
|
|
{
|
|
if (NOTSTATE (myself, PID_STOPPED))
|
|
return;
|
|
|
|
myself->stopsig = 0;
|
|
myself->process_state &= ~PID_STOPPED;
|
|
/* Carefully tell sig_handle_tty_stop to wake up.
|
|
Make sure that any pending signal is handled before trying to
|
|
send a new one. Then make sure that SIGCONT has been recognized
|
|
before exiting the loop. */
|
|
bool sigsent = false;
|
|
while (1)
|
|
if (sig) /* Assume that it's ok to just test sig outside of a
|
|
lock since setup_handler does it this way. */
|
|
yield (); /* Attempt to schedule another thread. */
|
|
else if (sigsent)
|
|
break; /* SIGCONT has been recognized by other thread */
|
|
else
|
|
{
|
|
sig = SIGCONT;
|
|
set_signal_arrived (); /* alert sig_handle_tty_stop */
|
|
sigsent = true;
|
|
}
|
|
/* Clear pending stop signals */
|
|
sig_clear (SIGSTOP);
|
|
sig_clear (SIGTSTP);
|
|
sig_clear (SIGTTIN);
|
|
sig_clear (SIGTTOU);
|
|
}
|
|
|
|
int
|
|
sigpacket::process ()
|
|
{
|
|
int rc = 1;
|
|
bool issig_wait = false;
|
|
struct sigaction& thissig = global_sigs[si.si_signo];
|
|
void *handler = have_execed ? NULL : (void *) thissig.sa_handler;
|
|
|
|
threadlist_t *tl_entry = NULL;
|
|
_cygtls *tls = NULL;
|
|
|
|
/* Don't try to send signals if we're just starting up since signal masks
|
|
may not be available. */
|
|
if (!cygwin_finished_initializing)
|
|
{
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
|
|
sigproc_printf ("signal %d processing", si.si_signo);
|
|
|
|
myself->rusage_self.ru_nsignals++;
|
|
|
|
if (si.si_signo == SIGCONT)
|
|
{
|
|
tl_entry = cygheap->find_tls (_main_tls);
|
|
_main_tls->handle_SIGCONT ();
|
|
cygheap->unlock_tls (tl_entry);
|
|
}
|
|
|
|
/* SIGKILL is special. It always goes through. */
|
|
if (si.si_signo == SIGKILL)
|
|
{
|
|
tl_entry = cygheap->find_tls (_main_tls);
|
|
tls = _main_tls;
|
|
}
|
|
else if (ISSTATE (myself, PID_STOPPED))
|
|
{
|
|
rc = -1; /* Don't send signals when stopped */
|
|
goto done;
|
|
}
|
|
else if (!sigtls)
|
|
{
|
|
tl_entry = cygheap->find_tls (si.si_signo, issig_wait);
|
|
if (tl_entry)
|
|
{
|
|
tls = tl_entry->thread;
|
|
sigproc_printf ("using tls %p", tls);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tl_entry = cygheap->find_tls (sigtls);
|
|
if (tl_entry)
|
|
{
|
|
tls = tl_entry->thread;
|
|
if (sigismember (&tls->sigwait_mask, si.si_signo))
|
|
issig_wait = true;
|
|
else if (!sigismember (&tls->sigmask, si.si_signo))
|
|
issig_wait = false;
|
|
else
|
|
tls = NULL;
|
|
}
|
|
}
|
|
|
|
/* !tls means no threads available to catch a signal. */
|
|
if (!tls)
|
|
{
|
|
sigproc_printf ("signal %d blocked", si.si_signo);
|
|
rc = -1;
|
|
goto done;
|
|
}
|
|
|
|
/* Do stuff for gdb */
|
|
if ((HANDLE) *tls)
|
|
tls->signal_debugger (si);
|
|
|
|
if (issig_wait)
|
|
{
|
|
tls->sigwait_mask = 0;
|
|
goto dosig;
|
|
}
|
|
|
|
if (handler == SIG_IGN)
|
|
{
|
|
if (si.si_code == SI_TIMER)
|
|
((timer_tracker *) si.si_tid)->disarm_overrun_event ();
|
|
sigproc_printf ("signal %d ignored", si.si_signo);
|
|
goto done;
|
|
}
|
|
|
|
if (si.si_signo == SIGKILL)
|
|
goto exit_sig;
|
|
if (si.si_signo == SIGSTOP)
|
|
{
|
|
sig_clear (SIGCONT);
|
|
goto stop;
|
|
}
|
|
|
|
/* Clear pending SIGCONT on stop signals */
|
|
if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN
|
|
|| si.si_signo == SIGTTOU)
|
|
sig_clear (SIGCONT);
|
|
|
|
if (handler == (void *) SIG_DFL)
|
|
{
|
|
if (si.si_signo == SIGCHLD || si.si_signo == SIGIO
|
|
|| si.si_signo == SIGCONT || si.si_signo == SIGWINCH
|
|
|| si.si_signo == SIGURG)
|
|
{
|
|
if (si.si_code == SI_TIMER)
|
|
((timer_tracker *) si.si_tid)->disarm_overrun_event ();
|
|
sigproc_printf ("signal %d default is currently ignore", si.si_signo);
|
|
goto done;
|
|
}
|
|
|
|
if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN
|
|
|| si.si_signo == SIGTTOU)
|
|
goto stop;
|
|
|
|
goto exit_sig;
|
|
}
|
|
|
|
if (handler == (void *) SIG_ERR)
|
|
goto exit_sig;
|
|
|
|
goto dosig;
|
|
|
|
stop:
|
|
if (tls != _main_tls)
|
|
{
|
|
cygheap->unlock_tls (tl_entry);
|
|
tl_entry = cygheap->find_tls (_main_tls);
|
|
tls = _main_tls;
|
|
}
|
|
handler = (void *) sig_handle_tty_stop;
|
|
thissig = global_sigs[SIGSTOP];
|
|
goto dosig;
|
|
|
|
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)
|
|
{
|
|
sigproc_printf ("terminating captive process");
|
|
if (::cygheap->ctty)
|
|
::cygheap->ctty->cleanup_before_exit ();
|
|
TerminateProcess (ch_spawn, sigExeced = si.si_signo);
|
|
}
|
|
/* Dispatch to the appropriate function. */
|
|
sigproc_printf ("signal %d, signal handler %p", si.si_signo, handler);
|
|
rc = setup_handler (handler, thissig, tls);
|
|
|
|
done:
|
|
cygheap->unlock_tls (tl_entry);
|
|
sigproc_printf ("returning %d", rc);
|
|
return rc;
|
|
|
|
}
|
|
|
|
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 returns 0,
|
|
use the default guard page size. */
|
|
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 ()
|
|
{
|
|
int this_sa_flags = SA_RESTART;
|
|
while (1)
|
|
{
|
|
lock ();
|
|
if (!sig)
|
|
{
|
|
unlock ();
|
|
break;
|
|
}
|
|
|
|
/* Pop the stack if the next "return address" is sigdelayed, since
|
|
this function is doing what sigdelayed would have done anyway. */
|
|
if (retaddr () == (__tlsstack_t) sigdelayed)
|
|
pop ();
|
|
|
|
debug_only_printf ("dealing with signal %d", sig);
|
|
this_sa_flags = sa_flags;
|
|
|
|
sigset_t this_oldmask = set_process_mask_delta ();
|
|
|
|
if (infodata.si_code == SI_TIMER)
|
|
{
|
|
timer_tracker *tt = (timer_tracker *)
|
|
infodata.si_tid;
|
|
infodata.si_overrun = tt->disarm_overrun_event ();
|
|
}
|
|
|
|
/* Save information locally on stack to pass to handler. */
|
|
int thissig = sig;
|
|
siginfo_t thissi = infodata;
|
|
void (*thisfunc) (int, siginfo_t *, void *) = func;
|
|
|
|
ucontext_t *thiscontext = NULL, context_copy;
|
|
|
|
/* Only make a context for SA_SIGINFO handlers */
|
|
if (this_sa_flags & SA_SIGINFO)
|
|
{
|
|
context.uc_link = 0;
|
|
context.uc_flags = 0;
|
|
if (thissi.si_cyg)
|
|
memcpy (&context.uc_mcontext,
|
|
((cygwin_exception *) thissi.si_cyg)->context (),
|
|
sizeof (CONTEXT));
|
|
else
|
|
{
|
|
/* Software-generated signal. We're fetching the current
|
|
context, unwind to the caller and in case we're called
|
|
from sigdelayed, fix the instruction pointer accordingly. */
|
|
context.uc_mcontext.ctxflags = CONTEXT_FULL;
|
|
RtlCaptureContext ((PCONTEXT) &context.uc_mcontext);
|
|
__unwind_single_frame ((PCONTEXT) &context.uc_mcontext);
|
|
if (stackptr > stack)
|
|
{
|
|
#ifdef __x86_64__
|
|
context.uc_mcontext.rip = retaddr ();
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (this_sa_flags & SA_ONSTACK
|
|
&& !_my_tls.altstack.ss_flags
|
|
&& _my_tls.altstack.ss_sp)
|
|
{
|
|
context.uc_stack = _my_tls.altstack;
|
|
context.uc_stack.ss_flags = SS_ONSTACK;
|
|
}
|
|
else
|
|
{
|
|
context.uc_stack.ss_sp = NtCurrentTeb ()->Tib.StackBase;
|
|
context.uc_stack.ss_flags = 0;
|
|
if (!NtCurrentTeb ()->DeallocationStack)
|
|
context.uc_stack.ss_size
|
|
= (uintptr_t) NtCurrentTeb ()->Tib.StackLimit
|
|
- (uintptr_t) NtCurrentTeb ()->Tib.StackBase;
|
|
else
|
|
context.uc_stack.ss_size
|
|
= (uintptr_t) NtCurrentTeb ()->DeallocationStack
|
|
- (uintptr_t) NtCurrentTeb ()->Tib.StackBase;
|
|
}
|
|
context.uc_sigmask = context.uc_mcontext.oldmask = this_oldmask;
|
|
|
|
context.uc_mcontext.cr2 = (thissi.si_signo == SIGSEGV
|
|
|| thissi.si_signo == SIGBUS)
|
|
? (uintptr_t) thissi.si_addr : 0;
|
|
|
|
thiscontext = &context;
|
|
context_copy = context;
|
|
}
|
|
|
|
int this_errno = saved_errno;
|
|
reset_signal_arrived ();
|
|
incyg = false;
|
|
sig = 0; /* Flag that we can accept another signal */
|
|
unlock (); /* unlock signal stack */
|
|
|
|
/* Alternate signal stack requested for this signal and alternate signal
|
|
stack set up for this thread? */
|
|
if (this_sa_flags & SA_ONSTACK
|
|
&& !_my_tls.altstack.ss_flags
|
|
&& _my_tls.altstack.ss_sp)
|
|
{
|
|
/* Yes, use alternate signal stack.
|
|
|
|
NOTE:
|
|
|
|
We DO NOT change the TEB's stack addresses and we DO NOT move the
|
|
_cygtls area to the alternate stack. This seems to work fine,
|
|
but there may be Windows functions not working correctly under
|
|
these circumstances.
|
|
|
|
On the other hand, if a Windows function crashed and we're handling
|
|
this here, moving the TEB stack addresses may be fatal.
|
|
|
|
If the current code does not work as expected in the "usual"
|
|
POSIX circumstances, this problem must be revisited. */
|
|
|
|
/* 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) & ~0xf;
|
|
/* In assembler: Save regs on new stack, move to alternate stack,
|
|
call thisfunc, revert stack regs. */
|
|
#ifdef __x86_64__
|
|
/* Clobbered regs: rcx, rdx, r8, r9, r10, r11, rbp, rsp */
|
|
__asm__ ("\n\
|
|
movq %[NEW_SP], %%rax # Load alt stack into rax \n\
|
|
subq $0x60, %%rax # Make room on alt stack \n\
|
|
# for clobbered regs and \n\
|
|
# required shadow space \n\
|
|
movq %%rcx, 0x20(%%rax)# Save clobbered regs \n\
|
|
movq %%rdx, 0x28(%%rax) \n\
|
|
movq %%r8, 0x30(%%rax) \n\
|
|
movq %%r9, 0x38(%%rax) \n\
|
|
movq %%r10, 0x40(%%rax) \n\
|
|
movq %%r11, 0x48(%%rax) \n\
|
|
movq %%rbp, 0x50(%%rax) \n\
|
|
movq %%rsp, 0x58(%%rax) \n\
|
|
movl %[SIG], %%ecx # thissig to 1st arg reg \n\
|
|
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 *%%r10 # Call wrapper \n\
|
|
movq %%rsp, %%rax # Restore clobbered regs \n\
|
|
movq 0x58(%%rax), %%rsp \n\
|
|
movq 0x50(%%rax), %%rbp \n\
|
|
movq 0x48(%%rax), %%r11 \n\
|
|
movq 0x40(%%rax), %%r10 \n\
|
|
movq 0x38(%%rax), %%r9 \n\
|
|
movq 0x30(%%rax), %%r8 \n\
|
|
movq 0x28(%%rax), %%rdx \n\
|
|
movq 0x20(%%rax), %%rcx \n"
|
|
: : [NEW_SP] "o" (new_sp),
|
|
[SIG] "o" (thissig),
|
|
[SI] "o" (thissi),
|
|
[CTX] "o" (thiscontext),
|
|
[FUNC] "o" (thisfunc),
|
|
[WRAPPER] "o" (altstack_wrapper)
|
|
: "memory");
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
}
|
|
else
|
|
/* No alternate signal stack requested or available, just call
|
|
signal handler. */
|
|
thisfunc (thissig, &thissi, thiscontext);
|
|
|
|
incyg = true;
|
|
|
|
set_signal_mask (_my_tls.sigmask, (this_sa_flags & SA_SIGINFO)
|
|
? context.uc_sigmask : this_oldmask);
|
|
if (this_errno >= 0)
|
|
set_errno (this_errno);
|
|
if (this_sa_flags & SA_SIGINFO)
|
|
{
|
|
/* If more than just the sigmask in the context has been changed by
|
|
the signal handler, call setcontext. */
|
|
context_copy.uc_sigmask = context.uc_sigmask;
|
|
if (memcmp (&context, &context_copy, sizeof context) != 0)
|
|
setcontext (&context);
|
|
}
|
|
}
|
|
|
|
/* FIXME: Since 2011 this return statement always returned 1 (meaning
|
|
SA_RESTART is effective) if the thread we're running in is not the
|
|
main thread. We're disabling this check to enable EINTR behaviour
|
|
on system calls not running in the main thread. It's not quite clear
|
|
if that has undesired side-effects, therefore this comment. */
|
|
return this_sa_flags & SA_RESTART;
|
|
}
|
|
|
|
void
|
|
_cygtls::signal_debugger (siginfo_t& si)
|
|
{
|
|
HANDLE th;
|
|
/* If si.si_cyg is set then the signal was already sent to the debugger. */
|
|
if (isinitialized () && !si.si_cyg && (th = (HANDLE) *this)
|
|
&& being_debugged () && SuspendThread (th) >= 0)
|
|
{
|
|
CONTEXT c;
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
if (GetThreadContext (th, &c))
|
|
{
|
|
if (incyg)
|
|
c._CX_instPtr = retaddr ();
|
|
memcpy (&context.uc_mcontext, &c, sizeof (CONTEXT));
|
|
/* Enough space for 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, &context.uc_mcontext);
|
|
OutputDebugString (sigmsg);
|
|
}
|
|
ResumeThread (th);
|
|
}
|
|
}
|
|
|
|
extern "C" int
|
|
setcontext (const ucontext_t *ucp)
|
|
{
|
|
PCONTEXT ctx = (PCONTEXT) &ucp->uc_mcontext;
|
|
set_signal_mask (_my_tls.sigmask, ucp->uc_sigmask);
|
|
_my_tls.incyg = true;
|
|
RtlRestoreContext (ctx, NULL);
|
|
/* If we got here, something was wrong. */
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
extern "C" int
|
|
getcontext (ucontext_t *ucp)
|
|
{
|
|
PCONTEXT ctx = (PCONTEXT) &ucp->uc_mcontext;
|
|
ctx->ContextFlags = CONTEXT_FULL;
|
|
RtlCaptureContext (ctx);
|
|
__unwind_single_frame (ctx);
|
|
/* Successful getcontext is supposed to return 0. If we don't set the
|
|
return register to 0 here, there's a chance that code like this:
|
|
|
|
if (getcontext (&ctx) != 0)
|
|
|
|
assumes that getcontext failed after calling setcontext (&ctx). */
|
|
ucp->uc_mcontext._MC_retReg = 0;
|
|
ucp->uc_sigmask = ucp->uc_mcontext.oldmask = _my_tls.sigmask;
|
|
/* Do not touch any other member of ucontext_t. */
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int
|
|
swapcontext (ucontext_t *oucp, const ucontext_t *ucp)
|
|
{
|
|
PCONTEXT ctx = (PCONTEXT) &oucp->uc_mcontext;
|
|
ctx->ContextFlags = CONTEXT_FULL;
|
|
RtlCaptureContext (ctx);
|
|
__unwind_single_frame (ctx);
|
|
/* See comment in getcontext. */
|
|
oucp->uc_mcontext._MC_retReg = 0;
|
|
oucp->uc_sigmask = oucp->uc_mcontext.oldmask = _my_tls.sigmask;
|
|
return setcontext (ucp);
|
|
}
|
|
|
|
/* Trampoline function to set the context to uc_link. The pointer to the
|
|
address of uc_link is stored in a callee-saved register, referenced by
|
|
_MC_uclinkReg from the C code. If uc_link is NULL, call exit. */
|
|
#ifdef __x86_64__
|
|
/* _MC_uclinkReg == %rbx */
|
|
__asm__ (" \n\
|
|
.global __cont_link_context \n\
|
|
.seh_proc __cont_link_context \n\
|
|
__cont_link_context: \n\
|
|
.seh_endprologue \n\
|
|
movq %rbx, %rsp \n\
|
|
movq (%rsp), %rcx \n\
|
|
# align stack and subtract shadow space \n\
|
|
andq $~0xf, %rsp \n\
|
|
subq $0x20, %rsp \n\
|
|
testq %rcx, %rcx \n\
|
|
je 1f \n\
|
|
call setcontext \n\
|
|
movq $0xff, %rcx \n\
|
|
1: \n\
|
|
call cygwin_exit \n\
|
|
nop \n\
|
|
.seh_endproc \n\
|
|
");
|
|
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
|
|
/* makecontext is modelled after GLibc's makecontext. The stack from uc_stack
|
|
is prepared so that it starts with a pointer to the linked context uc_link,
|
|
followed by the arguments to func, and finally at the bottom the "return"
|
|
address set to __cont_link_context. In the ucp context, rbx/ebx is set to
|
|
point to the stack address where the pointer to uc_link is stored. The
|
|
requirement to make this work is that rbx/ebx are callee-saved registers
|
|
per the ABI. If any function is called which doesn't follow the ABI
|
|
conventions, e.g. assembler code, this method will break. But that's ok. */
|
|
extern "C" void
|
|
makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...)
|
|
{
|
|
extern void __cont_link_context (void);
|
|
uintptr_t *sp;
|
|
va_list ap;
|
|
|
|
/* Initialize sp to the top of the stack. */
|
|
sp = (uintptr_t *) ((uintptr_t) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size);
|
|
/* Subtract slots required for arguments and the pointer to uc_link. */
|
|
sp -= (argc + 1);
|
|
/* Align. */
|
|
sp = (uintptr_t *) ((uintptr_t) sp & ~0xf);
|
|
/* Subtract one slot for setting the return address. */
|
|
--sp;
|
|
/* Set return address to the trampolin function __cont_link_context. */
|
|
sp[0] = (uintptr_t) __cont_link_context;
|
|
/* Fetch arguments and store them on the stack.
|
|
|
|
x86_64:
|
|
|
|
- Store first four args in the AMD64 ABI arg registers.
|
|
|
|
- Note that the stack is not short by these four register args. The
|
|
reason is the shadow space for these regs required by the AMD64 ABI.
|
|
|
|
- The definition of makecontext only allows for "int" sized arguments to
|
|
func, 32 bit, likely for historical reasons. However, the argument
|
|
slots on x86_64 are 64 bit anyway, so we can fetch and store the args
|
|
as 64 bit values, and func can request 64 bit args without violating
|
|
the definition. This potentially allows porting 32 bit applications
|
|
providing pointer values to func without additional porting effort. */
|
|
va_start (ap, argc);
|
|
for (int i = 0; i < argc; ++i)
|
|
#ifdef __x86_64__
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
ucp->uc_mcontext.rcx = va_arg (ap, uintptr_t);
|
|
break;
|
|
case 1:
|
|
ucp->uc_mcontext.rdx = va_arg (ap, uintptr_t);
|
|
break;
|
|
case 2:
|
|
ucp->uc_mcontext.r8 = va_arg (ap, uintptr_t);
|
|
break;
|
|
case 3:
|
|
ucp->uc_mcontext.r9 = va_arg (ap, uintptr_t);
|
|
break;
|
|
default:
|
|
sp[i + 1] = va_arg (ap, uintptr_t);
|
|
break;
|
|
}
|
|
#else
|
|
#error unimplemented for this target
|
|
#endif
|
|
va_end (ap);
|
|
/* Store pointer to uc_link at the top of the stack. */
|
|
sp[argc + 1] = (uintptr_t) ucp->uc_link;
|
|
/* Last but not least set the register in the context at ucp so that a
|
|
subsequent setcontext or swapcontext picks up the right values:
|
|
- Set instruction pointer to the target function.
|
|
- Set stack pointer to the just computed stack pointer value.
|
|
- Set Cygwin-specific uclink register to the address of the pointer
|
|
to uc_link. */
|
|
ucp->uc_mcontext._MC_instPtr = (uint64_t) func;
|
|
ucp->uc_mcontext._MC_stackPtr = (uint64_t) sp;
|
|
ucp->uc_mcontext._MC_uclinkReg = (uint64_t) (sp + argc + 1);
|
|
}
|