From: Christopher Faylor Date: Fri, 1 Aug 2008 19:12:48 +0000 (+0000) Subject: * dcrt0.cc (dll_crt0_1): Add a symbol denoting return from call to main(). X-Git-Tag: binutils-2_19~391 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=07b3c34a288837f573c1a402288306f073fb56cb;p=pf3gnuchains%2Fpf3gnuchains4x.git * dcrt0.cc (dll_crt0_1): Add a symbol denoting return from call to main(). * exceptions.cc (stack_info::walk): Stop walking the stack when we hit _cygwin_exit_return rather than just blindly stopping when PC becomes zero. --- diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index d1c1ae7c3d..e907245297 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,11 @@ +2008-08-01 Christopher Faylor + + * dcrt0.cc (dll_crt0_1): Add a symbol denoting return from call to + main(). + * exceptions.cc (stack_info::walk): Stop walking the stack when we + hit _cygwin_exit_return rather than just blindly stopping when PC + becomes zero. + 2008-07-31 Corinna Vinschen * path.cc (symlink_info::check): Handle (and explain) a special case diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc index 6b2d283fd9..e37cbec9b9 100644 --- a/winsup/cygwin/dcrt0.cc +++ b/winsup/cygwin/dcrt0.cc @@ -933,6 +933,10 @@ dll_crt0_1 (void *) ld_preload (); if (user_data->main) cygwin_exit (user_data->main (__argc, __argv, *user_data->envptr)); + __asm__ (" \n\ + .global __cygwin_exit_return \n\ +__cygwin_exit_return: \n\ +"); } extern "C" void __stdcall diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc new file mode 100644 index 0000000000..e744e7c5d0 --- /dev/null +++ b/winsup/cygwin/exceptions.cc @@ -0,0 +1,1432 @@ +/* exceptions.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, + 2005, 2006, 2007, 2008 Red Hat, Inc. + +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. */ + +#include "winsup.h" +#include "miscfuncs.h" +#include +#include +#include +#include +#include +#include + +#include "pinfo.h" +#include "cygtls.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" + +#define CALL_HANDLER_RETRY 20 + +char debugger_command[2 * NT_MAX_PATH + 20]; + +extern "C" { +extern void sigdelayed (); +}; + +extern child_info_spawn *chExeced; +int NO_COPY sigExeced; + +static BOOL WINAPI ctrl_c_handler (DWORD); +static WCHAR windows_system_directory[1024]; +static size_t windows_system_directory_length; + +/* This is set to indicate that we have already exited. */ + +static NO_COPY int exit_already = 0; +static muto NO_COPY mask_sync; + +NO_COPY static struct +{ + unsigned int 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) + { + debugger_command[0] = '\0'; + return; + } + + char pgm[NT_MAX_PATH]; + if (!GetModuleFileName (NULL, pgm, NT_MAX_PATH)) + strcpy (pgm, "cygwin1.dll"); + for (char *p = strchr (pgm, '\\'); p; p = strchr (p, '\\')) + *p = '/'; + + __small_sprintf (debugger_command, "%s \"%s\"", buf, pgm); +} + +static void +open_stackdumpfile () +{ + if (myself->progname[0]) + { + const char *p; + /* write to progname.stackdump if possible */ + if (!myself->progname[0]) + p = "unknown"; + else if ((p = strrchr (myself->progname, '\\'))) + p++; + else + p = myself->progname; + + WCHAR corefile[strlen (p) + sizeof (".stackdump")]; + UNICODE_STRING ucore; + OBJECT_ATTRIBUTES attr; + /* Create the UNICODE variation of .stackdump. */ + RtlInitEmptyUnicodeString (&ucore, corefile, + sizeof corefile - sizeof (WCHAR)); + ucore.Length = sys_mbstowcs (ucore.Buffer, + ucore.MaximumLength / sizeof (WCHAR), + p, strlen (p)) * sizeof (WCHAR); + RtlAppendUnicodeToString (&ucore, L".stackdump"); + /* Create an object attribute which refers to .stackdump + in Cygwin's cwd. Stick to caseinsensitivity. */ + InitializeObjectAttributes (&attr, &ucore, OBJ_CASE_INSENSITIVE, + cygheap->cwd.get_handle (), NULL); + HANDLE h; + 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 + | FILE_OPEN_FOR_RECOVERY, 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. */ + +static void +exception (EXCEPTION_RECORD *e, CONTEXT *in) +{ + const char *exception_name = NULL; + + if (e) + { + for (int i = 0; status_info[i].name; i++) + { + if (status_info[i].code == e->ExceptionCode) + { + exception_name = status_info[i].name; + break; + } + } + } + + if (exception_name) + small_printf ("Exception: %s at eip=%08x\r\n", exception_name, in->Eip); + else + small_printf ("Signal %d at eip=%08x\r\n", e->ExceptionCode, in->Eip); + small_printf ("eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\r\n", + in->Eax, in->Ebx, in->Ecx, in->Edx, in->Esi, in->Edi); + small_printf ("ebp=%08x esp=%08x program=%s, pid %u, thread %s\r\n", + in->Ebp, in->Esp, myself->progname, myself->pid, cygthread::name ()); + small_printf ("cs=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\r\n", + in->SegCs, in->SegDs, in->SegEs, in->SegFs, in->SegGs, in->SegSs); +} + +/* 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; + DWORD dummy_frame; +public: + STACKFRAME sf; /* For storing the stack information */ + void init (DWORD, bool, bool); /* 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 (DWORD ebp, bool wantargs, bool goodframe) +{ +# define debp ((DWORD *) ebp) + memset (&sf, 0, sizeof (sf)); + if (!goodframe) + sf.AddrFrame.Offset = ebp; + else + { + dummy_frame = ebp; + sf.AddrFrame.Offset = (DWORD) &dummy_frame; + } + sf.AddrReturn.Offset = debp[1]; + sf.AddrFrame.Mode = AddrModeFlat; + needargs = wantargs; +# undef debp +} + +extern "C" void _cygwin_exit_return (); + +/* Walk the stack by looking at successive stored 'bp' frames. + This is not foolproof. */ +int +stack_info::walk () +{ + char **ebp; + + if ((void (*) ()) sf.AddrPC.Offset == _cygwin_exit_return) + return 0; /* stack frames are exhausted */ + + if (((ebp = (char **) next_offset ()) == NULL) || (ebp >= (char **) cygwin_hmodule)) + return 0; + + sf.AddrFrame.Offset = (DWORD) ebp; + sf.AddrPC.Offset = sf.AddrReturn.Offset; + + /* The return address always follows the stack pointer */ + sf.AddrReturn.Offset = (DWORD) *++ebp; + + if (needargs) + { + unsigned nparams = NPARAMS; + + /* The arguments follow the return address */ + sf.Params[0] = (DWORD) *++ebp; + /* Hack for XP/2K3 WOW64. If the first stack param points to the + application entry point, we can only fetch one additional + parameter. Accessing anything beyond this address results in + a SEGV. This is fixed in Vista/2K8 WOW64. */ + if (wincap.has_restricted_stack_args () && sf.Params[0] == 0x401000) + nparams = 2; + for (unsigned i = 1; i < nparams; i++) + sf.Params[i] = (DWORD) *++ebp; + } + + return 1; +} + +static void +stackdump (DWORD ebp, int open_file, bool isexception) +{ + extern unsigned long rlim_core; + static bool already_dumped; + + if (rlim_core == 0UL || (open_file && already_dumped)) + return; + + if (open_file) + open_stackdumpfile (); + + already_dumped = true; + + int i; + + thestack.init (ebp, 1, !isexception); /* Initialize from the input CONTEXT */ + small_printf ("Stack trace:\r\nFrame Function Args\r\n"); + for (i = 0; i < 16 && thestack++; i++) + { + small_printf ("%08x %08x ", thestack.sf.AddrFrame.Offset, + thestack.sf.AddrPC.Offset); + for (unsigned j = 0; j < NPARAMS; j++) + small_printf ("%s%08x", j == 0 ? " (" : ", ", thestack.sf.Params[j]); + small_printf (")\r\n"); + } + small_printf ("End of stack trace%s\n", + i == 16 ? " (more stack frames may be present)" : ""); +} + +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->Eip, &m, sizeof m)) + sigproc_printf ("couldn't get memory info, pc %p, %E", cx->Eip); + + size_t size = (windows_system_directory_length + 6) * sizeof (WCHAR); + PWCHAR checkdir = (PWCHAR) alloca (size); + memset (checkdir, 0, size); + +# define h ((HMODULE) m.AllocationBase) + /* Apparently Windows 95 can sometimes return bogus addresses from + GetThreadContext. These resolve to a strange allocation base. + These should *never* be treated as interruptible. */ + if (!h || m.State != MEM_COMMIT) + res = true; + 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); + } + sigproc_printf ("pc %p, h %p, inside_kernel %d", cx->Eip, 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; + GetThreadContext (GetCurrentThread (), &c); + stackdump (c.Ebp, 0, 0); +} + +#define TIME_TO_WAIT_FOR_DEBUGGER 10000 + +extern "C" int +try_to_debug (bool waitloop) +{ + WCHAR dbg_cmd[sizeof debugger_command]; + + debug_printf ("debugger_command '%s'", debugger_command); + if (*debugger_command == '\0') + return 0; + if (being_debugged ()) + { + extern void break_here (); + break_here (); + return 0; + } + + __small_sprintf (strchr (debugger_command, '\0'), " %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 any of these mutexes is owned, we will fail to start any cygwin app + until trapped app exits */ + + 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 ; + } + } + + console_printf ("*** starting debugger for pid %u, tid %u\n", + cygwin_pid (GetCurrentProcessId ()), GetCurrentThreadId ()); + BOOL dbg; + sys_mbstowcs (dbg_cmd, sizeof debugger_command, debugger_command); + dbg = CreateProcessW (NULL, + dbg_cmd, + NULL, + NULL, + FALSE, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi); + + if (!dbg) + system_printf ("Failed to start debugger, %E"); + else + { + if (!waitloop) + return dbg; + SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_IDLE); + while (!being_debugged ()) + low_priority_sleep (0); + Sleep (2000); + } + + console_printf ("*** continuing pid %u from debugger call (%d)\n", + cygwin_pid (GetCurrentProcessId ()), dbg); + + SetThreadPriority (GetCurrentThread (), prio); + return dbg; +} + +extern "C" DWORD __stdcall RtlUnwind (void *, void *, void *, DWORD); +static void __stdcall rtl_unwind (exception_list *, PEXCEPTION_RECORD) __attribute__ ((noinline, regparm (3))); +void __stdcall +rtl_unwind (exception_list *frame, PEXCEPTION_RECORD e) +{ + __asm__ ("\n\ + pushl %%ebx \n\ + pushl %%edi \n\ + pushl %%esi \n\ + pushl $0 \n\ + pushl %1 \n\ + pushl $1f \n\ + pushl %0 \n\ + call _RtlUnwind@16 \n\ +1: \n\ + popl %%esi \n\ + popl %%edi \n\ + popl %%ebx \n\ +": : "r" (frame), "r" (e)); +} + +/* Main exception handler. */ + +extern exception_list *_except_list asm ("%fs:0"); + +extern "C" char *__progname; +int +_cygtls::handle_exceptions (EXCEPTION_RECORD *e, exception_list *frame, CONTEXT *in, void *) +{ + static bool NO_COPY debugging; + static int NO_COPY recursed; + _cygtls& me = _my_tls; + + if (debugging && ++debugging < 500000) + { + SetThreadPriority (hMainThread, THREAD_PRIORITY_NORMAL); + return 0; + } + + /* If we've already exited, don't do anything here. Returning 1 + tells Windows to keep looking for an exception handler. */ + if (exit_already || e->ExceptionFlags) + return 1; + + siginfo_t si = {0}; + si.si_code = SI_KERNEL; + /* Coerce win32 value to posix value. */ + switch (e->ExceptionCode) + { + case STATUS_FLOAT_DENORMAL_OPERAND: + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_FLOAT_INVALID_OPERATION: + 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 0; + 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_ARRAY_BOUNDS_EXCEEDED: + case STATUS_IN_PAGE_ERROR: + case STATUS_NO_MEMORY: + case STATUS_INVALID_DISPOSITION: + case STATUS_STACK_OVERFLOW: + 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 0; + + 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 1; + } + + debug_printf ("In cygwin_except_handler exc %p at %p sp %p", e->ExceptionCode, in->Eip, in->Esp); + debug_printf ("In cygwin_except_handler sig %d at %p", si.si_signo, in->Eip); + + bool masked = !!(me.sigmask & SIGTOMASK (si.si_signo)); + if (masked) + syscall_printf ("signal %d, masked %p", si.si_signo, + global_sigs[si.si_signo].sa_mask); + + debug_printf ("In cygwin_except_handler calling %p", + global_sigs[si.si_signo].sa_handler); + + DWORD *ebp = (DWORD *) in->Esp; + for (DWORD *bpend = (DWORD *) __builtin_frame_address (0); ebp > bpend; ebp--) + if (*ebp == in->SegCs && ebp[-1] == in->Eip) + { + ebp -= 2; + break; + } + + if (me.fault_guarded ()) + me.return_from_fault (); + + me.copy_context (in); + + /* Temporarily replace windows top level SEH with our own handler. + We don't want any Windows magic kicking in. This top level frame + will be removed automatically after our exception handler returns. */ + _except_list->handler = _cygtls::handle_exceptions; + + if (masked + || &me == _sig_tls + || !cygwin_finished_initializing + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_DFL + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_IGN + || (void *) global_sigs[si.si_signo].sa_handler == (void *) SIG_ERR) + { + /* Print the exception to the console */ + if (!myself->cygstarted) + for (int i = 0; status_info[i].name; i++) + if (status_info[i].code == e->ExceptionCode) + { + system_printf ("Exception: %s", status_info[i].name); + break; + } + + /* Another exception could happen while tracing or while exiting. + Only do this once. */ + if (recursed++) + system_printf ("Error while dumping state (probably corrupted stack)"); + else + { + if (try_to_debug (0)) + { + debugging = true; + return 0; + } + + rtl_unwind (frame, e); + open_stackdumpfile (); + exception (e, in); + stackdump ((DWORD) ebp, 0, 1); + } + + if (e->ExceptionCode == STATUS_ACCESS_VIOLATION) + { + int error_code = 0; + if (si.si_code == SEGV_ACCERR) /* Address present */ + error_code |= 1; + if (e->ExceptionInformation[0]) /* Write access */ + error_code |= 2; + if (!me.inside_kernel (in)) /* User space */ + error_code |= 4; + klog (LOG_INFO, "%s[%d]: segfault at %08x rip %08x rsp %08x error %d", + __progname, myself->pid, + e->ExceptionInformation[1], in->Eip, in->Esp, + ((in->Eip >= 0x61000000 && in->Eip < 0x61200000) + ? 0 : 4) | (e->ExceptionInformation[0] << 1)); + } + + me.signal_exit (0x80 | si.si_signo); // Flag signal + core dump + } + + si.si_addr = (void *) in->Eip; + si.si_errno = si.si_pid = si.si_uid = 0; + me.incyg++; + sig_send (NULL, si, &me); // Signal myself + me.incyg--; + e->ExceptionFlags = 0; + return 0; +} + +/* 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 __stdcall +handle_sigsuspend (sigset_t tempmask) +{ + if (&_my_tls != _main_tls) + { + cancelable_wait (signal_arrived, INFINITE, cw_cancel_self); + return -1; + } + + sigset_t oldmask = _my_tls.sigmask; // Remember for restoration + + set_signal_mask (tempmask, _my_tls.sigmask); + sigproc_printf ("oldmask %p, newmask %p", oldmask, tempmask); + + pthread_testcancel (); + cancelable_wait (signal_arrived, INFINITE); + + 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) +{ + _my_tls.incyg = 1; + /* 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; + return; + } + + myself->stopsig = sig; + myself->alert_parent (sig); + sigproc_printf ("process %d stopped by signal %d", myself->pid, sig); + HANDLE w4[2]; + w4[0] = sigCONT; + w4[1] = signal_arrived; + switch (WaitForMultipleObjects (2, w4, TRUE, INFINITE)) + { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + reset_signal_arrived (); + myself->alert_parent (SIGCONT); + break; + default: + api_fatal ("WaitSingleObject failed, %E"); + break; + } + _my_tls.incyg = 0; +} +} + +bool +_cygtls::interrupt_now (CONTEXT *cx, int sig, void *handler, + struct sigaction& siga) +{ + bool interrupted; + + if (incyg || spinning || locked () || inside_kernel (cx)) + interrupted = false; + else + { + push ((__stack_t) cx->Eip); + interrupt_setup (sig, handler, siga); + cx->Eip = pop (); + SetThreadContext (*this, cx); /* Restart the thread in a new location */ + interrupted = true; + } + return interrupted; +} + +void __stdcall +_cygtls::interrupt_setup (int sig, void *handler, struct sigaction& siga) +{ + push ((__stack_t) sigdelayed); + deltamask = siga.sa_mask & ~SIG_NONMASKABLE; + sa_flags = siga.sa_flags; + func = (void (*) (int)) 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; + } + + this->sig = sig; // Should always be last thing set to avoid a race + + if (!event) + threadkill = false; + else + { + HANDLE h = event; + event = NULL; + SetEvent (h); + } + + /* Clear any waiting threads prior to dispatching to handler function */ + int res = SetEvent (signal_arrived); // For an EINTR case + proc_subproc (PROC_CLEARWAIT, 1); + sigproc_printf ("armed signal_arrived %p, sig %d, res %d", signal_arrived, + sig, res); +} + +extern "C" void __stdcall +set_sig_errno (int e) +{ + *_my_tls.errno_addr = e; + _my_tls.saved_errno = e; + // sigproc_printf ("errno %d", e); +} + +static int setup_handler (int, void *, struct sigaction&, _cygtls *tls) + __attribute__((regparm(3))); +static int +setup_handler (int sig, void *handler, struct sigaction& siga, _cygtls *tls) +{ + CONTEXT cx; + bool interrupted = false; + + if (tls->sig) + { + sigproc_printf ("trying to send sig %d but signal %d already armed", + sig, tls->sig); + goto out; + } + + for (int i = 0; i < CALL_HANDLER_RETRY; 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 (sig, handler, siga); + interrupted = true; + tls->unlock (); + break; + } + + tls->unlock (); + DWORD res; + HANDLE hth = (HANDLE) *tls; + + /* Suspend the thread which will receive the signal. + For Windows 95, we also have to ensure that the addresses returned by + GetThreadContext are valid. + If one of these conditions is not true we loop for a fixed number of times + since we don't want to stall the signal handler. FIXME: Will this result in + noticeable delays? + If the thread is already suspended (which can occur when a program has called + SuspendThread on itself) then just queue the signal. */ + +#ifndef DEBUGGING + sigproc_printf ("suspending mainthread"); +#else + cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext (hth, &cx)) + memset (&cx, 0, sizeof cx); + sigproc_printf ("suspending mainthread PC %p", cx.Eip); +#endif + res = SuspendThread (hth); + /* Just set pending if thread is already suspended */ + if (res) + { + ResumeThread (hth); + break; + } + cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext (hth, &cx)) + system_printf ("couldn't get context of main thread, %E"); + else + interrupted = tls->interrupt_now (&cx, sig, handler, siga); + + res = ResumeThread (hth); + if (interrupted) + break; + + sigproc_printf ("couldn't interrupt. trying again."); + low_priority_sleep (0); + } + +out: + sigproc_printf ("signal %d %sdelivered", sig, 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 WINAPI +ctrl_c_handler (DWORD type) +{ + static bool saw_close; + lock_process now; + + if (!cygwin_finished_initializing) + { + if (myself->cygstarted) /* Was this process created by a cygwin process? */ + return TRUE; /* Yes. Let the parent eventually handle CTRL-C issues. */ + debug_printf ("exiting with status %p", STATUS_CONTROL_C_EXIT); + ExitProcess (STATUS_CONTROL_C_EXIT); + } + + _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_nowait, SIGHUP); + return TRUE; + } + return FALSE; + } + } + + if (chExeced) + { + chExeced->set_saw_ctrl_c (); + 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; + + tty_min *t = cygwin_shared->tty.get_tty (myself->ctty); + /* Ignore this if we're not the process group leader since it should be handled + *by* the process group leader. */ + if (myself->ctty != -1 && t->getpgid () == myself->pid && + (GetTickCount () - 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 = GetTickCount (); + killsys (-myself->pid, sig); + t->last_ctrl_c = GetTickCount (); + return TRUE; + } + + return TRUE; +} + +/* Function used by low level sig wrappers. */ +extern "C" void __stdcall +set_process_mask (sigset_t newmask) +{ + set_signal_mask (newmask, _my_tls.sigmask); +} + +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; + } + mask_sync.acquire (INFINITE); + sigset_t mask = _my_tls.sigmask; + sigaddset (&mask, sig); + set_signal_mask (mask, _my_tls.sigmask); + mask_sync.release (); + 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; + } + mask_sync.acquire (INFINITE); + sigset_t mask = _my_tls.sigmask; + sigdelset (&mask, sig); + set_signal_mask (mask, _my_tls.sigmask); + mask_sync.release (); + 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; + } + + mask_sync.acquire (INFINITE); + 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 (mask, _my_tls.sigmask); + mask_sync.release (); + 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 sigdelayed */ +extern "C" sigset_t +set_process_mask_delta () +{ + mask_sync.acquire (INFINITE); + 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 %p, newmask %p, deltamask %p", oldmask, newmask, + _my_tls.deltamask); + _my_tls.sigmask = newmask; + mask_sync.release (); + return oldmask; +} + +/* Set the signal mask for this process. + Note that some signals are unmaskable, as in UNIX. */ +extern "C" void __stdcall +set_signal_mask (sigset_t newmask, sigset_t& oldmask) +{ +#ifdef CGF + if (&_my_tls == _sig_tls) + small_printf ("********* waiting in signal thread\n"); +#endif + mask_sync.acquire (INFINITE); + newmask &= ~SIG_NONMASKABLE; + sigset_t mask_bits = oldmask & ~newmask; + sigproc_printf ("oldmask %p, newmask %p, mask_bits %p", oldmask, newmask, + mask_bits); + oldmask = newmask; + if (mask_bits) + sig_dispatch_pending (true); + else + sigproc_printf ("not calling sig_dispatch_pending"); + mask_sync.release (); +} + +int __stdcall +sigpacket::process () +{ + DWORD continue_now; + struct sigaction dummy = global_sigs[SIGSTOP]; + + if (si.si_signo != SIGCONT) + continue_now = false; + else + { + continue_now = myself->process_state & PID_STOPPED; + myself->stopsig = 0; + myself->process_state &= ~PID_STOPPED; + /* Clear pending stop signals */ + sig_clear (SIGSTOP); + sig_clear (SIGTSTP); + sig_clear (SIGTTIN); + sig_clear (SIGTTOU); + } + + int rc = 1; + + sigproc_printf ("signal %d processing", si.si_signo); + struct sigaction& thissig = global_sigs[si.si_signo]; + + myself->rusage_self.ru_nsignals++; + + bool masked; + void *handler; + if (!hExeced || (void *) thissig.sa_handler == (void *) SIG_IGN) + handler = (void *) thissig.sa_handler; + else if (tls) + return 1; + else + handler = NULL; + + if (si.si_signo == SIGKILL) + goto exit_sig; + if (si.si_signo == SIGSTOP) + { + sig_clear (SIGCONT); + if (!tls) + tls = _main_tls; + goto stop; + } + + bool insigwait_mask; + if ((masked = ISSTATE (myself, PID_STOPPED))) + insigwait_mask = false; + else if (!tls) + insigwait_mask = !handler && (tls = _cygtls::find_tls (si.si_signo)); + else + insigwait_mask = sigismember (&tls->sigwait_mask, si.si_signo); + + if (insigwait_mask) + goto thread_specific; + + if (masked) + /* nothing to do */; + else if (sigismember (mask, si.si_signo)) + masked = true; + else if (tls) + masked = sigismember (&tls->sigmask, si.si_signo); + + if (!tls) + tls = _main_tls; + + if (masked) + { + sigproc_printf ("signal %d blocked", si.si_signo); + rc = -1; + goto done; + } + + /* Clear pending SIGCONT on stop signals */ + if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU) + sig_clear (SIGCONT); + +#ifdef CGF + if (being_debugged ()) + { + char sigmsg[sizeof (_CYGWIN_SIGNAL_STRING " 0xffffffff")]; + __small_sprintf (sigmsg, _CYGWIN_SIGNAL_STRING " %p", si.si_signo); + OutputDebugString (sigmsg); + } +#endif + + if (handler == (void *) SIG_DFL) + { + if (insigwait_mask) + goto thread_specific; + if (si.si_signo == SIGCHLD || si.si_signo == SIGIO || si.si_signo == SIGCONT || si.si_signo == SIGWINCH + || si.si_signo == SIGURG) + { + sigproc_printf ("default signal %d ignored", si.si_signo); + if (continue_now) + SetEvent (signal_arrived); + goto done; + } + + if (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU) + goto stop; + + goto exit_sig; + } + + if (handler == (void *) SIG_IGN) + { + sigproc_printf ("signal %d ignored", si.si_signo); + goto done; + } + + if (handler == (void *) SIG_ERR) + goto exit_sig; + + tls->set_siginfo (this); + goto dosig; + +stop: + /* Eat multiple attempts to STOP */ + if (ISSTATE (myself, PID_STOPPED)) + goto done; + handler = (void *) sig_handle_tty_stop; + thissig = dummy; + +dosig: + /* Dispatch to the appropriate function. */ + sigproc_printf ("signal %d, about to call %p", si.si_signo, handler); + rc = setup_handler (si.si_signo, handler, thissig, tls); + +done: + if (continue_now) + SetEvent (sigCONT); + sigproc_printf ("returning %d", rc); + return rc; + +thread_specific: + tls->sig = si.si_signo; + tls->set_siginfo (this); + sigproc_printf ("releasing sigwait for thread"); + SetEvent (tls->event); + goto done; + +exit_sig: + if (si.si_signo == SIGQUIT || si.si_signo == SIGABRT) + { + CONTEXT c; + c.ContextFlags = CONTEXT_FULL; + GetThreadContext (hMainThread, &c); + tls->copy_context (&c); + si.si_signo |= 0x80; + } + sigproc_printf ("signal %d, about to call do_exit", si.si_signo); + tls->signal_exit (si.si_signo); /* never returns */ +} + +/* Cover function to `do_exit' to handle exiting even in presence of more + exceptions. We used to call exit, but a SIGSEGV shouldn't cause atexit + routines to run. */ +void +_cygtls::signal_exit (int rc) +{ + if (hExeced) + { + sigproc_printf ("terminating captive process"); + TerminateProcess (hExeced, sigExeced = rc); + } + + signal_debugger (rc & 0x7f); + if ((rc & 0x80) && !try_to_debug ()) + stackdump (thread_context.ebp, 1, 1); + + lock_process until_exit (true); + if (hExeced || exit_state > ES_PROCESS_LOCKED) + myself.exit (rc); + + /* 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)", rc); + SetEvent (signal_arrived); + do_exit (rc); +} + +void +events_init () +{ + mask_sync.init ("mask_sync"); + windows_system_directory[0] = L'\0'; + GetSystemDirectoryW (windows_system_directory, sizeof (windows_system_directory) / sizeof (WCHAR) - 2); + PWCHAR end = wcschr (windows_system_directory, L'\0'); + if (end == windows_system_directory) + api_fatal ("can't find windows system directory"); + if (end[-1] != L'\\') + { + *end++ = L'\\'; + *end = L'\0'; + } + windows_system_directory_length = end - windows_system_directory; + debug_printf ("windows_system_directory '%W', windows_system_directory_length %d", + windows_system_directory, windows_system_directory_length); +} + +void +events_terminate () +{ + exit_already = 1; +} + +int +_cygtls::call_signal_handler () +{ + int this_sa_flags = 0; + /* Call signal handler. */ + while (sig) + { + lock (); + this_sa_flags = sa_flags; + int thissig = sig; + + pop (); + reset_signal_arrived (); + sigset_t this_oldmask = set_process_mask_delta (); + int this_errno = saved_errno; + sig = 0; + unlock (); // make sure synchronized + incyg = 0; + if (!(this_sa_flags & SA_SIGINFO)) + { + void (*sigfunc) (int) = func; + sigfunc (thissig); + } + else + { + siginfo_t thissi = infodata; + void (*sigact) (int, siginfo_t *, void *) = (void (*) (int, siginfo_t *, void *)) func; + /* no ucontext_t information provided yet */ + sigact (thissig, &thissi, NULL); + } + incyg = 1; + set_signal_mask (this_oldmask, _my_tls.sigmask); + if (this_errno >= 0) + set_errno (this_errno); + } + + return this_sa_flags & SA_RESTART; +} + +extern "C" void __stdcall +reset_signal_arrived () +{ + // NEEDED? WaitForSingleObject (signal_arrived, 10); + ResetEvent (signal_arrived); + sigproc_printf ("reset signal_arrived"); + if (_my_tls.stackptr > _my_tls.stack) + debug_printf ("stackptr[-1] %p", _my_tls.stackptr[-1]); +} + +void +_cygtls::copy_context (CONTEXT *c) +{ + memcpy (&thread_context, c, (&thread_context._internal - (unsigned char *) &thread_context)); +} + +void +_cygtls::signal_debugger (int sig) +{ + if (isinitialized () && being_debugged ()) + { + char sigmsg[2 * sizeof (_CYGWIN_SIGNAL_STRING " ffffffff ffffffff")]; + __small_sprintf (sigmsg, _CYGWIN_SIGNAL_STRING " %d %p %p", sig, thread_id, &thread_context); + OutputDebugString (sigmsg); + } +}