From 04a8ee061cd34bc98604483e6a76a40f03aa5bdc Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Thu, 6 Jul 2006 19:10:32 +0000 Subject: * Merge HEAD into cv-branch. --- winsup/cygwin/ChangeLog | 11 + winsup/cygwin/child_info.h | 142 +++++++++ winsup/cygwin/fork.cc | 714 +++++++++++++++++++++++++++++++++++++++++++++ winsup/cygwin/hookapi.cc | 237 +++++++++++++++ 4 files changed, 1104 insertions(+) create mode 100644 winsup/cygwin/child_info.h create mode 100644 winsup/cygwin/fork.cc create mode 100644 winsup/cygwin/hookapi.cc diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index f99f88bfdc2..45dfda92356 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,14 @@ +2006-07-06 Christopher Faylor + + * hookapi.cc: Add comment header + (putmem): Make static. + (get_export): Ditto. + (rvadelta): Ditto. Don't assume that a section which ends where the + import_rva begins is the import list. + + * child_info.h: Update copyright. + * fork.cc: Ditto. + 2006-07-06 Corinna Vinschen * include/cygwin/in6.h (struct in6_addr): Fix typo. diff --git a/winsup/cygwin/child_info.h b/winsup/cygwin/child_info.h new file mode 100644 index 00000000000..9f11015ddcb --- /dev/null +++ b/winsup/cygwin/child_info.h @@ -0,0 +1,142 @@ +/* child_info.h: shared child info for cygwin + + Copyright 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 + +enum child_info_types +{ + _PROC_EXEC, + _PROC_SPAWN, + _PROC_FORK, + _PROC_WHOOPS +}; + +enum child_status +{ + _CI_STRACED = 0x01, + _CI_ISCYGWIN = 0x02, + _CI_SAW_CTRL_C = 0x04 + +}; + +#define OPROC_MAGIC_MASK 0xff00ff00 +#define OPROC_MAGIC_GENERIC 0xaf00f000 + +#define PROC_MAGIC_GENERIC 0xaf00fa00 + +#define PROC_EXEC (_PROC_EXEC) +#define PROC_SPAWN (_PROC_SPAWN) +#define PROC_FORK (_PROC_FORK) + +#define EXEC_MAGIC_SIZE sizeof(child_info) + +/* Change this value if you get a message indicating that it is out-of-sync. */ +#define CURR_CHILD_INFO_MAGIC 0x704d1f7eU + +/* NOTE: Do not make gratuitous changes to the names or organization of the + below class. The layout is checksummed to determine compatibility between + different cygwin versions. */ +class child_info +{ +public: + DWORD zero[4]; // must be zeroed + DWORD cb; // size of this record + DWORD intro; // improbable string + unsigned long magic; // magic number unique to child_info + unsigned short type; // type of record, exec, spawn, fork + HANDLE subproc_ready; // used for synchronization with parent + HANDLE user_h; + HANDLE parent; + init_cygheap *cygheap; + void *cygheap_max; + DWORD cygheap_reserve_sz; + unsigned char flag; + unsigned fhandler_union_cb; + int retry; // number of times we've tried to start child process + DWORD exit_code; // process exit code + static int retry_count;// retry count; + child_info (unsigned, child_info_types, bool); + child_info (): subproc_ready (NULL), parent (NULL) {} + ~child_info (); + void ready (bool); + bool sync (int, HANDLE&, DWORD) __attribute__ ((regparm (3))); + DWORD proc_retry (HANDLE) __attribute__ ((regparm (2))); + bool isstraced () const {return !!(flag & _CI_STRACED);} + bool iscygwin () const {return !!(flag & _CI_ISCYGWIN);} + bool saw_ctrl_c () const {return !!(flag & _CI_SAW_CTRL_C);} + void set_saw_ctrl_c () {flag |= _CI_SAW_CTRL_C;} +}; + +class mount_info; +class _pinfo; + +class child_info_fork: public child_info +{ +public: + HANDLE forker_finished;// for synchronization with child + DWORD stacksize; // size of parent stack + jmp_buf jmp; // where child will jump to + void *stacktop; // location of top of parent stack + void *stackbottom; // location of bottom of parent stack + child_info_fork (); + void handle_fork () __attribute__ ((regparm (1)));; + bool handle_failure (DWORD) __attribute__ ((regparm (2))); + void alloc_stack (); + void alloc_stack_hard_way (volatile char *); +}; + +class fhandler_base; + +class cygheap_exec_info +{ +public: + char *old_title; + int argc; + char **argv; + int envc; + char **envp; + HANDLE myself_pinfo; +}; + +class child_info_spawn: public child_info +{ +public: + cygheap_exec_info *moreinfo; + + ~child_info_spawn () + { + if (moreinfo) + { + if (moreinfo->old_title) + cfree (moreinfo->old_title); + if (moreinfo->envp) + { + for (char **e = moreinfo->envp; *e; e++) + cfree (*e); + cfree (moreinfo->envp); + } + CloseHandle (moreinfo->myself_pinfo); + cfree (moreinfo); + } + } + child_info_spawn (): moreinfo (NULL) {}; + child_info_spawn (child_info_types, bool); + void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} + void set (child_info_types ci, bool b) { new (this) child_info_spawn (ci, b);} + void handle_spawn () __attribute__ ((regparm (1))); +}; + +void __stdcall init_child_info (DWORD, child_info *, HANDLE); + +extern "C" { +extern child_info *child_proc_info; +extern child_info_spawn *spawn_info asm ("_child_proc_info"); +extern child_info_fork *fork_info asm ("_child_proc_info"); +} diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc new file mode 100644 index 00000000000..20436291143 --- /dev/null +++ b/winsup/cygwin/fork.cc @@ -0,0 +1,714 @@ +/* fork.cc + + Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 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 +#include +#include +#include +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "sigproc.h" +#include "pinfo.h" +#include "cygheap.h" +#include "child_info.h" +#include "cygtls.h" +#include "perprocess.h" +#include "dll_init.h" +#include "sync.h" +#include "shared_info.h" +#include "cygmalloc.h" +#include "cygthread.h" + +#define NPIDS_HELD 4 + +/* Timeout to wait for child to start, parent to init child, etc. */ +/* FIXME: Once things stabilize, bump up to a few minutes. */ +#define FORK_WAIT_TIMEOUT (300 * 1000) /* 300 seconds */ + +class frok +{ + dll *first_dll; + bool load_dlls; + child_info_fork ch; + const char *error; + int child_pid; + int this_errno; + int __stdcall parent (void *esp); + int __stdcall child (void *esp); + friend int fork (); +}; + +static void +resume_child (HANDLE forker_finished) +{ + SetEvent (forker_finished); + debug_printf ("signalled child"); + return; +} + +/* Notify parent that it is time for the next step. */ +static void __stdcall +sync_with_parent (const char *s, bool hang_self) +{ + debug_printf ("signalling parent: %s", s); + fork_info->ready (false); + if (hang_self) + { + HANDLE h = fork_info->forker_finished; + /* Wait for the parent to fill in our stack and heap. + Don't wait forever here. If our parent dies we don't want to clog + the system. If the wait fails, we really can't continue so exit. */ + DWORD psync_rc = WaitForSingleObject (h, FORK_WAIT_TIMEOUT); + debug_printf ("awake"); + switch (psync_rc) + { + case WAIT_TIMEOUT: + api_fatal ("WFSO timed out %s", s); + break; + case WAIT_FAILED: + if (GetLastError () == ERROR_INVALID_HANDLE && + WaitForSingleObject (fork_info->forker_finished, 1) != WAIT_FAILED) + break; + api_fatal ("WFSO failed %s, fork_finished %p, %E", s, + fork_info->forker_finished); + break; + default: + debug_printf ("no problems"); + break; + } + } +} + +int __stdcall +frok::child (void *) +{ + HANDLE& hParent = ch.parent; + extern void fixup_hooks_after_fork (); + extern void fixup_timers_after_fork (); + debug_printf ("child is running. pid %d, ppid %d, stack here %p", + myself->pid, myself->ppid, __builtin_frame_address (0)); + + sync_with_parent ("after longjmp", true); + sigproc_printf ("hParent %p, child 1 first_dll %p, load_dlls %d", hParent, + first_dll, load_dlls); + + /* If we've played with the stack, stacksize != 0. That means that + fork() was invoked from other than the main thread. Make sure that + the threadinfo information is properly set up. */ + if (fork_info->stacksize) + { + _main_tls = &_my_tls; + _main_tls->init_thread (NULL, NULL); + _main_tls->local_clib = *_impure_ptr; + _impure_ptr = &_main_tls->local_clib; + } + + if (wincap.has_security ()) + { + set_cygwin_privileges (hProcImpToken); + cygheap->user.reimpersonate (); + } + +#ifdef DEBUGGING + char c; + if (GetEnvironmentVariable ("FORKDEBUG", &c, 1)) + try_to_debug (); + char buf[80]; + /* This is useful for debugging fork problems. Use gdb to attach to + the pid reported here. */ + if (GetEnvironmentVariable ("CYGWIN_FORK_SLEEP", buf, sizeof (buf))) + { + small_printf ("Sleeping %d after fork, pid %u\n", atoi (buf), GetCurrentProcessId ()); + Sleep (atoi (buf)); + } +#endif + + set_file_api_mode (current_codepage); + + MALLOC_CHECK; + +#ifdef USE_SERVER + /* Incredible but true: If we use sockets and SYSV IPC shared memory, + there's a good chance that a duplicated socket in the child occupies + memory which is needed to duplicate shared memory from the parent + process, if the shared memory hasn't been duplicated already. + The same goes very likely for "normal" mmap shared memory, too, but + with SYSV IPC it was the first time observed. So, *never* fixup + fdtab before fixing up shared memory. */ + if (fixup_shms_after_fork ()) + api_fatal ("recreate_shm areas after fork failed"); +#endif + + MALLOC_CHECK; + + /* If we haven't dynamically loaded any dlls, just signal + the parent. Otherwise, load all the dlls, tell the parent + that we're done, and wait for the parent to fill in the. + loaded dlls' data/bss. */ + if (!load_dlls) + { + cygheap->fdtab.fixup_after_fork (hParent); + sync_with_parent ("performed fork fixup", false); + } + else + { + dlls.load_after_fork (hParent, first_dll); + cygheap->fdtab.fixup_after_fork (hParent); + sync_with_parent ("loaded dlls", true); + } + + init_console_handler (myself->ctty >= 0); + ForceCloseHandle1 (fork_info->forker_finished, forker_finished); + + pthread::atforkchild (); + fixup_timers_after_fork (); + cygbench ("fork-child"); + ld_preload (); + fixup_hooks_after_fork (); + _my_tls.fixup_after_fork (); + wait_for_sigthread (true); + cygwin_finished_initializing = true; + return 0; +} + +#define NO_SLOW_PID_REUSE +#ifndef NO_SLOW_PID_REUSE +static void +slow_pid_reuse (HANDLE h) +{ + static NO_COPY HANDLE last_fork_procs[NPIDS_HELD]; + static NO_COPY unsigned nfork_procs; + + if (nfork_procs >= (sizeof (last_fork_procs) / sizeof (last_fork_procs [0]))) + nfork_procs = 0; + /* Keep a list of handles to child processes sitting around to prevent + Windows from reusing the same pid n times in a row. Having the same pids + close in succesion confuses bash. Keeping a handle open will stop + windows from reusing the same pid. */ + if (last_fork_procs[nfork_procs]) + ForceCloseHandle1 (last_fork_procs[nfork_procs], fork_stupidity); + if (DuplicateHandle (hMainProc, h, hMainProc, &last_fork_procs[nfork_procs], + 0, FALSE, DUPLICATE_SAME_ACCESS)) + ProtectHandle1 (last_fork_procs[nfork_procs], fork_stupidity); + else + { + last_fork_procs[nfork_procs] = NULL; + system_printf ("couldn't create last_fork_proc, %E"); + } + nfork_procs++; +} +#endif + +int __stdcall +frok::parent (void *stack_here) +{ + HANDLE forker_finished; + DWORD rc; + child_pid = -1; + error = NULL; + this_errno = 0; + bool fix_impersonation = false; + pinfo child; + static char errbuf[256]; + + pthread::atforkprepare (); + + int c_flags = GetPriorityClass (hMainProc); + debug_printf ("priority class %d", c_flags); + + /* If we don't have a console, then don't create a console for the + child either. */ + HANDLE console_handle = CreateFile ("CONOUT$", GENERIC_WRITE, + FILE_SHARE_WRITE, &sec_none_nih, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL); + + if (console_handle != INVALID_HANDLE_VALUE) + CloseHandle (console_handle); + else + c_flags |= DETACHED_PROCESS; + + /* Some file types (currently only sockets) need extra effort in the + parent after CreateProcess and before copying the datastructures + to the child. So we have to start the child in suspend state, + unfortunately, to avoid a race condition. */ + if (cygheap->fdtab.need_fixup_before ()) + c_flags |= CREATE_SUSPENDED; + + /* Remember the address of the first loaded dll and decide + if we need to load dlls. We do this here so that this + information will be available in the parent and, when + the stack is copied, in the child. */ + first_dll = dlls.start.next; + load_dlls = dlls.reload_on_fork && dlls.loaded_dlls; + + /* This will help some of the confusion. */ + fflush (stdout); + + forker_finished = CreateEvent (&sec_all, FALSE, FALSE, NULL); + if (forker_finished == NULL) + { + this_errno = geterrno_from_win_error (); + error = "unable to allocate forker_finished event"; + return -1; + } + + ProtectHandleINH (forker_finished); + + ch.forker_finished = forker_finished; + + ch.stackbottom = _tlsbase; + ch.stacktop = stack_here; + ch.stacksize = (char *) ch.stackbottom - (char *) stack_here; + debug_printf ("stack - bottom %p, top %p, size %d", + ch.stackbottom, ch.stacktop, ch.stacksize); + + PROCESS_INFORMATION pi; + STARTUPINFO si; + + memset (&si, 0, sizeof (si)); + si.cb = sizeof (STARTUPINFO); + si.lpReserved2 = (LPBYTE) &ch; + si.cbReserved2 = sizeof (ch); + + syscall_printf ("CreateProcess (%s, %s, 0, 0, 1, %p, 0, 0, %p, %p)", + myself->progname, myself->progname, c_flags, &si, &pi); + bool locked = __malloc_lock (); + time_t start_time = time (NULL); + + /* Remove impersonation */ + cygheap->user.deimpersonate (); + fix_impersonation = true; + + while (1) + { + rc = CreateProcess (myself->progname, /* image to run */ + myself->progname, /* what we send in arg0 */ + &sec_none_nih, + &sec_none_nih, + TRUE, /* inherit handles from parent */ + c_flags, + NULL, /* environment filled in later */ + 0, /* use current drive/directory */ + &si, + &pi); + + if (!rc) + { + this_errno = geterrno_from_win_error (); + error = "CreateProcessA failed"; + memset (&pi, 0, sizeof (pi)); + goto cleanup; + } + + /* Fixup the parent datastructure if needed and resume the child's + main thread. */ + if (c_flags & CREATE_SUSPENDED) + { + cygheap->fdtab.fixup_before_fork (pi.dwProcessId); + ResumeThread (pi.hThread); + } + + CloseHandle (pi.hThread); + + /* Protect the handle but name it similarly to the way it will + be called in subproc handling. */ + ProtectHandle1 (pi.hProcess, childhProc); + + strace.write_childpid (ch, pi.dwProcessId); + + /* Wait for subproc to initialize itself. */ + if (!ch.sync (pi.dwProcessId, pi.hProcess, FORK_WAIT_TIMEOUT)) + { + DWORD exit_code = ch.proc_retry (pi.hProcess); + if (!exit_code) + continue; + this_errno = EAGAIN; + /* Not thread safe, but do we care? */ + __small_sprintf (errbuf, "died waiting for longjmp before initialization, " + "retry %d, exit code %p", ch.retry, exit_code); + error = errbuf; + goto cleanup; + } + break; + } + + /* Restore impersonation */ + cygheap->user.reimpersonate (); + fix_impersonation = false; + + child_pid = cygwin_pid (pi.dwProcessId); + child.init (child_pid, 1, NULL); + + if (!child) + { + this_errno = get_errno () == ENOMEM ? ENOMEM : EAGAIN; +#ifdef DEBUGGING + error = "pinfo failed"; +#else + syscall_printf ("pinfo failed"); +#endif + goto cleanup; + } + + child->start_time = start_time; /* Register child's starting time. */ + child->nice = myself->nice; + + /* Initialize things that are done later in dll_crt0_1 that aren't done + for the forkee. */ + strcpy (child->progname, myself->progname); + + /* Fill in fields in the child's process table entry. */ + child->dwProcessId = pi.dwProcessId; + child.hProcess = pi.hProcess; + + /* Hopefully, this will succeed. The alternative to doing things this + way is to reserve space prior to calling CreateProcess and then fill + it in afterwards. This requires more bookkeeping than I like, though, + so we'll just do it the easy way. So, terminate any child process if + we can't actually record the pid in the internal table. */ + if (!child.remember (false)) + { + TerminateProcess (pi.hProcess, 1); + this_errno = EAGAIN; +#ifdef DEBUGGING0 + error = "child.remember failed"; +#endif + goto cleanup; + } + +#ifndef NO_SLOW_PID_REUSE + slow_pid_reuse (pi.hProcess); +#endif + + /* CHILD IS STOPPED */ + debug_printf ("child is alive (but stopped)"); + + /* Initialize, in order: stack, dll data, dll bss. + data, bss, heap were done earlier (in dcrt0.cc) + Note: variables marked as NO_COPY will not be copied since they are + placed in a protected segment. */ + + MALLOC_CHECK; + const void *impure_beg; + const void *impure_end; + const char *impure; + if (&_my_tls == _main_tls) + impure_beg = impure_end = impure = NULL; + else + { + impure = "impure"; + impure_beg = _impure_ptr; + impure_end = _impure_ptr + 1; + } + rc = child_copy (pi.hProcess, true, + "stack", stack_here, ch.stackbottom, + impure, impure_beg, impure_end, + NULL); + + __malloc_unlock (); + locked = false; + MALLOC_CHECK; + if (!rc) + { + this_errno = get_errno (); + DWORD exit_code; + if (!GetExitCodeProcess (pi.hProcess, &exit_code)) + exit_code = 0xdeadbeef; + __small_sprintf (errbuf, "pid %u, exitval %p", pi.dwProcessId, exit_code); + error = errbuf; + goto cleanup; + } + + /* Now fill data/bss of any DLLs that were linked into the program. */ + for (dll *d = dlls.istart (DLL_LINK); d; d = dlls.inext ()) + { + debug_printf ("copying data/bss of a linked dll"); + if (!child_copy (pi.hProcess, true, + "linked dll data", d->p.data_start, d->p.data_end, + "linked dll bss", d->p.bss_start, d->p.bss_end, + NULL)) + { + this_errno = get_errno (); +#ifdef DEBUGGING + DWORD exit_code; + if (!GetExitCodeProcess (pi.hProcess, &exit_code)) + exit_code = 0xdeadbeef; + __small_sprintf (errbuf, "pid %u, exitval %p", pi.dwProcessId, exit_code); + error = errbuf; +#endif + goto cleanup; + } + } + + /* Start thread, and then wait for it to reload dlls. */ + resume_child (forker_finished); + if (!ch.sync (child->pid, pi.hProcess, FORK_WAIT_TIMEOUT)) + { + this_errno = EAGAIN; + error = "died waiting for dll loading"; + goto cleanup; + } + + /* If DLLs were loaded in the parent, then the child has reloaded all + of them and is now waiting to have all of the individual data and + bss sections filled in. */ + if (load_dlls) + { + /* CHILD IS STOPPED */ + /* write memory of reloaded dlls */ + for (dll *d = dlls.istart (DLL_LOAD); d; d = dlls.inext ()) + { + debug_printf ("copying data/bss for a loaded dll"); + if (!child_copy (pi.hProcess, true, + "loaded dll data", d->p.data_start, d->p.data_end, + "loaded dll bss", d->p.bss_start, d->p.bss_end, + NULL)) + { + this_errno = get_errno (); +#ifdef DEBUGGING + error = "copying data/bss for a loaded dll"; +#endif + goto cleanup; + } + } + /* Start the child up again. */ + resume_child (forker_finished); + } + + ForceCloseHandle (forker_finished); + forker_finished = NULL; + pthread::atforkparent (); + + return child_pid; + +/* Common cleanup code for failure cases */ +cleanup: + if (fix_impersonation) + cygheap->user.reimpersonate (); + if (locked) + __malloc_unlock (); + + /* Remember to de-allocate the fd table. */ + if (pi.hProcess && !child.hProcess) + ForceCloseHandle1 (pi.hProcess, childhProc); + if (forker_finished) + ForceCloseHandle (forker_finished); + debug_printf ("returning -1"); + return -1; +} + +extern "C" int +fork () +{ + frok grouped; + MALLOC_CHECK; + + debug_printf ("entering"); + grouped.first_dll = NULL; + grouped.load_dlls = 0; + + int res; + int ischild; + + myself->set_has_pgid_children (); + + if (grouped.ch.parent == NULL) + return -1; + if (grouped.ch.subproc_ready == NULL) + { + system_printf ("unable to allocate subproc_ready event, %E"); + return -1; + } + + if (sig_send (NULL, __SIGHOLD)) + { + if (exit_state) + Sleep (INFINITE); + set_errno (EAGAIN); + return -1; + } + + ischild = setjmp (grouped.ch.jmp); + + void *esp; + __asm__ volatile ("movl %%esp,%0": "=r" (esp)); + + if (ischild) + res = grouped.child (esp); + else + { + res = grouped.parent (esp); + sig_send (NULL, __SIGNOHOLD); + } + + MALLOC_CHECK; + if (ischild || res > 0) + /* everything is ok */; + else + { + if (!grouped.error) + syscall_printf ("fork failed - child pid %d, errno %d", grouped.child_pid, grouped.this_errno); + else + { + char buf[strlen (grouped.error) + sizeof ("child %d - , errno 4294967295 ")]; + strcpy (buf, "child %d - "); + strcat (buf, grouped.error); + strcat (buf, ", errno %d"); + system_printf (buf, grouped.child_pid, grouped.this_errno); + } + + set_errno (grouped.this_errno); + } + syscall_printf ("%d = fork()", res); + return res; +} +#ifdef DEBUGGING +void +fork_init () +{ +} +#endif /*DEBUGGING*/ + +#ifdef NEWVFORK +/* Dummy function to force second assignment below to actually be + carried out */ +static vfork_save * +get_vfork_val () +{ + return vfork_storage.val (); +} +#endif + +extern "C" int +vfork () +{ +#ifndef NEWVFORK + debug_printf ("stub called"); + return fork (); +#else + vfork_save *vf = get_vfork_val (); + char **esp, **pp; + + if (vf == NULL) + vf = vfork_storage.create (); + else if (vf->pid) + return fork (); + + // FIXME the tls stuff could introduce a signal race if a child process + // exits quickly. + if (!setjmp (vf->j)) + { + vf->pid = -1; + __asm__ volatile ("movl %%esp,%0": "=r" (vf->vfork_esp):); + __asm__ volatile ("movl %%ebp,%0": "=r" (vf->vfork_ebp):); + for (pp = (char **) vf->frame, esp = vf->vfork_esp; + esp <= vf->vfork_ebp + 2; pp++, esp++) + *pp = *esp; + vf->ctty = myself->ctty; + vf->sid = myself->sid; + vf->pgid = myself->pgid; + cygheap->ctty_on_hold = cygheap->ctty; + vf->console_count = cygheap->console_count; + debug_printf ("cygheap->ctty_on_hold %p, cygheap->console_count %d", cygheap->ctty_on_hold, cygheap->console_count); + int res = cygheap->fdtab.vfork_child_dup () ? 0 : -1; + debug_printf ("%d = vfork()", res); + _my_tls.call_signal_handler (); // FIXME: racy + vf->tls = _my_tls; + return res; + } + + vf = get_vfork_val (); + + for (pp = (char **) vf->frame, esp = vf->vfork_esp; + esp <= vf->vfork_ebp + 2; pp++, esp++) + *esp = *pp; + + cygheap->fdtab.vfork_parent_restore (); + + myself->ctty = vf->ctty; + myself->sid = vf->sid; + myself->pgid = vf->pgid; + termios_printf ("cygheap->ctty %p, cygheap->ctty_on_hold %p", cygheap->ctty, cygheap->ctty_on_hold); + cygheap->console_count = vf->console_count; + + if (vf->pid < 0) + { + int exitval = vf->exitval; + vf->pid = 0; + if ((vf->pid = fork ()) == 0) + exit (exitval); + } + + int pid = vf->pid; + vf->pid = 0; + debug_printf ("exiting vfork, pid %d", pid); + sig_dispatch_pending (); + + _my_tls.call_signal_handler (); // FIXME: racy + _my_tls = vf->tls; + return pid; +#endif +} + +/* Copy memory from one process to another. */ + +bool +child_copy (HANDLE hp, bool write, ...) +{ + va_list args; + va_start (args, write); + static const char *huh[] = {"read", "write"}; + + char *what; + while ((what = va_arg (args, char *))) + { + char *low = va_arg (args, char *); + char *high = va_arg (args, char *); + DWORD todo = wincap.chunksize () ?: high - low; + char *here; + + for (here = low; here < high; here += todo) + { + DWORD done = 0; + if (here + todo > high) + todo = high - here; + int res; + if (write) + res = WriteProcessMemory (hp, here, here, todo, &done); + else + res = ReadProcessMemory (hp, here, here, todo, &done); + debug_printf ("%s - hp %p low %p, high %p, res %d", what, hp, low, high, res); + if (!res || todo != done) + { + if (!res) + __seterrno (); + /* If this happens then there is a bug in our fork + implementation somewhere. */ + system_printf ("%s %s copy failed, %p..%p, done %d, windows pid %u, %E", + what, huh[write], low, high, done); + goto err; + } + } + } + + debug_printf ("done"); + return true; + + err: + TerminateProcess (hp, 1); + set_errno (EAGAIN); + return false; +} diff --git a/winsup/cygwin/hookapi.cc b/winsup/cygwin/hookapi.cc new file mode 100644 index 00000000000..39ba6de8849 --- /dev/null +++ b/winsup/cygwin/hookapi.cc @@ -0,0 +1,237 @@ +/* hookapi.cc + + Copyright 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 "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include +#include +#include + +#define rva(coerce, base, addr) (coerce) ((char *) (base) + (addr)) +#define rvacyg(coerce, addr) rva (coerce, cygwin_hmodule, addr) + +struct function_hook +{ + const char *name; // Function name, e.g. "DirectDrawCreateEx". + const void *hookfn; // Address of your function. + void *origfn; // Stored by HookAPICalls, the address of the original function. +}; + +/* Given an HMODULE, returns a pointer to the PE header */ +static PIMAGE_NT_HEADERS +PEHeaderFromHModule (HMODULE hModule) +{ + PIMAGE_NT_HEADERS pNTHeader; + + if (PIMAGE_DOS_HEADER(hModule) ->e_magic != IMAGE_DOS_SIGNATURE) + pNTHeader = NULL; + else + { + pNTHeader = PIMAGE_NT_HEADERS (PBYTE (hModule) + + PIMAGE_DOS_HEADER (hModule) ->e_lfanew); + if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) + pNTHeader = NULL; + } + + return pNTHeader; +} + +static long +rvadelta (PIMAGE_NT_HEADERS pnt, DWORD import_rva) +{ + PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER) (pnt + 1); + for (int i = 0; i < pnt->FileHeader.NumberOfSections; i++) + if (section[i].VirtualAddress <= import_rva + && (section[i].VirtualAddress + section[i].Misc.VirtualSize) > import_rva) + // if (strncasematch ((char *) section[i].Name, ".idata", IMAGE_SIZEOF_SHORT_NAME)) + return section[i].VirtualAddress - section[i].PointerToRawData; + return -1; +} + +static void * +putmem (PIMAGE_THUNK_DATA pi, const void *hookfn) +{ + DWORD ofl; + if (!VirtualProtect (pi, sizeof (PVOID), PAGE_READWRITE, &ofl) ) + return NULL; + + void *origfn = (void *) pi->u1.Function; + pi->u1.Function = (DWORD) hookfn; + + VirtualProtect (pi, sizeof (PVOID), ofl, &ofl); + return origfn; +} + +/* Builds stubs for and redirects the IAT for one DLL (pImportDesc) */ + +static bool +RedirectIAT (function_hook& fh, PIMAGE_IMPORT_DESCRIPTOR pImportDesc, + HMODULE hm) +{ + // If no import names table, we can't redirect this, so bail + if (pImportDesc->OriginalFirstThunk == 0) + return false; + + /* import address table */ + PIMAGE_THUNK_DATA pt = rva (PIMAGE_THUNK_DATA, hm, pImportDesc->FirstThunk); + /* import names table */ + PIMAGE_THUNK_DATA pn = rva (PIMAGE_THUNK_DATA, hm, pImportDesc->OriginalFirstThunk); + + /* Scan through the IAT, completing the stubs and redirecting the IAT + entries to point to the stubs. */ + for (PIMAGE_THUNK_DATA pi = pt; pn->u1.Ordinal; pi++, pn++) + { + if (IMAGE_SNAP_BY_ORDINAL (pn->u1.Ordinal) ) + continue; + + /* import by name */ + PIMAGE_IMPORT_BY_NAME pimp = rva (PIMAGE_IMPORT_BY_NAME, hm, pn->u1.AddressOfData); + + if (strcmp (fh.name, (char *) pimp->Name) == 0) + { + fh.origfn = putmem (pi, fh.hookfn); + if (!fh.origfn) + return false; + hook_chain *hc; + for (hc = &cygheap->hooks; hc->next; hc = hc->next) + continue; + hc->next = (hook_chain *) cmalloc (HEAP_1_HOOK, sizeof (hook_chain)); + hc->next->loc = (void **) pi; + hc->next->func = fh.hookfn; + hc->next->next = NULL; + break; + } + } + + return true; +} + +static void +get_export (function_hook& fh) +{ + PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER) cygwin_hmodule; + if (pdh->e_magic != IMAGE_DOS_SIGNATURE) + return; + PIMAGE_NT_HEADERS pnt = (PIMAGE_NT_HEADERS) ((char *) pdh + pdh->e_lfanew); + if (pnt->Signature != IMAGE_NT_SIGNATURE || pnt->FileHeader.SizeOfOptionalHeader == 0) + return; + PIMAGE_EXPORT_DIRECTORY pexp = + rvacyg (PIMAGE_EXPORT_DIRECTORY, + pnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if (!pexp) + return; + + PDWORD pfuncs = rvacyg (PDWORD, pexp->AddressOfFunctions); + PDWORD pnames = rvacyg (PDWORD, pexp->AddressOfNames); + for (DWORD i = 0; i < pexp->NumberOfNames; i++) + if (strcmp (fh.name, rvacyg (char *, pnames[i])) == 0) + { + fh.origfn = rvacyg (void *, pfuncs[i]); + break; + } +} + +static const char * +makename (const char *name, char *&buf, int& i, int inc) +{ + i += inc; + static const char *testers[] = {"NOTUSED", "64", "32"}; + if (i < 0 || i >= (int) (sizeof (testers) / sizeof (testers[0]))) + return NULL; + if (i) + { + __small_sprintf (buf, "_%s%s", name, testers[i]); + name = buf; + } + return name; +} + +// Top level routine to find the EXE's imports, and redirect them +void * +hook_or_detect_cygwin (const char *name, const void *fn, WORD& subsys) +{ + HMODULE hm = fn ? GetModuleHandle (NULL) : (HMODULE) name; + PIMAGE_NT_HEADERS pExeNTHdr = PEHeaderFromHModule (hm); + + if (!pExeNTHdr) + return false; + + subsys = pExeNTHdr->OptionalHeader.Subsystem; + + DWORD importRVA = pExeNTHdr->OptionalHeader.DataDirectory + [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if (!importRVA) + return false; + + long delta = fn ? 0 : rvadelta (pExeNTHdr, importRVA); + if (delta < 0) + return false; + + // Convert imports RVA to a usable pointer + PIMAGE_IMPORT_DESCRIPTOR pdfirst = rva (PIMAGE_IMPORT_DESCRIPTOR, hm, importRVA - delta); + + function_hook fh; + fh.origfn = NULL; + fh.hookfn = fn; + char *buf = (char *) alloca (strlen (name) + sizeof ("_64")); + int i; + // Iterate through each import descriptor, and redirect if appropriate + for (PIMAGE_IMPORT_DESCRIPTOR pd = pdfirst; pd->FirstThunk; pd++) + { + if (!strcasematch (rva (PSTR, hm, pd->Name - delta), "cygwin1.dll")) + continue; + if (!fn) + return (void *) "found it"; // just checking if executable used cygwin1.dll + i = -1; + while (!fh.origfn && (fh.name = makename (name, buf, i, 1))) + RedirectIAT (fh, pd, hm); + if (fh.origfn) + break; + } + + while (!fh.origfn && (fh.name = makename (name, buf, i, -1))) + get_export (fh); + + return fh.origfn; +} + +void +ld_preload () +{ + char *p = getenv ("LD_PRELOAD"); + if (!p) + return; + char *s = (char *) alloca (strlen (p) + 1); + strcpy (s, p); + char *here = NULL; + for (p = strtok_r (s, ":\t\n", &here); p; p = strtok_r (NULL, ":\t\n", &here)) + { + path_conv lib (p); + if (!LoadLibrary (lib)) + { + __seterrno (); + api_fatal ("error while loading shared libraries: %s: " + "cannot open shared object file: %s", p, + strerror (get_errno ())); + } + } +} + +void +fixup_hooks_after_fork () +{ + for (hook_chain *hc = &cygheap->hooks; (hc = hc->next); ) + putmem ((PIMAGE_THUNK_DATA) hc->loc, hc->func); +} -- cgit v1.2.1