diff options
author | Corinna Vinschen <vinschen@redhat.com> | 2006-07-13 08:34:54 +0000 |
---|---|---|
committer | Corinna Vinschen <vinschen@redhat.com> | 2006-07-13 08:34:54 +0000 |
commit | df457dc39eb25d1645bb867e7709b7bfbae468e5 (patch) | |
tree | 99f5aa2426e66d2c0e8dc7b95d05b26e581f7c8d | |
parent | 36c31208450a61ffc6e2f93908f74042c00fb43c (diff) | |
download | gdb-df457dc39eb25d1645bb867e7709b7bfbae468e5.tar.gz |
* Merge HEAD into cv-branch.
-rw-r--r-- | winsup/cygwin/ChangeLog | 16 | ||||
-rw-r--r-- | winsup/cygwin/exceptions.cc | 1393 | ||||
-rw-r--r-- | winsup/cygwin/mmap.cc | 2037 | ||||
-rw-r--r-- | winsup/cygwin/winsup.h | 362 |
4 files changed, 3808 insertions, 0 deletions
diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 30bfa928a8f..f63f1117ec8 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,19 @@ +2006-07-13 Corinna Vinschen <corinna@vinschen.de> + + * exceptions.cc (_cygtls::handle_exceptions): Call new + mmap_is_attached_or_noreserve_page function in case of access violation + and allow application to retry access on noreserve pages. + * mmap.cc (mmap_is_attached_or_noreserve_page): Changed from + mmap_is_attached_page. Handle also noreserve pages now. Change + comment accordingly. + * winsup.h (mmap_is_attached_or_noreserve_page): Declare instead of + mmap_is_attached_page. + +2006-07-12 Corinna Vinschen <corinna@vinschen.de> + + * mmap.cc (mmap_record::alloc_page_map): Don't call VirtualProtect + on maps created with MAP_NORESERVE. + 2006-07-12 Corinna Vinschen <corinna@vinschen.de> * include/netdb.h: Declare rcmd, rcmd_af, rexec, rresvport, diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc new file mode 100644 index 00000000000..12c6e0687b5 --- /dev/null +++ b/winsup/cygwin/exceptions.cc @@ -0,0 +1,1393 @@ +/* exceptions.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, + 2005, 2006 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 <wingdi.h> +#include <winuser.h> +#include <imagehlp.h> +#include <stdlib.h> +#include <setjmp.h> +#include <assert.h> +#include <syslog.h> + +#include "exceptions.h" +#include "sync.h" +#include "pinfo.h" +#include "cygtls.h" +#include "sigproc.h" +#include "cygerrno.h" +#include "shared_info.h" +#include "perprocess.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "child_info.h" + +#define CALL_HANDLER_RETRY 20 + +char debugger_command[2 * CYG_MAX_PATH + 20]; + +extern "C" { +extern void sigdelayed (); +}; + +extern child_info_spawn *chExeced; +int NO_COPY sigExeced; + +static BOOL WINAPI ctrl_c_handler (DWORD); +char 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. */ + +BOOL WINAPI +dummy_ctrl_c_handler (DWORD) +{ + return TRUE; +} + +void +init_console_handler (bool install_handler) +{ + BOOL res; + + SetConsoleCtrlHandler (ctrl_c_handler, FALSE); + if (wincap.has_null_console_handler_routine ()) + SetConsoleCtrlHandler (NULL, FALSE); + if (install_handler) + res = SetConsoleCtrlHandler (ctrl_c_handler, TRUE); + else if (wincap.has_null_console_handler_routine ()) + res = SetConsoleCtrlHandler (NULL, TRUE); + else + res = SetConsoleCtrlHandler (dummy_ctrl_c_handler, 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[CYG_MAX_PATH]; + if (!GetModuleFileName (NULL, pgm, CYG_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; + char corefile[strlen (p) + sizeof (".stackdump")]; + __small_sprintf (corefile, "%s.stackdump", p); + HANDLE h = CreateFile (corefile, GENERIC_WRITE, 0, &sec_none_nih, + CREATE_ALWAYS, 0, 0); + if (h != INVALID_HANDLE_VALUE) + { + if (!myself->cygstarted) + system_printf ("Dumping stack trace to %s", corefile); + else + debug_printf ("Dumping stack trace to %s", corefile); + 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 +} + +/* Walk the stack by looking at successive stored 'bp' frames. + This is not foolproof. */ +int +stack_info::walk () +{ + char **ebp; + if ((ebp = (char **) next_offset ()) == NULL) + return 0; + + sf.AddrFrame.Offset = (DWORD) ebp; + sf.AddrPC.Offset = sf.AddrReturn.Offset; + + if (!sf.AddrPC.Offset) + return 0; /* stack frames are exhausted */ + + /* The return address always follows the stack pointer */ + sf.AddrReturn.Offset = (DWORD) *++ebp; + + if (needargs) + /* The arguments follow the return address */ + for (unsigned i = 0; 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)" : ""); +} + +static bool +inside_kernel (CONTEXT *cx) +{ + int res; + MEMORY_BASIC_INFORMATION m; + + if (in_dllentry) + 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); + + char *checkdir = (char *) alloca (windows_system_directory_length + 4); + memset (checkdir, 0, sizeof (checkdir)); + +# 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 (!GetModuleFileName (h, checkdir, windows_system_directory_length + 2)) + res = false; + else + res = strncasematch (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) +{ + 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}; + + STARTUPINFO 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 */ + char* rawenv = GetEnvironmentStrings () ; + for (char* p = rawenv; *p != '\0'; p = strchr (p, '\0') + 1) + { + if (strncmp (p, "CYGWIN=", strlen ("CYGWIN=")) == 0) + { + char* q = strstr (p, "error_start") ; + /* replace 'error_start=...' with '_rror_start=...' */ + if (q) + { + *q = '_' ; + SetEnvironmentVariable ("CYGWIN", p + strlen ("CYGWIN=")) ; + } + break ; + } + } + + console_printf ("*** starting debugger for pid %u, tid %u\n", + cygwin_pid (GetCurrentProcessId ()), GetCurrentThreadId ()); + BOOL dbg; + dbg = CreateProcess (NULL, + debugger_command, + 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 "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_page (e->ExceptionInformation[1])) + { + case 2: /* MAP_NORESERVE page, now commited. */ + return 0; + case 1: /* 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; + } + + rtl_unwind (frame, e); + + 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); + + if (global_sigs[si.si_signo].sa_mask & SIGTOMASK (si.si_signo)) + 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); + if (!cygwin_finished_initializing + || &me == _sig_tls + || (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; + } + + 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 (!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 = myself->getsigmask (); // Remember for restoration + + set_signal_mask (tempmask, myself->getsigmask ()); + 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 ()) + && GetUserObjectInformationA (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; + + 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, myself->getsigmask ()); +sigproc_printf ("mask now %p\n", myself->getsigmask ()); +} + +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 = myself->getsigmask (); + sigaddset (&mask, sig); + set_signal_mask (mask, myself->getsigmask ()); + 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 = myself->getsigmask (); + sigdelset (&mask, sig); + set_signal_mask (mask, myself->getsigmask ()); + 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 = myself->getsigmask (); + /* 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, myself->getsigmask ()); + 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 = myself->getsigmask (); + newmask = (oldmask | _my_tls.deltamask) & ~SIG_NONMASKABLE; + sigproc_printf ("oldmask %p, newmask %p, deltamask %p", oldmask, newmask, + _my_tls.deltamask); + myself->setsigmask (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; + 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; + struct sigaction dummy = global_sigs[SIGSTOP]; + 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) + myself.exit (rc); + + /* Starve other threads in a vain attempt to stop them from doing something + stupid. */ + SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL); + + user_data->resourcelocks->Delete (); + user_data->resourcelocks->Init (); + + 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] = '\0'; + GetSystemDirectory (windows_system_directory, sizeof (windows_system_directory) - 2); + char *end = strchr (windows_system_directory, '\0'); + if (end == windows_system_directory) + api_fatal ("can't find windows system directory"); + if (end[-1] != '\\') + { + *end++ = '\\'; + *end = '\0'; + } + windows_system_directory_length = end - windows_system_directory; + debug_printf ("windows_system_directory '%s', 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, myself->getsigmask ()); + 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); + } +} diff --git a/winsup/cygwin/mmap.cc b/winsup/cygwin/mmap.cc new file mode 100644 index 00000000000..193ed7c0f22 --- /dev/null +++ b/winsup/cygwin/mmap.cc @@ -0,0 +1,2037 @@ +/* mmap.cc + + Copyright 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2004, 2005, + 2006 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 <unistd.h> +#include <stdlib.h> +#include <stddef.h> +#include <sys/mman.h> +#include <sys/param.h> +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "pinfo.h" +#include "sys/cygwin.h" +#include "ntdll.h" + +/* __PROT_ATTACH indicates an anonymous mapping which is supposed to be + attached to a file mapping for pages beyond the file's EOF. The idea + is to support mappings longer than the file, without the file growing + to mapping length (POSIX semantics). */ +#define __PROT_ATTACH 0x8000000 +/* Filler pages are the pages from the last file backed page to the next + 64K boundary. These pages are created as anonymous pages, but with + the same page protection as the file's pages, since POSIX applications + expect to be able to access this part the same way as the file pages. */ +#define __PROT_FILLER 0x4000000 + +#define PAGE_CNT(bytes) howmany((bytes),getpagesize()) + +#define PGBITS (sizeof (DWORD)*8) +#define MAPSIZE(pages) howmany ((pages), PGBITS) + +#define MAP_SET(n) (page_map[(n)/PGBITS] |= (1L << ((n) % PGBITS))) +#define MAP_CLR(n) (page_map[(n)/PGBITS] &= ~(1L << ((n) % PGBITS))) +#define MAP_ISSET(n) (page_map[(n)/PGBITS] & (1L << ((n) % PGBITS))) + +/* Used for anonymous mappings. */ +static fhandler_dev_zero fh_anonymous; +/* Used for reopening a disk file when necessary. */ +static fhandler_disk_file fh_disk_file; + +/* Small helpers to avoid having lots of flag bit tests in the code. */ +static inline bool +priv (int flags) +{ + return (flags & MAP_PRIVATE) == MAP_PRIVATE; +} + +static inline bool +fixed (int flags) +{ + return (flags & MAP_FIXED) == MAP_FIXED; +} + +static inline bool +anonymous (int flags) +{ + return (flags & MAP_ANONYMOUS) == MAP_ANONYMOUS; +} + +static inline bool +noreserve (int flags) +{ + return (flags & MAP_NORESERVE) == MAP_NORESERVE; +} + +static inline bool +autogrow (int flags) +{ + return (flags & MAP_AUTOGROW) == MAP_AUTOGROW; +} + +static inline bool +attached (int prot) +{ + return (prot & __PROT_ATTACH) == __PROT_ATTACH; +} + +static inline bool +filler (int prot) +{ + return (prot & __PROT_FILLER) == __PROT_FILLER; +} + +static inline DWORD +gen_create_protect (DWORD openflags, int flags) +{ + DWORD ret = PAGE_READONLY; + + if (priv (flags)) + ret = PAGE_WRITECOPY; + else if (openflags & GENERIC_WRITE) + ret = PAGE_READWRITE; + + /* Ignore EXECUTE permission on 9x. */ + if ((openflags & GENERIC_EXECUTE) + && wincap.virtual_protect_works_on_shared_pages ()) + ret <<= 4; + + return ret; +} + +/* Generate Windows protection flags from mmap prot and flag values. */ +static inline DWORD +gen_protect (int prot, int flags) +{ + DWORD ret = PAGE_NOACCESS; + + /* Attached pages are only reserved, but the protection must be a + valid value, so we just return PAGE_READWRITE. */ + if (attached (prot)) + return PAGE_EXECUTE_READWRITE; + + if (prot & PROT_WRITE) + ret = (priv (flags) && (!anonymous (flags) || filler (prot))) + ? PAGE_WRITECOPY : PAGE_READWRITE; + else if (prot & PROT_READ) + ret = PAGE_READONLY; + + /* Ignore EXECUTE permission on 9x. */ + if ((prot & PROT_EXEC) + && wincap.virtual_protect_works_on_shared_pages ()) + ret <<= 4; + + return ret; +} + +/* Generate Windows access flags from mmap prot and flag values. + Only used on 9x. PROT_EXEC not supported here since it's not + necessary. */ +static inline DWORD +gen_access (DWORD openflags, int flags) +{ + DWORD ret = FILE_MAP_READ; + if (priv (flags)) + ret = FILE_MAP_COPY; + else if (openflags & GENERIC_WRITE) + ret = priv (flags) ? FILE_MAP_COPY : FILE_MAP_WRITE; + return ret; +} + +/* OS specific wrapper functions for map/section functions. */ +static BOOL +VirtualProt9x (PVOID addr, SIZE_T len, DWORD prot, PDWORD oldprot) +{ + if (addr >= (caddr_t)0x80000000 && addr <= (caddr_t)0xBFFFFFFF) + return TRUE; /* FAKEALARM! */ + return VirtualProtect (addr, len, prot, oldprot); +} + +static BOOL +VirtualProtNT (PVOID addr, SIZE_T len, DWORD prot, PDWORD oldprot) +{ + return VirtualProtect (addr, len, prot, oldprot); +} + +static BOOL +VirtualProtEx9x (HANDLE parent, PVOID addr, SIZE_T len, DWORD prot, + PDWORD oldprot) +{ + if (addr >= (caddr_t)0x80000000 && addr <= (caddr_t)0xBFFFFFFF) + return TRUE; /* FAKEALARM! */ + return VirtualProtectEx (parent, addr, len, prot, oldprot); +} +static BOOL +VirtualProtExNT (HANDLE parent, PVOID addr, SIZE_T len, DWORD prot, + PDWORD oldprot) +{ + return VirtualProtectEx (parent, addr, len, prot, oldprot); +} + +/* This allows to stay lazy about VirtualProtect usage in subsequent code. */ +#define VirtualProtect(a,l,p,o) (mmap_func->VirtualProt((a),(l),(p),(o))) +#define VirtualProtectEx(h,a,l,p,o) (mmap_func->VirtualProtEx((h),(a),(l),(p),(o))) + +static HANDLE +CreateMapping9x (HANDLE fhdl, size_t len, _off64_t off, DWORD openflags, + int prot, int flags, const char *name) +{ + HANDLE h; + DWORD high, low; + + DWORD protect = gen_create_protect (openflags, flags); + + /* copy-on-write doesn't work properly on 9x with real files. While the + changes are not propagated to the file, they are visible to other + processes sharing the same file mapping object. Workaround: Don't + use named file mapping. That should work since sharing file + mappings only works reliable using named file mapping on 9x. + + On 9x/ME try first to open the mapping by name when opening a + shared file object. This is needed since 9x/ME only shares objects + between processes by name. What a mess... */ + + if (fhdl != INVALID_HANDLE_VALUE && !priv (flags)) + { + /* Grrr, the whole stuff is just needed to try to get a reliable + mapping of the same file. Even that uprising isn't bullet + proof but it does it's best... */ + char namebuf[CYG_MAX_PATH]; + cygwin_conv_to_full_posix_path (name, namebuf); + for (int i = strlen (namebuf) - 1; i >= 0; --i) + namebuf[i] = cyg_tolower (namebuf [i]); + + debug_printf ("named sharing"); + DWORD access = gen_access (openflags, flags); + /* Different access modes result in incompatible mappings. So we + create different maps per access mode by using different names. */ + switch (access) + { + case FILE_MAP_READ: + namebuf[0] = 'R'; + break; + case FILE_MAP_WRITE: + namebuf[0] = 'W'; + break; + case FILE_MAP_COPY: + namebuf[0] = 'C'; + break; + } + if (!(h = OpenFileMapping (access, TRUE, namebuf))) + h = CreateFileMapping (fhdl, &sec_none, protect, 0, 0, namebuf); + } + else if (fhdl == INVALID_HANDLE_VALUE) + { + /* Standard anonymous mapping needs non-zero len. */ + h = CreateFileMapping (fhdl, &sec_none, protect, 0, len, NULL); + } + else if (autogrow (flags)) + { + high = (off + len) >> 32; + low = (off + len) & UINT32_MAX; + /* Auto-grow only works if the protection is PAGE_READWRITE. So, + first we call CreateFileMapping with PAGE_READWRITE, then, if the + requested protection is different, we close the mapping and + reopen it again with the correct protection, if auto-grow worked. */ + h = CreateFileMapping (fhdl, &sec_none, PAGE_READWRITE, + high, low, NULL); + if (h && protect != PAGE_READWRITE) + { + CloseHandle (h); + h = CreateFileMapping (fhdl, &sec_none, protect, + high, low, NULL); + } + } + else + { + /* Zero len creates mapping for whole file. */ + h = CreateFileMapping (fhdl, &sec_none, protect, 0, 0, NULL); + } + return h; +} + +static HANDLE +CreateMappingNT (HANDLE fhdl, size_t len, _off64_t off, DWORD openflags, + int prot, int flags, const char *) +{ + HANDLE h; + NTSTATUS ret; + + LARGE_INTEGER sectionsize = { QuadPart: len }; + ULONG protect = gen_create_protect (openflags, flags); + ULONG attributes = attached (prot) ? SEC_RESERVE : SEC_COMMIT; + + OBJECT_ATTRIBUTES oa; + InitializeObjectAttributes (&oa, NULL, OBJ_INHERIT, NULL, + sec_none.lpSecurityDescriptor); + + if (fhdl == INVALID_HANDLE_VALUE) + { + /* Standard anonymous mapping needs non-zero len. */ + ret = NtCreateSection (&h, SECTION_ALL_ACCESS, &oa, + §ionsize, protect, attributes, NULL); + } + else if (autogrow (flags)) + { + /* Auto-grow only works if the protection is PAGE_READWRITE. So, + first we call NtCreateSection with PAGE_READWRITE, then, if the + requested protection is different, we close the mapping and + reopen it again with the correct protection, if auto-grow worked. */ + sectionsize.QuadPart += off; + ret = NtCreateSection (&h, SECTION_ALL_ACCESS, &oa, + §ionsize, PAGE_READWRITE, attributes, fhdl); + if (NT_SUCCESS (ret) && protect != PAGE_READWRITE) + { + CloseHandle (h); + ret = NtCreateSection (&h, SECTION_ALL_ACCESS, &oa, + §ionsize, protect, attributes, fhdl); + } + } + else + { + /* Zero len creates mapping for whole file and allows + AT_EXTENDABLE_FILE mapping, if we ever use it... */ + sectionsize.QuadPart = 0; + ret = NtCreateSection (&h, SECTION_ALL_ACCESS, &oa, + §ionsize, protect, attributes, fhdl); + } + if (!NT_SUCCESS (ret)) + { + h = NULL; + SetLastError (RtlNtStatusToDosError (ret)); + } + return h; +} + +void * +MapView9x (HANDLE h, void *addr, size_t len, DWORD openflags, + int prot, int flags, _off64_t off) +{ + DWORD high = off >> 32; + DWORD low = off & UINT32_MAX; + DWORD access = gen_access (openflags, flags); + void *base; + + /* Try mapping using the given address first, even if it's NULL. + If it failed, and addr was not NULL and flags is not MAP_FIXED, + try again with NULL address. */ + if (!addr) + base = MapViewOfFile (h, access, high, low, len); + else + { + base = MapViewOfFileEx (h, access, high, low, len, addr); + if (!base && !fixed (flags)) + base = MapViewOfFile (h, access, high, low, len); + } + debug_printf ("%x = MapViewOfFileEx (h:%x, access:%x, 0, off:%D, " + "len:%u, addr:%x)", base, h, access, off, len, addr); + return base; +} + +void * +MapViewNT (HANDLE h, void *addr, size_t len, DWORD openflags, + int prot, int flags, _off64_t off) +{ + NTSTATUS ret; + LARGE_INTEGER offset = { QuadPart:off }; + DWORD protect = gen_create_protect (openflags, flags); + void *base = addr; + ULONG commitsize = attached (prot) ? 0 : len; + ULONG viewsize = len; + ULONG alloc_type = base && !wincap.is_wow64 () ? AT_ROUND_TO_PAGE : 0; + + /* Try mapping using the given address first, even if it's NULL. + If it failed, and addr was not NULL and flags is not MAP_FIXED, + try again with NULL address. */ + ret = NtMapViewOfSection (h, GetCurrentProcess (), &base, 0, commitsize, + &offset, &viewsize, ViewShare, alloc_type, protect); + if (!NT_SUCCESS (ret) && addr && !fixed (flags)) + { + base = NULL; + ret = NtMapViewOfSection (h, GetCurrentProcess (), &base, 0, commitsize, + &offset, &viewsize, ViewShare, 0, protect); + } + if (!NT_SUCCESS (ret)) + { + base = NULL; + SetLastError (RtlNtStatusToDosError (ret)); + } + debug_printf ("%x = NtMapViewOfSection (h:%x, addr:%x, len:%u, off:%D, " + "protect:%x, type:%x)", base, h, addr, len, off, protect, 0); + return base; +} + +struct mmap_func_t +{ + HANDLE (*CreateMapping)(HANDLE, size_t, _off64_t, DWORD, int, int, + const char *); + void * (*MapView)(HANDLE, void *, size_t, DWORD, int, int, _off64_t); + BOOL (*VirtualProt)(PVOID, SIZE_T, DWORD, PDWORD); + BOOL (*VirtualProtEx)(HANDLE, PVOID, SIZE_T, DWORD, PDWORD); +}; + +mmap_func_t mmap_funcs_9x = +{ + CreateMapping9x, + MapView9x, + VirtualProt9x, + VirtualProtEx9x +}; + +mmap_func_t mmap_funcs_nt = +{ + CreateMappingNT, + MapViewNT, + VirtualProtNT, + VirtualProtExNT +}; + +mmap_func_t *mmap_func; + +void +mmap_init () +{ + mmap_func = wincap.is_winnt () ? &mmap_funcs_nt : &mmap_funcs_9x; +} + +/* Class structure used to keep a record of all current mmap areas + in a process. Needed for bookkeeping all mmaps in a process and + for duplicating all mmaps after fork() since mmaps are not propagated + to child processes by Windows. All information must be duplicated + by hand, see fixup_mmaps_after_fork(). + + The class structure: + + One member of class map per process, global variable mmapped_areas. + Contains a dynamic class list array. Each list entry represents all + mapping to a file, keyed by file descriptor and file name hash. + Each list entry contains a dynamic class mmap_record array. Each + mmap_record represents exactly one mapping. For each mapping, there's + an additional so called `page_map'. It's an array of bits, one bit + per mapped memory page. The bit is set if the page is accessible, + unset otherwise. */ + +class mmap_record +{ + private: + int fd; + HANDLE mapping_hdl; + DWORD openflags; + int prot; + int flags; + _off64_t offset; + DWORD len; + caddr_t base_address; + DWORD *page_map; + device dev; + + public: + mmap_record (int nfd, HANDLE h, DWORD of, int p, int f, _off64_t o, DWORD l, + caddr_t b) : + fd (nfd), + mapping_hdl (h), + openflags (of), + prot (p), + flags (f), + offset (o), + len (l), + base_address (b), + page_map (NULL) + { + dev.devn = 0; + if (fd >= 0 && !cygheap->fdtab.not_open (fd)) + dev = cygheap->fdtab[fd]->dev (); + else if (fd == -1) + dev.parse (FH_ZERO); + } + + int get_fd () const { return fd; } + HANDLE get_handle () const { return mapping_hdl; } + device& get_device () { return dev; } + int get_prot () const { return prot; } + int get_openflags () const { return openflags; } + int get_flags () const { return flags; } + bool priv () const { return ::priv (flags); } + bool fixed () const { return ::fixed (flags); } + bool anonymous () const { return ::anonymous (flags); } + bool noreserve () const { return ::noreserve (flags); } + bool autogrow () const { return ::autogrow (flags); } + bool attached () const { return ::attached (prot); } + bool filler () const { return ::filler (prot); } + _off64_t get_offset () const { return offset; } + DWORD get_len () const { return len; } + caddr_t get_address () const { return base_address; } + + bool alloc_page_map (); + void free_page_map () { if (page_map) cfree (page_map); } + + DWORD find_unused_pages (DWORD pages) const; + _off64_t map_pages (_off64_t off, DWORD len); + bool map_pages (caddr_t addr, DWORD len); + bool unmap_pages (caddr_t addr, DWORD len); + int access (caddr_t address); + + fhandler_base *alloc_fh (); + void free_fh (fhandler_base *fh); + + DWORD gen_create_protect () const + { return ::gen_create_protect (get_openflags (), get_flags ()); } + DWORD gen_protect () const + { return ::gen_protect (get_prot (), get_flags ()); } + DWORD gen_access () const + { return ::gen_access (get_openflags (), get_flags ()); } + bool compatible_flags (int fl) const; +}; + +class list +{ + private: + mmap_record *recs; + int nrecs, maxrecs; + int fd; + DWORD hash; + + public: + int get_fd () const { return fd; } + DWORD get_hash () const { return hash; } + mmap_record *get_record (int i) { return i >= nrecs ? NULL : recs + i; } + + bool anonymous () const { return fd == -1; } + void set (int nfd); + mmap_record *add_record (mmap_record r); + bool del_record (int i); + void free_recs () { if (recs) cfree (recs); } + mmap_record *search_record (_off64_t off, DWORD len); + long search_record (caddr_t addr, DWORD len, caddr_t &m_addr, DWORD &m_len, + long start); + caddr_t try_map (void *addr, size_t len, int flags, _off64_t off); +}; + +class map +{ + private: + list *lists; + unsigned nlists, maxlists; + + public: + list *get_list (unsigned i) { return i >= nlists ? NULL : lists + i; } + list *get_list_by_fd (int fd); + list *add_list (int fd); + void del_list (unsigned i); +}; + +/* This is the global map structure pointer. */ +static map mmapped_areas; + +bool +mmap_record::compatible_flags (int fl) const +{ +#define MAP_COMPATMASK (MAP_TYPE | MAP_NORESERVE) + return (get_flags () & MAP_COMPATMASK) == (fl & MAP_COMPATMASK); +} + +DWORD +mmap_record::find_unused_pages (DWORD pages) const +{ + DWORD mapped_pages = PAGE_CNT (get_len ()); + DWORD start; + + if (pages > mapped_pages) + return (DWORD)-1; + for (start = 0; start <= mapped_pages - pages; ++start) + if (!MAP_ISSET (start)) + { + DWORD cnt; + for (cnt = 0; cnt < pages; ++cnt) + if (MAP_ISSET (start + cnt)) + break; + if (cnt >= pages) + return start; + } + return (DWORD)-1; +} + +bool +mmap_record::alloc_page_map () +{ + /* Allocate one bit per page */ + if (!(page_map = (DWORD *) ccalloc (HEAP_MMAP, + MAPSIZE (PAGE_CNT (get_len ())), + sizeof (DWORD)))) + return false; + + DWORD start_protect = gen_create_protect (); + DWORD real_protect = gen_protect (); + if (real_protect != start_protect && !noreserve () + && !VirtualProtect (get_address (), get_len (), + real_protect, &start_protect)) + system_printf ("Warning: VirtualProtect (addr: %p, len: 0x%x, " + "new_prot: 0x%x, old_prot: 0x%x), %E", + get_address (), get_len (), + real_protect, start_protect); + DWORD len = PAGE_CNT (get_len ()); + while (len-- > 0) + MAP_SET (len); + return true; +} + +_off64_t +mmap_record::map_pages (_off64_t off, DWORD len) +{ + /* Used ONLY if this mapping matches into the chunk of another already + performed mapping in a special case of MAP_ANON|MAP_PRIVATE. + + Otherwise it's job is now done by alloc_page_map(). */ + DWORD old_prot; + debug_printf ("map_pages (fd=%d, off=%D, len=%u)", get_fd (), off, len); + len = PAGE_CNT (len); + + if ((off = find_unused_pages (len)) == (DWORD)-1) + return 0L; + if (!noreserve () + && !VirtualProtect (get_address () + off * getpagesize (), + len * getpagesize (), gen_protect (), &old_prot)) + { + __seterrno (); + return (_off64_t)-1; + } + + while (len-- > 0) + MAP_SET (off + len); + return off * getpagesize (); +} + +bool +mmap_record::map_pages (caddr_t addr, DWORD len) +{ + debug_printf ("map_pages (addr=%x, len=%u)", addr, len); + DWORD old_prot; + DWORD off = addr - get_address (); + off /= getpagesize (); + len = PAGE_CNT (len); + /* First check if the area is unused right now. */ + for (DWORD l = 0; l < len; ++l) + if (MAP_ISSET (off + l)) + { + set_errno (EINVAL); + return false; + } + if (!noreserve () + && !VirtualProtect (get_address () + off * getpagesize (), + len * getpagesize (), gen_protect (), &old_prot)) + { + __seterrno (); + return false; + } + for (; len-- > 0; ++off) + MAP_SET (off); + return true; +} + +bool +mmap_record::unmap_pages (caddr_t addr, DWORD len) +{ + DWORD old_prot; + DWORD off = addr - get_address (); + off /= getpagesize (); + len = PAGE_CNT (len); + if (anonymous () && priv () && noreserve () + && !VirtualFree (get_address () + off * getpagesize (), + len * getpagesize (), MEM_DECOMMIT)) + debug_printf ("VirtualFree in unmap_pages () failed, %E"); + else if (!VirtualProtect (get_address () + off * getpagesize (), + len * getpagesize (), PAGE_NOACCESS, &old_prot)) + debug_printf ("VirtualProtect in unmap_pages () failed, %E"); + + for (; len-- > 0; ++off) + MAP_CLR (off); + /* Return TRUE if all pages are free'd which may result in unmapping + the whole chunk. */ + for (len = MAPSIZE (PAGE_CNT (get_len ())); len > 0; ) + if (page_map[--len]) + return false; + return true; +} + +int +mmap_record::access (caddr_t address) +{ + if (address < get_address () || address >= get_address () + get_len ()) + return 0; + DWORD off = (address - get_address ()) / getpagesize (); + return MAP_ISSET (off); +} + +fhandler_base * +mmap_record::alloc_fh () +{ + if (anonymous ()) + { + fh_anonymous.set_io_handle (INVALID_HANDLE_VALUE); + fh_anonymous.set_access (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); + return &fh_anonymous; + } + + /* The file descriptor could have been closed or, even + worse, could have been reused for another file before + the call to fork(). This requires creating a fhandler + of the correct type to be sure to call the method of the + correct class. */ + fhandler_base *fh = build_fh_dev (get_device ()); + fh->set_access (get_openflags ()); + return fh; +} + +void +mmap_record::free_fh (fhandler_base *fh) +{ + if (!anonymous ()) + cfree (fh); +} + +mmap_record * +list::add_record (mmap_record r) +{ + if (nrecs == maxrecs) + { + mmap_record *new_recs; + if (maxrecs == 0) + new_recs = (mmap_record *) + cmalloc (HEAP_MMAP, 5 * sizeof (mmap_record)); + else + new_recs = (mmap_record *) + crealloc (recs, (maxrecs + 5) * sizeof (mmap_record)); + if (!new_recs) + return NULL; + maxrecs += 5; + recs = new_recs; + } + recs[nrecs] = r; + if (!recs[nrecs].alloc_page_map ()) + return NULL; + return recs + nrecs++; +} + +/* Used in mmap() */ +mmap_record * +list::search_record (_off64_t off, DWORD len) +{ + if (anonymous () && !off) + { + len = PAGE_CNT (len); + for (int i = 0; i < nrecs; ++i) + if (recs[i].find_unused_pages (len) != (DWORD)-1) + return recs + i; + } + else + { + for (int i = 0; i < nrecs; ++i) + if (off >= recs[i].get_offset () + && off + len <= recs[i].get_offset () + + (PAGE_CNT (recs[i].get_len ()) * getpagesize ())) + return recs + i; + } + return NULL; +} + +/* Used in munmap() */ +long +list::search_record (caddr_t addr, DWORD len, caddr_t &m_addr, DWORD &m_len, + long start) +{ + caddr_t low, high; + + for (long i = start + 1; i < nrecs; ++i) + { + low = (addr >= recs[i].get_address ()) ? addr : recs[i].get_address (); + high = recs[i].get_address (); + if (recs[i].filler ()) + high += recs[i].get_len (); + else + high += (PAGE_CNT (recs[i].get_len ()) * getpagesize ()); + high = (addr + len < high) ? addr + len : high; + if (low < high) + { + m_addr = low; + m_len = high - low; + return i; + } + } + return -1; +} + +void +list::set (int nfd) +{ + fd = nfd; + if (!anonymous ()) + { + /* The fd isn't sufficient since it could already be the fd of another + file. So we use the inode number as evaluated by fstat to identify + the file. */ + struct stat st; + fstat (nfd, &st); + hash = st.st_ino; + } + nrecs = maxrecs = 0; + recs = NULL; +} + +bool +list::del_record (int i) +{ + if (i < nrecs) + { + recs[i].free_page_map (); + for (; i < nrecs - 1; i++) + recs[i] = recs[i + 1]; + nrecs--; + } + /* Return true if the list is empty which allows the caller to remove + this list from the list array. */ + return !nrecs; +} + +caddr_t +list::try_map (void *addr, size_t len, int flags, _off64_t off) +{ + mmap_record *rec; + + if (off == 0 && !fixed (flags)) + { + /* If MAP_FIXED isn't given, check if this mapping matches into the + chunk of another already performed mapping. */ + if ((rec = search_record (off, len)) != NULL + && rec->compatible_flags (flags)) + { + if ((off = rec->map_pages (off, len)) == (_off64_t)-1) + return (caddr_t) MAP_FAILED; + return (caddr_t) rec->get_address () + off; + } + } + else if (fixed (flags)) + { + /* If MAP_FIXED is given, test if the requested area is in an + unmapped part of an still active mapping. This can happen + if a memory region is unmapped and remapped with MAP_FIXED. */ + caddr_t u_addr; + DWORD u_len; + long record_idx = -1; + if ((record_idx = search_record ((caddr_t) addr, len, u_addr, u_len, + record_idx)) >= 0) + { + rec = get_record (record_idx); + if (u_addr > (caddr_t) addr || u_addr + len < (caddr_t) addr + len + || !rec->compatible_flags (flags)) + { + /* Partial match only, or access mode doesn't match. */ + /* FIXME: Handle partial mappings gracefully if adjacent + memory is available. */ + set_errno (EINVAL); + return (caddr_t) MAP_FAILED; + } + if (!rec->map_pages ((caddr_t) addr, len)) + return (caddr_t) MAP_FAILED; + return (caddr_t) addr; + } + } + return NULL; +} + +list * +map::get_list_by_fd (int fd) +{ + unsigned i; + for (i = 0; i < nlists; i++) + { + if (fd == -1 && lists[i].anonymous ()) + return lists + i; + /* The fd isn't sufficient since it could already be the fd of another + file. So we use the inode number as evaluated by fstat to identify + the file. */ + struct stat st; + if (fd != -1 && !fstat (fd, &st) && lists[i].get_hash () == st.st_ino) + return lists + i; + } + return 0; +} + +list * +map::add_list (int fd) +{ + if (nlists == maxlists) + { + list *new_lists; + if (maxlists == 0) + new_lists = (list *) cmalloc (HEAP_MMAP, 5 * sizeof (list)); + else + new_lists = (list *) crealloc (lists, (maxlists + 5) * sizeof (list)); + if (!new_lists) + return NULL; + maxlists += 5; + lists = new_lists; + } + lists[nlists].set (fd); + return lists + nlists++; +} + +void +map::del_list (unsigned i) +{ + if (i < nlists) + { + lists[i].free_recs (); + for (; i < nlists - 1; i++) + lists[i] = lists[i + 1]; + nlists--; + } +} + +/* This function is called from exception_handler when a segmentation + violation has happened. We have two cases to check here. + + First, is it an address within "attached" mmap pages (indicated by + the __PROT_ATTACH protection, see there)? In this case the function + returns 1 and the exception_handler raises SIGBUS, as demanded by the + memory protection extension described in SUSv3 (see the mmap man + page). + + Second, check if the address is within "noreserve" mmap pages + (indicated by MAP_NORESERVE flag). If so, the function calls + VirtualAlloc to commit the page and returns 2. The exception handler + then just returns with 0 and the affected application retries the + failing memory access. If VirtualAlloc fails, the function returns + 1, so that the exception handler raises a SIGBUS, as described in the + MAP_NORESERVE man pages for Linux and Solaris. + + In any other case 0 is returned and a normal SIGSEGV is raised. */ +int +mmap_is_attached_or_noreserve_page (ULONG_PTR addr) +{ + list *map_list; + long record_idx; + caddr_t u_addr; + DWORD u_len; + DWORD pagesize = getsystempagesize (); + + addr = rounddown (addr, pagesize); + if (!(map_list = mmapped_areas.get_list_by_fd (-1))) + return 0; + if ((record_idx = map_list->search_record ((caddr_t)addr, pagesize, + u_addr, u_len, -1)) < 0) + return 0; + if (map_list->get_record (record_idx)->attached ()) + return 1; + if (!map_list->get_record (record_idx)->noreserve ()) + return 0; + DWORD new_prot = map_list->get_record (record_idx)->gen_protect (); + return VirtualAlloc ((void *)addr, pagesize, MEM_COMMIT, new_prot) ? 2 : 1; +} + +static caddr_t +mmap_worker (fhandler_base *fh, caddr_t base, size_t len, int prot, int flags, + int fd, _off64_t off) +{ + list *map_list; + HANDLE h = fh->mmap (&base, len, prot, flags, off); + if (h == INVALID_HANDLE_VALUE) + return NULL; + if (!(map_list = mmapped_areas.get_list_by_fd (fd)) + && !(map_list = mmapped_areas.add_list (fd))) + { + fh->munmap (h, base, len); + return NULL; + } + mmap_record mmap_rec (fd, h, fh->get_access (), prot, flags, off, len, base); + mmap_record *rec = map_list->add_record (mmap_rec); + if (!rec) + { + fh->munmap (h, base, len); + return NULL; + } + return base; +} + +extern "C" void * +mmap64 (void *addr, size_t len, int prot, int flags, int fd, _off64_t off) +{ + syscall_printf ("addr %x, len %u, prot %x, flags %x, fd %d, off %D", + addr, len, prot, flags, fd, off); + + caddr_t ret = (caddr_t) MAP_FAILED; + fhandler_base *fh = NULL; + list *map_list = NULL; + size_t orig_len = 0; + caddr_t base = NULL; + + DWORD pagesize = getpagesize (); + + fh_anonymous.set_io_handle (INVALID_HANDLE_VALUE); + fh_anonymous.set_access (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); + fh_disk_file.set_io_handle (NULL); + + SetResourceLock (LOCK_MMAP_LIST, READ_LOCK | WRITE_LOCK, "mmap"); + + /* EINVAL error conditions. Note that the addr%pagesize test is deferred + to workaround a serious alignment problem in Windows 98. */ + if (off % pagesize + || ((prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))) + || ((flags & MAP_TYPE) != MAP_SHARED + && (flags & MAP_TYPE) != MAP_PRIVATE) +#if 0 + || (fixed (flags) && ((uintptr_t) addr % pagesize)) +#endif + || !len) + { + set_errno (EINVAL); + goto out; + } + + /* There's a serious alignment problem in Windows 98. MapViewOfFile + sometimes returns addresses which are page aligned instead of + granularity aligned. OTOH, it's not possible to force such an + address using MapViewOfFileEx. So what we do here to let it work + at least most of the time is, allow 4K aligned addresses in 98, + to enable remapping of formerly mapped pages. If no matching + free pages exist, check addr again, this time for the real alignment. */ + DWORD checkpagesize = wincap.has_mmap_alignment_bug () ? + getsystempagesize () : pagesize; + if (fixed (flags) && ((uintptr_t) addr % checkpagesize)) + { + set_errno (EINVAL); + goto out; + } + + if (!anonymous (flags) && fd != -1) + { + /* Ensure that fd is open */ + cygheap_fdget cfd (fd); + if (cfd < 0) + goto out; + + fh = cfd; + + /* mmap /dev/zero is like MAP_ANONYMOUS. */ + if (fh->get_device () == FH_ZERO) + flags |= MAP_ANONYMOUS; + } + if (anonymous (flags) || fd == -1) + { + fh = &fh_anonymous; + fd = -1; + flags |= MAP_ANONYMOUS; + /* Anonymous mappings are always forced to pagesize length with + no offset. */ + len = roundup2 (len, pagesize); + off = 0; + } + else if (fh->get_device () == FH_FS) + { + /* EACCES error conditions according to SUSv3. File must be opened + for reading, regardless of the requested protection, and file must + be opened for writing when PROT_WRITE together with MAP_SHARED + is requested. */ + if (!(fh->get_access () & GENERIC_READ) + || (!(fh->get_access () & GENERIC_WRITE) + && (prot & PROT_WRITE) && !priv (flags))) + { + set_errno (EACCES); + goto out; + } + + /* On 9x you can't create mappings with PAGE_WRITECOPY protection if + the file isn't explicitely opened with WRITE access. */ + if (!wincap.is_winnt () && priv (flags) + && !(fh->get_access () & GENERIC_WRITE)) + { + HANDLE h = CreateFile (fh->get_win32_name (), + fh->get_access () | GENERIC_WRITE, + wincap.shared (), &sec_none_nih, + OPEN_EXISTING, 0, NULL); + if (h == INVALID_HANDLE_VALUE) + { + set_errno (EACCES); + goto out; + } + fh_disk_file.set_io_handle (h); + fh_disk_file.set_access (fh->get_access () | GENERIC_WRITE); + path_conv pc; + pc.set_name (fh->get_win32_name (), ""); + fh_disk_file.set_name (pc); + fh = &fh_disk_file; + } + + /* On NT you can't create mappings with PAGE_EXECUTE protection if + the file isn't explicitely opened with EXECUTE access. */ + if (wincap.is_winnt ()) + { + HANDLE h = CreateFile (fh->get_win32_name (), + fh->get_access () | GENERIC_EXECUTE, + wincap.shared (), &sec_none_nih, + OPEN_EXISTING, 0, NULL); + if (h != INVALID_HANDLE_VALUE) + { + fh_disk_file.set_io_handle (h); + fh_disk_file.set_access (fh->get_access () | GENERIC_EXECUTE); + fh = &fh_disk_file; + } + else if (prot & PROT_EXEC) + { + /* TODO: To be or not to be... I'm opting for refusing this + mmap request rather than faking it, but that might break + some non-portable code. */ + set_errno (EACCES); + goto out; + } + } + + DWORD high; + DWORD low = GetFileSize (fh->get_handle (), &high); + _off64_t fsiz = ((_off64_t)high << 32) + low; + + /* Don't allow file mappings beginning beyond EOF since Windows can't + handle that POSIX like, unless MAP_AUTOGROW flag is set, which + mimics Windows behaviour. */ + if (off >= fsiz && !autogrow (flags)) + { + /* Instead, it seems suitable to return an anonymous mapping of + the given size instead. Mapped addresses beyond EOF aren't + written back to the file anyway, so the handling is identical + to other pages beyond EOF. */ + fh = &fh_anonymous; + len = roundup2 (len, pagesize); + prot = PROT_READ | PROT_WRITE | __PROT_ATTACH; + flags &= MAP_FIXED; + flags |= MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; + fd = -1; + off = 0; + goto go_ahead; + } + fsiz -= off; + /* On NT systems we're creating the pages beyond EOF as reserved, + anonymous pages. That's not possible on 9x for two reasons. + It neither allows to create reserved pages in the shared memory + area, nor does it allow to create page aligend mappings (in + contrast to granularity aligned mappings). + + Note that this isn't done in WOW64 environments since apparently + WOW64 does not support the AT_ROUND_TO_PAGE flag which is required + to get this right. Too bad. */ + if (wincap.virtual_protect_works_on_shared_pages () + && !wincap.is_wow64 () + && ((len > fsiz && !autogrow (flags)) + || len < pagesize)) + orig_len = len; + if (len > fsiz) + { + if (autogrow (flags)) + { + /* Allow mapping beyond EOF if MAP_AUTOGROW flag is set. + Check if file has been opened for writing, otherwise + MAP_AUTOGROW is invalid. */ + if (!(fh->get_access () & GENERIC_WRITE)) + { + set_errno (EINVAL); + goto out; + } + } + else + /* Otherwise, don't map beyond EOF, since Windows would change + the file to the new length, in contrast to POSIX. */ + len = fsiz; + } + + /* If the requested offset + len is <= file size, drop MAP_AUTOGROW. + This simplifes fhandler::mmap's job. */ + if (autogrow (flags) && (off + len) <= fsiz) + flags &= ~MAP_AUTOGROW; + } + +go_ahead: + + map_list = mmapped_areas.get_list_by_fd (fd); + + /* Test if an existing anonymous mapping can be recycled. */ + if (map_list && anonymous (flags)) + { + caddr_t tried = map_list->try_map (addr, len, flags, off); + /* try_map returns NULL if no map matched, otherwise it returns + a valid address, of MAP_FAILED in case of a fatal error. */ + if (tried) + { + ret = tried; + goto out; + } + } + + /* Deferred alignment test, see above. */ + if (wincap.has_mmap_alignment_bug () + && fixed (flags) && ((uintptr_t) addr % pagesize)) + { + set_errno (EINVAL); + goto out; + } + + base = mmap_worker (fh, (caddr_t) addr, len, prot, flags, fd, off); + if (!base) + goto out; + + if (orig_len) + { + /* If the requested length is bigger than the file size, the + remainder is created as anonymous mapping. Actually two + mappings are created, first the reminder from the file end to + the next 64K boundary as accessible pages with the same + protection as the file's pages, then as much pages as necessary + to accomodate the requested length, but as reserved pages which + raise a SIGBUS when trying to access them. AT_ROUND_TO_PAGE + and page protection on shared pages is only supported by 32 bit NT, + so don't even try on 9x and in WOW64. This is accomplished by not + setting orig_len on 9x and in WOW64 above. */ + orig_len = roundup2 (orig_len, pagesize); + len = roundup2 (len, getsystempagesize ()); + if (orig_len - len) + { + orig_len -= len; + size_t valid_page_len = orig_len % pagesize; + size_t sigbus_page_len = orig_len - valid_page_len; + + caddr_t at_base = base + len; + if (valid_page_len) + { + prot |= __PROT_FILLER; + flags &= MAP_SHARED | MAP_PRIVATE; + flags |= MAP_ANONYMOUS | MAP_FIXED; + at_base = mmap_worker (&fh_anonymous, at_base, valid_page_len, + prot, flags, -1, 0); + if (!at_base) + { + fh->munmap (fh->get_handle (), base, len); + set_errno (ENOMEM); + goto out; + } + at_base += valid_page_len; + } + if (sigbus_page_len) + { + prot = PROT_READ | PROT_WRITE | __PROT_ATTACH; + flags = MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED; + at_base = mmap_worker (&fh_anonymous, at_base, sigbus_page_len, + prot, flags, -1, 0); + if (!at_base) + debug_printf ("Warning: Mapping beyond EOF failed, %E"); + } + } + } + + ret = base; + +out: + + ReleaseResourceLock (LOCK_MMAP_LIST, READ_LOCK | WRITE_LOCK, "mmap"); + + if (fh_disk_file.get_handle ()) + CloseHandle (fh_disk_file.get_handle ()); + + syscall_printf ("%p = mmap() ", ret); + return ret; +} + +extern "C" void * +mmap (void *addr, size_t len, int prot, int flags, int fd, _off_t off) +{ + return mmap64 (addr, len, prot, flags, fd, (_off64_t)off); +} + +/* munmap () removes all mmapped pages between addr and addr+len. */ + +extern "C" int +munmap (void *addr, size_t len) +{ + syscall_printf ("munmap (addr %x, len %u)", addr, len); + + /* Error conditions according to SUSv3 */ + if (!addr || !len || check_invalid_virtual_addr (addr, len)) + { + set_errno (EINVAL); + return -1; + } + /* See comment in mmap64 for a description. */ + size_t pagesize = wincap.has_mmap_alignment_bug () ? + getsystempagesize () : getpagesize (); + if (((uintptr_t) addr % pagesize) || !len) + { + set_errno (EINVAL); + return -1; + } + len = roundup2 (len, pagesize); + + SetResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "munmap"); + + /* Iterate through the map, unmap pages between addr and addr+len + in all maps. */ + list *map_list; + for (unsigned list_idx = 0; + (map_list = mmapped_areas.get_list (list_idx)); + ++list_idx) + { + long record_idx = -1; + caddr_t u_addr; + DWORD u_len; + + while ((record_idx = map_list->search_record((caddr_t)addr, len, u_addr, + u_len, record_idx)) >= 0) + { + mmap_record *rec = map_list->get_record (record_idx); + if (rec->unmap_pages (u_addr, u_len)) + { + /* The whole record has been unmapped, so we now actually + unmap it from the system in full length... */ + fhandler_base *fh = rec->alloc_fh (); + fh->munmap (rec->get_handle (), + rec->get_address (), + rec->get_len ()); + rec->free_fh (fh); + + /* ...and delete the record. */ + if (map_list->del_record (record_idx--)) + { + /* Yay, the last record has been removed from the list, + we can remove the list now, too. */ + mmapped_areas.del_list (list_idx--); + break; + } + } + } + } + + ReleaseResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "munmap"); + syscall_printf ("0 = munmap(): %x", addr); + return 0; +} + +/* Sync file with memory. Ignore flags for now. */ + +extern "C" int +msync (void *addr, size_t len, int flags) +{ + int ret = -1; + list *map_list; + + syscall_printf ("msync (addr: %p, len %u, flags %x)", addr, len, flags); + + SetResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "msync"); + + /* See comment in mmap64 for a description. */ + size_t pagesize = wincap.has_mmap_alignment_bug () ? + getsystempagesize () : getpagesize (); + if (((uintptr_t) addr % pagesize) + || (flags & ~(MS_ASYNC | MS_SYNC | MS_INVALIDATE)) + || (flags & (MS_ASYNC | MS_SYNC) == (MS_ASYNC | MS_SYNC))) + { + set_errno (EINVAL); + goto out; + } + len = roundup2 (len, pagesize); + + /* Iterate through the map, looking for the mmapped area. + Error if not found. */ + for (unsigned list_idx = 0; + (map_list = mmapped_areas.get_list (list_idx)); + ++list_idx) + { + mmap_record *rec; + for (int record_idx = 0; + (rec = map_list->get_record (record_idx)); + ++record_idx) + { + if (rec->access ((caddr_t)addr)) + { + /* Check whole area given by len. */ + for (DWORD i = getpagesize (); i < len; ++i) + if (!rec->access ((caddr_t)addr + i)) + { + set_errno (ENOMEM); + goto out; + } + fhandler_base *fh = rec->alloc_fh (); + ret = fh->msync (rec->get_handle (), (caddr_t)addr, len, flags); + rec->free_fh (fh); + goto out; + } + } + } + + /* No matching mapping exists. */ + set_errno (ENOMEM); + +out: + syscall_printf ("%d = msync()", ret); + ReleaseResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "msync"); + return ret; +} + +/* Set memory protection */ + +extern "C" int +mprotect (void *addr, size_t len, int prot) +{ + bool in_mapped = false; + bool ret = false; + DWORD old_prot; + DWORD new_prot = 0; + + syscall_printf ("mprotect (addr: %p, len %u, prot %x)", addr, len, prot); + + /* See comment in mmap64 for a description. */ + size_t pagesize = wincap.has_mmap_alignment_bug () ? + getsystempagesize () : getpagesize (); + if ((uintptr_t) addr % pagesize) + { + set_errno (EINVAL); + goto out; + } + len = roundup2 (len, pagesize); + + SetResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "mprotect"); + + /* Iterate through the map, protect pages between addr and addr+len + in all maps. */ + list *map_list; + for (unsigned list_idx = 0; + (map_list = mmapped_areas.get_list (list_idx)); + ++list_idx) + { + long record_idx = -1; + caddr_t u_addr; + DWORD u_len; + + while ((record_idx = map_list->search_record((caddr_t)addr, len, + u_addr, u_len, + record_idx)) >= 0) + { + mmap_record *rec = map_list->get_record (record_idx); + in_mapped = true; + if (rec->attached ()) + continue; + new_prot = gen_protect (prot, rec->get_flags ()); + if (rec->anonymous () && rec->priv () && rec->noreserve ()) + { + if (new_prot == PAGE_NOACCESS) + ret = VirtualFree (u_addr, u_len, MEM_DECOMMIT); + else + ret = !!VirtualAlloc (u_addr, u_len, MEM_COMMIT, new_prot); + } + else + ret = VirtualProtect (u_addr, u_len, new_prot, &old_prot); + if (!ret) + { + __seterrno (); + break; + } + } + } + + ReleaseResourceLock (LOCK_MMAP_LIST, WRITE_LOCK | READ_LOCK, "mprotect"); + + if (!in_mapped) + { + int flags = 0; + MEMORY_BASIC_INFORMATION mbi; + + ret = VirtualQuery (addr, &mbi, sizeof mbi); + if (ret) + { + /* If write protection is requested, check if the page was + originally protected writecopy. In this case call VirtualProtect + requesting PAGE_WRITECOPY, otherwise the VirtualProtect will fail + on NT version >= 5.0 */ + if (prot & PROT_WRITE) + { + if (mbi.AllocationProtect == PAGE_WRITECOPY + || mbi.AllocationProtect == PAGE_EXECUTE_WRITECOPY) + flags = MAP_PRIVATE; + } + new_prot = gen_protect (prot, flags); + if (new_prot != PAGE_NOACCESS && mbi.State == MEM_RESERVE) + ret = VirtualAlloc (addr, len, MEM_COMMIT, new_prot); + else + ret = VirtualProtect (addr, len, new_prot, &old_prot); + } + if (!ret) + __seterrno (); + } + +out: + + syscall_printf ("%d = mprotect ()", ret ? 0 : -1); + return ret ? 0 : -1; +} + +extern "C" int +mlock (const void *addr, size_t len) +{ + if (!wincap.has_working_virtual_lock ()) + return 0; + + int ret = -1; + + /* Instead of using VirtualLock, which does not guarantee that the pages + aren't swapped out when the process is inactive, we're using + ZwLockVirtualMemory with the LOCK_VM_IN_RAM flag to do what mlock on + POSIX systems does. On NT, this requires SeLockMemoryPrivilege, + which is given only to SYSTEM by default. */ + + push_thread_privilege (SE_LOCK_MEMORY_PRIV, true); + + /* Align address and length values to page size. */ + size_t pagesize = getpagesize (); + PVOID base = (PVOID) rounddown((uintptr_t) addr, pagesize); + ULONG size = roundup2 (((uintptr_t) addr - (uintptr_t) base) + len, pagesize); + NTSTATUS status = 0; + do + { + status = NtLockVirtualMemory (hMainProc, &base, &size, LOCK_VM_IN_RAM); + if (status == STATUS_WORKING_SET_QUOTA) + { + /* The working set is too small, try to increase it so that the + requested locking region fits in. Unfortunately I don't know + any function which would return the currently locked pages of + a process (no go with NtQueryVirtualMemory). + + So, except for the border cases, what we do here is something + really embarrassing. We raise the working set by 64K at a time + and retry, until either we fail to raise the working set size + further, or until NtLockVirtualMemory returns successfully (or + with another error). */ + ULONG min, max; + if (!GetProcessWorkingSetSize (hMainProc, &min, &max)) + { + set_errno (ENOMEM); + break; + } + if (min < size) + min = size + pagesize; + else if (size < pagesize) + min += size; + else + min += pagesize; + if (max < min) + max = min; + if (!SetProcessWorkingSetSize (hMainProc, min, max)) + { + set_errno (ENOMEM); + break; + } + } + else if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + else + ret = 0; + } + while (status == STATUS_WORKING_SET_QUOTA); + + pop_thread_privilege (); + + return ret; +} + +extern "C" int +munlock (const void *addr, size_t len) +{ + if (!wincap.has_working_virtual_lock ()) + return 0; + + int ret = -1; + + push_thread_privilege (SE_LOCK_MEMORY_PRIV, true); + + /* Align address and length values to page size. */ + size_t pagesize = getpagesize (); + PVOID base = (PVOID) rounddown((uintptr_t) addr, pagesize); + ULONG size = roundup2 (((uintptr_t) addr - (uintptr_t) base) + len, pagesize); + NTSTATUS status = NtUnlockVirtualMemory (hMainProc, &base, &size, + LOCK_VM_IN_RAM); + if (!NT_SUCCESS (status)) + __seterrno_from_nt_status (status); + else + ret = 0; + + pop_thread_privilege (); + + return ret; +} + +/* + * Base implementation: + * + * `mmap' returns ENODEV as documented in SUSv2. + * In contrast to the global function implementation, the member function + * `mmap' has to return the mapped base address in `addr' and the handle to + * the mapping object as return value. In case of failure, the fhandler + * mmap has to close that handle by itself and return INVALID_HANDLE_VALUE. + * + * `munmap' and `msync' get the handle to the mapping object as first parameter + * additionally. +*/ +HANDLE +fhandler_base::mmap (caddr_t *addr, size_t len, int prot, + int flags, _off64_t off) +{ + set_errno (ENODEV); + return INVALID_HANDLE_VALUE; +} + +int +fhandler_base::munmap (HANDLE h, caddr_t addr, size_t len) +{ + set_errno (ENODEV); + return -1; +} + +int +fhandler_base::msync (HANDLE h, caddr_t addr, size_t len, int flags) +{ + set_errno (ENODEV); + return -1; +} + +bool +fhandler_base::fixup_mmap_after_fork (HANDLE h, int prot, int flags, + _off64_t offset, DWORD size, + void *address) +{ + set_errno (ENODEV); + return -1; +} + +/* Implementation for anonymous maps. Using fhandler_dev_zero looks + quite the natural way. */ +HANDLE +fhandler_dev_zero::mmap (caddr_t *addr, size_t len, int prot, + int flags, _off64_t off) +{ + HANDLE h; + void *base; + + if (priv (flags) && !filler (prot)) + { + /* Private anonymous maps are now implemented using VirtualAlloc. + This has two advantages: + + - VirtualAlloc has a smaller footprint than a copy-on-write + anonymous map. + + - It supports decommitting using VirtualFree, in contrast to + section maps. This allows minimum footprint private maps, + when using the (non-POSIX, yay-Linux) MAP_NORESERVE flag. + */ + DWORD protect = gen_protect (prot, flags); + DWORD alloc_type = MEM_RESERVE | (noreserve (flags) ? 0 : MEM_COMMIT); + base = VirtualAlloc (*addr, len, alloc_type, protect); + if (!base && addr && !fixed (flags)) + base = VirtualAlloc (NULL, len, alloc_type, protect); + if (!base || (fixed (flags) && base != *addr)) + { + if (!base) + __seterrno (); + else + { + VirtualFree (base, 0, MEM_RELEASE); + set_errno (EINVAL); + debug_printf ("VirtualAlloc: address shift with MAP_FIXED given"); + } + return INVALID_HANDLE_VALUE; + } + h = (HANDLE) 1; /* Fake handle to indicate success. */ + } + else + { + h = mmap_func->CreateMapping (get_handle (), len, off, get_access (), + prot, flags, get_win32_name ()); + if (!h) + { + __seterrno (); + debug_printf ("CreateMapping failed with %E"); + return INVALID_HANDLE_VALUE; + } + + base = mmap_func->MapView (h, *addr, len, get_access(), prot, flags, off); + if (!base || (fixed (flags) && base != *addr)) + { + if (!base) + __seterrno (); + else + { + UnmapViewOfFile (base); + set_errno (EINVAL); + debug_printf ("MapView: address shift with MAP_FIXED given"); + } + CloseHandle (h); + return INVALID_HANDLE_VALUE; + } + } + *addr = (caddr_t) base; + return h; +} + +int +fhandler_dev_zero::munmap (HANDLE h, caddr_t addr, size_t len) +{ + if (h == (HANDLE) 1) /* See fhandler_dev_zero::mmap. */ + VirtualFree (addr, 0, MEM_RELEASE); + else + { + UnmapViewOfFile (addr); + CloseHandle (h); + } + return 0; +} + +int +fhandler_dev_zero::msync (HANDLE h, caddr_t addr, size_t len, int flags) +{ + return 0; +} + +bool +fhandler_dev_zero::fixup_mmap_after_fork (HANDLE h, int prot, int flags, + _off64_t offset, DWORD size, + void *address) +{ + /* Re-create the map */ + void *base; + if (priv (flags) && !filler (prot)) + { + DWORD alloc_type = MEM_RESERVE | (noreserve (flags) ? 0 : MEM_COMMIT); + /* Always allocate R/W so that ReadProcessMemory doesn't fail + due to a non-writable target address. The protection is + set to the correct one anyway in the fixup loop. */ + base = VirtualAlloc (address, size, alloc_type, PAGE_READWRITE); + } + else + base = mmap_func->MapView (h, address, size, get_access (), + prot, flags, offset); + if (base != address) + { + MEMORY_BASIC_INFORMATION m; + VirtualQuery (address, &m, sizeof (m)); + system_printf ("requested %p != %p mem alloc base %p, state %p, " + "size %d, %E", address, base, m.AllocationBase, m.State, + m.RegionSize); + } + return base == address; +} + +/* Implementation for disk files and anonymous mappings. */ +HANDLE +fhandler_disk_file::mmap (caddr_t *addr, size_t len, int prot, + int flags, _off64_t off) +{ + HANDLE h = mmap_func->CreateMapping (get_handle (), len, off, get_access (), + prot, flags, get_win32_name ()); + if (!h) + { + __seterrno (); + debug_printf ("CreateMapping failed with %E"); + return INVALID_HANDLE_VALUE; + } + + void *base = mmap_func->MapView (h, *addr, len, get_access (), + prot, flags, off); + if (!base || (fixed (flags) && base != *addr)) + { + if (!base) + __seterrno (); + else + { + UnmapViewOfFile (base); + set_errno (EINVAL); + debug_printf ("MapView: address shift with MAP_FIXED given"); + } + CloseHandle (h); + return INVALID_HANDLE_VALUE; + } + + *addr = (caddr_t) base; + return h; +} + +int +fhandler_disk_file::munmap (HANDLE h, caddr_t addr, size_t len) +{ + UnmapViewOfFile (addr); + CloseHandle (h); + return 0; +} + +int +fhandler_disk_file::msync (HANDLE h, caddr_t addr, size_t len, int flags) +{ + if (FlushViewOfFile (addr, len) == 0) + { + __seterrno (); + return -1; + } + return 0; +} + +bool +fhandler_disk_file::fixup_mmap_after_fork (HANDLE h, int prot, int flags, + _off64_t offset, DWORD size, + void *address) +{ + /* Re-create the map */ + void *base = mmap_func->MapView (h, address, size, get_access (), + prot, flags, offset); + if (base != address) + { + MEMORY_BASIC_INFORMATION m; + VirtualQuery (address, &m, sizeof (m)); + system_printf ("requested %p != %p mem alloc base %p, state %p, " + "size %d, %E", address, base, m.AllocationBase, m.State, + m.RegionSize); + } + return base == address; +} + +HANDLE +fhandler_dev_mem::mmap (caddr_t *addr, size_t len, int prot, + int flags, _off64_t off) +{ + if (off >= mem_size + || (DWORD) len >= mem_size + || off + len >= mem_size) + { + set_errno (EINVAL); + debug_printf ("-1 = mmap(): illegal parameter, set EINVAL"); + return INVALID_HANDLE_VALUE; + } + + UNICODE_STRING memstr; + RtlInitUnicodeString (&memstr, L"\\device\\physicalmemory"); + + OBJECT_ATTRIBUTES attr; + InitializeObjectAttributes (&attr, &memstr, + OBJ_CASE_INSENSITIVE | OBJ_INHERIT, + NULL, NULL); + + /* Section access is bit-wise ored, while on the Win32 level access + is only one of the values. It's not quite clear if the section + access has to be defined this way, or if SECTION_ALL_ACCESS would + be sufficient but this worked fine so far, so why change? */ + ACCESS_MASK section_access; + if (prot & PROT_WRITE) + section_access = SECTION_MAP_READ | SECTION_MAP_WRITE; + else + section_access = SECTION_MAP_READ; + + HANDLE h; + NTSTATUS ret = NtOpenSection (&h, section_access, &attr); + if (!NT_SUCCESS (ret)) + { + __seterrno_from_nt_status (ret); + debug_printf ("-1 = mmap(): NtOpenSection failed with %E"); + return INVALID_HANDLE_VALUE; + } + + void *base = MapViewNT (h, *addr, len, get_access (), + prot, flags | MAP_ANONYMOUS, off); + if (!base || (fixed (flags) && base != *addr)) + { + if (!base) + __seterrno (); + else + { + NtUnmapViewOfSection (GetCurrentProcess (), base); + set_errno (EINVAL); + debug_printf ("MapView: address shift with MAP_FIXED given"); + } + CloseHandle (h); + return INVALID_HANDLE_VALUE; + } + + *addr = (caddr_t) base; + return h; +} + +int +fhandler_dev_mem::munmap (HANDLE h, caddr_t addr, size_t len) +{ + NTSTATUS ret; + if (!NT_SUCCESS (ret = NtUnmapViewOfSection (INVALID_HANDLE_VALUE, addr))) + { + __seterrno_from_nt_status (ret); + return -1; + } + CloseHandle (h); + return 0; +} + +int +fhandler_dev_mem::msync (HANDLE h, caddr_t addr, size_t len, int flags) +{ + return 0; +} + +bool +fhandler_dev_mem::fixup_mmap_after_fork (HANDLE h, int prot, int flags, + _off64_t offset, DWORD size, + void *address) +{ + void *base = MapViewNT (h, address, size, get_access (), prot, + flags | MAP_ANONYMOUS, offset); + if (base != address) + { + MEMORY_BASIC_INFORMATION m; + VirtualQuery (address, &m, sizeof (m)); + system_printf ("requested %p != %p mem alloc base %p, state %p, " + "size %d, %E", address, base, m.AllocationBase, m.State, + m.RegionSize); + } + return base == address; +} + +/* Call to re-create all the file mappings in a forked child. Called from + the child in initialization. At this point we are passed a valid + mmapped_areas map, and all the HANDLE's are valid for the child, but + none of the mapped areas are in our address space. We need to iterate + through the map, doing the MapViewOfFile calls. */ + +int __stdcall +fixup_mmaps_after_fork (HANDLE parent) +{ + /* Iterate through the map */ + list *map_list; + for (unsigned list_idx = 0; + (map_list = mmapped_areas.get_list (list_idx)); + ++list_idx) + { + mmap_record *rec; + for (int record_idx = 0; + (rec = map_list->get_record (record_idx)); + ++record_idx) + { + debug_printf ("fd %d, h 0x%x, address %p, len 0x%x, prot: 0x%x, " + "flags: 0x%x, offset %X", + rec->get_fd (), rec->get_handle (), rec->get_address (), + rec->get_len (), rec->get_prot (), rec->get_flags (), + rec->get_offset ()); + + fhandler_base *fh = rec->alloc_fh (); + bool ret = fh->fixup_mmap_after_fork (rec->get_handle (), + rec->get_prot (), + rec->get_flags () | MAP_FIXED, + rec->get_offset (), + rec->get_len (), + rec->get_address ()); + rec->free_fh (fh); + + if (!ret) + { + if (rec->attached ()) + { + system_printf ("Warning: Fixup mapping beyond EOF failed"); + continue; + } + return -1; + } + + MEMORY_BASIC_INFORMATION mbi; + DWORD old_prot; + + for (char *address = rec->get_address (); + address < rec->get_address () + rec->get_len (); + address += mbi.RegionSize) + { + if (!VirtualQueryEx (parent, address, &mbi, sizeof mbi)) + { + system_printf ("VirtualQueryEx failed for MAP_PRIVATE " + "address %p, %E", address); + return -1; + } + /* Just skip reserved pages. */ + if (mbi.State == MEM_RESERVE) + continue; + /* Copy-on-write pages must be copied to the child to circumvent + a strange notion how copy-on-write is supposed to work. */ + if (rec->priv ()) + { + if (rec->anonymous () && rec->noreserve () + && !VirtualAlloc (address, mbi.RegionSize, + MEM_COMMIT, PAGE_READWRITE)) + { + system_printf ("VirtualAlloc failed for MAP_PRIVATE " + "address %p, %E", address); + return -1; + } + if (mbi.Protect == PAGE_NOACCESS + && !VirtualProtectEx (parent, address, mbi.RegionSize, + PAGE_READONLY, &old_prot)) + { + system_printf ("VirtualProtectEx failed for MAP_PRIVATE " + "address %p, %E", address); + return -1; + } + else if ((mbi.AllocationProtect == PAGE_WRITECOPY + || mbi.AllocationProtect == PAGE_EXECUTE_WRITECOPY) + && (mbi.Protect == PAGE_READWRITE + || mbi.Protect == PAGE_EXECUTE_READWRITE)) + /* A WRITECOPY page which has been written to is set to + READWRITE, but that's an incompatible protection to + set the page to. Convert the protection to WRITECOPY + so that the below VirtualProtect doesn't fail. */ + mbi.Protect <<= 1; + + if (!ReadProcessMemory (parent, address, address, + mbi.RegionSize, NULL)) + { + system_printf ("ReadProcessMemory failed for MAP_PRIVATE " + "address %p, %E", address); + return -1; + } + if (mbi.Protect == PAGE_NOACCESS + && !VirtualProtectEx (parent, address, mbi.RegionSize, + PAGE_NOACCESS, &old_prot)) + { + system_printf ("WARNING: VirtualProtectEx to return to " + "PAGE_NOACCESS state in parent failed for " + "MAP_PRIVATE address %p, %E", address); + return -1; + } + } + /* Set child page protection to parent protection */ + if (!VirtualProtect (address, mbi.RegionSize, + mbi.Protect, &old_prot)) + { + MEMORY_BASIC_INFORMATION m; + VirtualQuery (address, &m, sizeof m); + system_printf ("VirtualProtect failed for " + "address %p, " + "parentstate: 0x%x, " + "state: 0x%x, " + "parentprot: 0x%x, " + "prot: 0x%x, %E", + address, mbi.State, m.State, + mbi.Protect, m.Protect); + return -1; + } + } + } + } + + debug_printf ("succeeded"); + return 0; +} diff --git a/winsup/cygwin/winsup.h b/winsup/cygwin/winsup.h new file mode 100644 index 00000000000..727151406d8 --- /dev/null +++ b/winsup/cygwin/winsup.h @@ -0,0 +1,362 @@ +/* winsup.h: main Cygwin header file. + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, + 2005, 2006 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. */ + +#ifdef DEBUGIT +#define spf(a, b, c) small_printf (a, b, c) +#else +#define spf(a, b, c) do {} while (0) +#endif + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define __INSIDE_CYGWIN__ + +#define strlen __builtin_strlen +#define strcmp __builtin_strcmp +#define strcpy __builtin_strcpy +#define memcpy __builtin_memcpy +#define memcmp __builtin_memcmp +#ifdef HAVE_BUILTIN_MEMSET +# define memset __builtin_memset +#endif + +#define NO_COPY __attribute__((nocommon)) __attribute__((section(".data_cygwin_nocopy"))) +#define NO_COPY_INIT __attribute__((section(".data_cygwin_nocopy"))) + +#define EXPORT_ALIAS(sym,symalias) extern "C" __typeof (sym) symalias __attribute__ ((alias(#sym))); + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#include <sys/types.h> +#include <sys/strace.h> + +/* Declarations for functions used in C and C++ code. */ +#ifdef __cplusplus +extern "C" { +#endif +extern __uid32_t getuid32 (void); +extern __uid32_t geteuid32 (void); +extern int seteuid32 (__uid32_t); +extern __gid32_t getegid32 (void); +extern struct passwd *getpwuid32 (__uid32_t); +extern struct passwd *getpwnam (const char *); +extern struct __sFILE64 *fopen64 (const char *, const char *); +extern struct hostent *cygwin_gethostbyname (const char *name); +extern unsigned long cygwin_inet_addr (const char *cp); + +#ifdef __cplusplus +} +#endif + +/* Note that MAX_PATH is defined in the windows headers */ +/* There is also PATH_MAX and MAXPATHLEN. + PATH_MAX is from Posix and does *not* include the trailing NUL. + MAXPATHLEN is from Unix. + + Thou shalt use CYG_MAX_PATH throughout. It avoids the NUL vs no-NUL + issue and is neither of the Unixy ones [so we can punt on which + one is the right one to use]. + + Windows ANSI calls are limited to MAX_PATH in length. Cygwin calls that + thunk through to Windows Wide calls are limited to 32K. We define + CYG_MAX_PATH as a convenient, not to short, not too long 'happy medium'. + + */ + +#define CYG_MAX_PATH (MAX_PATH) + +#ifdef __cplusplus + +extern const char case_folded_lower[]; +#define cyg_tolower(c) (case_folded_lower[(unsigned char)(c)]) +extern const char case_folded_upper[]; +#define cyg_toupper(c) (case_folded_upper[(unsigned char)(c)]) + +#ifndef MALLOC_DEBUG +#define cfree newlib_cfree_dont_use +#endif + +#define WIN32_LEAN_AND_MEAN 1 +#define _WINGDI_H +#define _WINUSER_H +#define _WINNLS_H +#define _WINVER_H +#define _WINNETWK_H +#define _WINSVC_H +#include <windows.h> +#include <wincrypt.h> +#include <lmcons.h> +#undef _WINGDI_H +#undef _WINUSER_H +#undef _WINNLS_H +#undef _WINVER_H +#undef _WINNETWK_H +#undef _WINSVC_H + +#include "wincap.h" + +/* The one function we use from winuser.h most of the time */ +extern "C" DWORD WINAPI GetLastError (void); + +enum codepage_type {ansi_cp, oem_cp}; +extern codepage_type current_codepage; + +UINT get_cp (); + +int __stdcall sys_wcstombs(char *, int, const WCHAR *, int = -1) + __attribute__ ((regparm(3))); + +int __stdcall sys_mbstowcs(WCHAR *, const char *, int) + __attribute__ ((regparm(3))); + +/* Used to check if Cygwin DLL is dynamically loaded. */ +extern int dynamically_loaded; + +extern int cygserver_running; + +#define _MT_SAFE // DELTEME someday + +#define TITLESIZE 1024 + +#include "debug.h" + +/* Events/mutexes */ +extern HANDLE tty_mutex; + +/**************************** Convenience ******************************/ + +/* Used to define status flag accessor methods */ +#define IMPLEMENT_STATUS_FLAG(type,flag) \ + void flag (type val) { status.flag = (val); } \ + type flag () const { return (type) status.flag; } + +/* Used when treating / and \ as equivalent. */ +#define isdirsep(ch) \ + ({ \ + char __c = (ch); \ + ((__c) == '/' || (__c) == '\\'); \ + }) + +/* Convert a signal to a signal mask */ +#define SIGTOMASK(sig) (1 << ((sig) - signal_shift_subtract)) +extern unsigned int signal_shift_subtract; + +extern int __api_fatal_exit_val; +#define set_api_fatal_return(n) do {extern int __api_fatal_exit_val; __api_fatal_exit_val = (n);} while (0) +#define api_fatal(fmt, args...) __api_fatal ("%P: *** " fmt,## args) + +#undef issep +#define issep(ch) (strchr (" \t\n\r", (ch)) != NULL) + +#define isabspath(p) \ + (isdirsep (*(p)) || (isalpha (*(p)) && (p)[1] == ':' && (!(p)[2] || isdirsep ((p)[2])))) + +/******************** Initialization/Termination **********************/ + +class per_process; +/* cygwin .dll initialization */ +void dll_crt0 (per_process *) __asm__ ("_dll_crt0__FP11per_process"); +extern "C" void __stdcall _dll_crt0 (); +extern void dll_crt0_1 (void *); +extern void dll_dllcrt0_1 (void *); + +/* dynamically loaded dll initialization */ +extern "C" int dll_dllcrt0 (HMODULE, per_process *); + +/* dynamically loaded dll initialization for non-cygwin apps */ +extern "C" int dll_noncygwin_dllcrt0 (HMODULE, per_process *); + +/* exit the program */ + +enum exit_states + { + ES_NOT_EXITING = 0, + ES_SET_MUTO, + ES_GLOBAL_DTORS, + ES_EVENTS_TERMINATE, + ES_THREADTERM, + ES_SIGNAL, + ES_CLOSEALL, + ES_HUP_PGRP, + ES_HUP_SID, + ES_EXEC_EXIT, + ES_TITLE, + ES_TTY_TERMINATE, + ES_FINAL + }; + +extern exit_states exit_state; +void __stdcall do_exit (int) __attribute__ ((regparm (1), noreturn)); + +/* UID/GID */ +void uinfo_init (); + +#define ILLEGAL_UID16 ((__uid16_t)-1) +#define ILLEGAL_UID ((__uid32_t)-1) +#define ILLEGAL_GID16 ((__gid16_t)-1) +#define ILLEGAL_GID ((__gid32_t)-1) +#define ILLEGAL_SEEK ((_off64_t)-1) + +#define uid16touid32(u16) ((u16)==ILLEGAL_UID16?ILLEGAL_UID:(__uid32_t)(u16)) +#define gid16togid32(g16) ((g16)==ILLEGAL_GID16?ILLEGAL_GID:(__gid32_t)(g16)) + +/* Convert LARGE_INTEGER into long long */ +#define get_ll(pl) (((long long) (pl).HighPart << 32) | (pl).LowPart) + +/* various events */ +void events_init (); +void events_terminate (); + +void __stdcall close_all_files (bool = false); + +/* debug_on_trap support. see exceptions.cc:try_to_debug() */ +extern "C" void error_start_init (const char*); +extern "C" int try_to_debug (bool waitloop = 1); + +extern void ld_preload (); + +void set_file_api_mode (codepage_type); + +extern bool cygwin_finished_initializing; + +/**************************** Miscellaneous ******************************/ + +void __stdcall set_std_handle (int); +int __stdcall stat_dev (DWORD, int, unsigned long, struct __stat64 *); + +__ino64_t __stdcall hash_path_name (__ino64_t hash, const char *name) __attribute__ ((regparm(2))); +void __stdcall nofinalslash (const char *src, char *dst) __attribute__ ((regparm(2))); +extern "C" char *__stdcall rootdir (const char *full_path, char *root_path) __attribute__ ((regparm(2))); + +/* String manipulation */ +extern "C" char *__stdcall strccpy (char *s1, const char **s2, char c); +extern "C" int __stdcall strcasematch (const char *s1, const char *s2) __attribute__ ((regparm(2))); +extern "C" int __stdcall strncasematch (const char *s1, const char *s2, size_t n) __attribute__ ((regparm(3))); +extern "C" char *__stdcall strcasestr (const char *searchee, const char *lookfor) __attribute__ ((regparm(2))); + +void *hook_or_detect_cygwin (const char *, const void *, WORD&) __attribute__ ((regparm (3))); + +/* Time related */ +void __stdcall totimeval (struct timeval *, FILETIME *, int, int); +long __stdcall to_time_t (FILETIME *); +void __stdcall to_timestruc_t (FILETIME *, timestruc_t *); +void __stdcall time_as_timestruc_t (timestruc_t *); +void __stdcall timeval_to_filetime (const struct timeval *, FILETIME *); + +/* Console related */ +void __stdcall set_console_title (char *); +void init_console_handler (bool); + +void init_global_security (); + +int __stdcall check_invalid_virtual_addr (const void *s, unsigned sz) __attribute__ ((regparm(2))); + +ssize_t check_iovec (const struct iovec *, int, bool) __attribute__ ((regparm(3))); +#define check_iovec_for_read(a, b) check_iovec ((a), (b), false) +#define check_iovec_for_write(a, b) check_iovec ((a), (b), true) + +#define set_winsock_errno() __set_winsock_errno (__FUNCTION__, __LINE__) +void __set_winsock_errno (const char *fn, int ln) __attribute__ ((regparm(2))); + +extern bool wsock_started; + +/* Printf type functions */ +extern "C" void __api_fatal (const char *, ...) __attribute__ ((noreturn)); +extern "C" int __small_sprintf (char *dst, const char *fmt, ...) /*__attribute__ ((regparm (2)))*/; +extern "C" int __small_vsprintf (char *dst, const char *fmt, va_list ap) /*__attribute__ ((regparm (3)))*/; +extern void multiple_cygwin_problem (const char *, unsigned, unsigned); + +extern "C" void vklog (int priority, const char *message, va_list ap); +extern "C" void klog (int priority, const char *message, ...); +bool child_copy (HANDLE, bool, ...); + +int symlink_worker (const char *, const char *, bool, bool) + __attribute__ ((regparm (3))); + +class path_conv; + +int fcntl_worker (int fd, int cmd, void *arg); + +__ino64_t __stdcall readdir_get_ino (struct __DIR *dir, const char *path, bool dot_dot) __attribute__ ((regparm (3))); + +extern "C" int low_priority_sleep (DWORD) __attribute__ ((regparm (1))); +#define SLEEP_0_STAY_LOW INFINITE + +/* Returns the real page size, not the allocation size. */ +size_t getsystempagesize (); + +/* mmap functions. */ +void mmap_init (); +int mmap_is_attached_or_noreserve_page (ULONG_PTR addr); + +int winprio_to_nice (DWORD) __attribute__ ((regparm (1))); +DWORD nice_to_winprio (int &) __attribute__ ((regparm (1))); + +bool __stdcall create_pipe (PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD) + __attribute__ ((regparm (3))); +#define CreatePipe create_pipe + +inline bool flush_file_buffers (HANDLE h) +{ + return (GetFileType (h) != FILE_TYPE_PIPE) ? FlushFileBuffers (h) : true; +} +#define FlushFileBuffers flush_file_buffers + +/**************************** Exports ******************************/ + +extern "C" { +int cygwin_select (int , fd_set *, fd_set *, fd_set *, + struct timeval *to); +int cygwin_gethostname (char *__name, size_t __len); + +extern DWORD binmode; +extern char _data_start__, _data_end__, _bss_start__, _bss_end__; +extern void (*__CTOR_LIST__) (void); +extern void (*__DTOR_LIST__) (void); +extern SYSTEM_INFO system_info; +}; + +/*************************** Unsorted ******************************/ + +#define WM_ASYNCIO 0x8000 // WM_APP + + +#define STD_RBITS (S_IRUSR | S_IRGRP | S_IROTH) +#define STD_WBITS (S_IWUSR) +#define STD_XBITS (S_IXUSR | S_IXGRP | S_IXOTH) +#define NO_W ~(S_IWUSR | S_IWGRP | S_IWOTH) +#define NO_R ~(S_IRUSR | S_IRGRP | S_IROTH) +#define NO_X ~(S_IXUSR | S_IXGRP | S_IXOTH) + +/* The title on program start. */ +extern char *old_title; +extern bool display_title; +extern bool transparent_exe; + +extern bool in_forkee; +extern bool in_dllentry; + +extern HANDLE hMainThread; +extern HANDLE hMainProc; +extern HANDLE hProcToken; +extern HANDLE hProcImpToken; +extern HANDLE hExeced; +extern HMODULE cygwin_hmodule; + +extern bool cygwin_testing; + +extern char almost_null[]; + +#endif /* defined __cplusplus */ |