From 344e68b166462eada01704cc27251a34a5e32398 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Fri, 16 Dec 2011 11:58:03 +0000 Subject: [PATCH] * Makefile.in (DLL_OFILES): Add wow64.o. * dcrt0.cc (CYGWIN_GUARD): Drop execute permission for stack, it's not used for stacks by the OS either. (child_info_fork::alloc_stack_hard_way): Ditto. (child_info_fork::alloc_stack): Don't alloc_stack_hard_way under WOW64 if forked from a 64 bit parent. Set child's StackBase to parent's StackBase. Add comments to explain why. (wow64_respawn): Move to wow64.cc. (wow64_started_from_native64): Move to wow64.cc. (respawn_wow64_process): Move to wow64.cc. (dll_crt0_0): Drop wow64_test_stack_marker and move stack test into wow64_test_for_64bit_parent function. Don't return early if WOW64 process has been started from native 64 bit process. (_dll_crt0): Implement moving stack for WOW64 processes started from native 64 bit process. * wow64.cc: New file. (wow64_has_64bit_parent): Rename from wow64_respawn. (wow64_test_for_64bit_parent): Rename from wow64_started_from_native64. Change comment. (wow64_revert_to_original_stack): New function. (wow64_respawn_process): Rename from respawn_wow64_process for symmetry. * wow64.h: New file. --- winsup/cygwin/ChangeLog | 25 ++++++ winsup/cygwin/Makefile.in | 2 +- winsup/cygwin/dcrt0.cc | 138 ++++++++++++------------------- winsup/cygwin/wow64.cc | 165 ++++++++++++++++++++++++++++++++++++++ winsup/cygwin/wow64.h | 15 ++++ 5 files changed, 258 insertions(+), 87 deletions(-) create mode 100644 winsup/cygwin/wow64.cc create mode 100644 winsup/cygwin/wow64.h diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index b3c7c8842..49a50cf34 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,28 @@ +2011-12-16 Corinna Vinschen + + * Makefile.in (DLL_OFILES): Add wow64.o. + * dcrt0.cc (CYGWIN_GUARD): Drop execute permission for stack, it's + not used for stacks by the OS either. + (child_info_fork::alloc_stack_hard_way): Ditto. + (child_info_fork::alloc_stack): Don't alloc_stack_hard_way under WOW64 + if forked from a 64 bit parent. Set child's StackBase to parent's + StackBase. Add comments to explain why. + (wow64_respawn): Move to wow64.cc. + (wow64_started_from_native64): Move to wow64.cc. + (respawn_wow64_process): Move to wow64.cc. + (dll_crt0_0): Drop wow64_test_stack_marker and move stack test into + wow64_test_for_64bit_parent function. Don't return early if WOW64 + process has been started from native 64 bit process. + (_dll_crt0): Implement moving stack for WOW64 processes started from + native 64 bit process. + * wow64.cc: New file. + (wow64_has_64bit_parent): Rename from wow64_respawn. + (wow64_test_for_64bit_parent): Rename from wow64_started_from_native64. + Change comment. + (wow64_revert_to_original_stack): New function. + (wow64_respawn_process): Rename from respawn_wow64_process for symmetry. + * wow64.h: New file. + 2011-12-16 Christopher Faylor * exceptions.cc (_cygtls::call_signal_handler): Fix debugging to not go diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index 3932bbdeb..c3310cf01 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -157,7 +157,7 @@ DLL_OFILES:=advapi32.o assert.o autoload.o bsdlib.o ctype.o cxx.o cygheap.o \ smallprint.o spawn.o strace.o strfmon.o strfuncs.o strptime.o strsep.o \ strsig.o sync.o syscalls.o sysconf.o syslog.o termios.o thread.o \ timer.o times.o tls_pbuf.o tty.o uinfo.o uname.o wait.o wincap.o \ - window.o winf.o xsique.o \ + window.o winf.o wow64.o xsique.o \ $(EXTRA_DLL_OFILES) $(EXTRA_OFILES) $(MALLOC_OFILES) $(MT_SAFE_OBJECTS) EXCLUDE_STATIC_OFILES:=$(addprefix --exclude=,\ diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc index b6392acf2..a3900372d 100644 --- a/winsup/cygwin/dcrt0.cc +++ b/winsup/cygwin/dcrt0.cc @@ -37,6 +37,7 @@ details. */ #include "cygxdr.h" #include "fenv.h" #include "ntdll.h" +#include "wow64.h" #define MAX_AT_FILE_LEVEL 10 @@ -385,7 +386,7 @@ check_sanity_and_sync (per_process *p) child_info NO_COPY *child_proc_info = NULL; -#define CYGWIN_GUARD (PAGE_EXECUTE_READWRITE | PAGE_GUARD) +#define CYGWIN_GUARD (PAGE_READWRITE | PAGE_GUARD) void child_info_fork::alloc_stack_hard_way (volatile char *b) @@ -408,8 +409,7 @@ child_info_fork::alloc_stack_hard_way (volatile char *b) api_fatal ("fork: can't reserve memory for stack %p - %p, %E", stackaddr, stackbottom); stacksize = (char *) stackbottom - (char *) stacktop; - stack_ptr = VirtualAlloc (stacktop, stacksize, MEM_COMMIT, - PAGE_EXECUTE_READWRITE); + stack_ptr = VirtualAlloc (stacktop, stacksize, MEM_COMMIT, PAGE_READWRITE); if (!stack_ptr) abort ("can't commit memory for stack %p(%d), %E", stacktop, stacksize); if (guardsize != (size_t) -1) @@ -447,7 +447,15 @@ child_info_fork::alloc_stack () { volatile char * volatile esp; __asm__ volatile ("movl %%esp,%0": "=r" (esp)); - if (_tlsbase != stackbottom) + /* Make sure not to try a hard allocation if we have been forked off from + the main thread of a Cygwin process which has been started from a 64 bit + parent. In that case the _tlsbase of the forked child is not the same + as the _tlsbase of the parent (== stackbottom), but only because the + stack of the parent has been slightly rearranged. See comment in + wow64_revert_to_original_stack for details. We just check here if the + stack is in the usual range for the main thread stack. */ + if (_tlsbase != stackbottom + && (!wincap.is_wow64 () || stackbottom > (char *) 0x400000)) alloc_stack_hard_way (esp); else { @@ -455,6 +463,11 @@ child_info_fork::alloc_stack () while (_tlstop >= st) esp = getstack (esp); stackaddr = 0; + /* This only affects forked children of a process started from a native + 64 bit process, but it doesn't hurt to do it unconditionally. Fix + StackBase in the child to be the same as in the parent, so that the + computation of _my_tls is correct. */ + _tlsbase = (char *) stackbottom; } } @@ -657,75 +670,6 @@ init_windows_system_directory () } } -static bool NO_COPY wow64_respawn = false; - -inline static bool -wow64_started_from_native64 () -{ - /* On Windows XP 64 and 2003 64 there's a problem with processes running - under WOW64. The first process started from a 64 bit process has an - unusual stack address for the main thread. That is, an address which - is in the usual space occupied by the process image, but below the auto - load address of DLLs. If we encounter this situation, check if we - really have been started from a 64 bit process here. If so, we exit - early from dll_crt0_0 and respawn first thing in dll_crt0_1. This - ping-pong game is necessary to workaround a problem observed on - Windows 2003 R2 64. Starting with Cygwin 1.7.10 we don't link against - advapi32.dll anymore. However, *any* process linked against advapi32, - directly or indirectly, now fails to respawn if respawn_wow_64_process - is called during DLL_PROCESS_ATTACH initialization. In that case - CreateProcessW returns with ERROR_ACCESS_DENIED for some reason. - Calling CreateProcessW later, inside dll_crt0_1 and so outside of - dll initialization works as before, though. */ - NTSTATUS ret; - PROCESS_BASIC_INFORMATION pbi; - HANDLE parent; - - ULONG wow64 = TRUE; /* Opt on the safe side. */ - - /* Unfortunately there's no simpler way to retrieve the - parent process in NT, as far as I know. Hints welcome. */ - ret = NtQueryInformationProcess (NtCurrentProcess (), - ProcessBasicInformation, - &pbi, sizeof pbi, NULL); - if (NT_SUCCESS (ret) - && (parent = OpenProcess (PROCESS_QUERY_INFORMATION, - FALSE, - pbi.InheritedFromUniqueProcessId))) - { - NtQueryInformationProcess (parent, ProcessWow64Information, - &wow64, sizeof wow64, NULL); - CloseHandle (parent); - } - return !wow64; -} - -inline static void -respawn_wow64_process () -{ - /* The parent is a real 64 bit process. Respawn. */ - WCHAR path[PATH_MAX]; - PROCESS_INFORMATION pi; - STARTUPINFOW si; - DWORD ret = 0; - - GetModuleFileNameW (NULL, path, PATH_MAX); - GetStartupInfoW (&si); - if (!CreateProcessW (path, GetCommandLineW (), NULL, NULL, TRUE, - CREATE_DEFAULT_ERROR_MODE - | GetPriorityClass (GetCurrentProcess ()), - NULL, NULL, &si, &pi)) - api_fatal ("Failed to create process <%W> <%W>, %E", - path, GetCommandLineW ()); - CloseHandle (pi.hThread); - if (WaitForSingleObject (pi.hProcess, INFINITE) == WAIT_FAILED) - api_fatal ("Waiting for process %d failed, %E", pi.dwProcessId); - GetExitCodeProcess (pi.hProcess, &ret); - CloseHandle (pi.hProcess); - TerminateProcess (GetCurrentProcess (), ret); - ExitProcess (ret); -} - void dll_crt0_0 () { @@ -759,15 +703,11 @@ dll_crt0_0 () if (!child_proc_info) { memory_init (true); - /* WOW64 bit process with stack at unusual address? Check if we - have been started from 64 bit process ans set wow64_respawn. - Full description in wow64_started_from_native64 above. */ - BOOL wow64_test_stack_marker; - if (wincap.is_wow64 () - && &wow64_test_stack_marker >= (PBOOL) 0x400000 - && &wow64_test_stack_marker <= (PBOOL) 0x10000000 - && (wow64_respawn = wow64_started_from_native64 ())) - return; + /* WOW64 process? Check if we have been started from 64 bit process + and if our stack is at an unusual address. Set wow64_has_64bit_parent + if so. Problem description in wow64_test_for_64bit_parent. */ + if (wincap.is_wow64 ()) + wow64_has_64bit_parent = wow64_test_for_64bit_parent (); } else { @@ -1003,10 +943,36 @@ __cygwin_exit_return: \n\ extern "C" void __stdcall _dll_crt0 () { - /* Respawn WOW64 process started from native 64 bit process. See comment - in wow64_started_from_native64 above for a full description. */ - if (wow64_respawn) - respawn_wow64_process (); + /* Handle WOW64 process started from native 64 bit process. See comment + in wow64_test_for_64bit_parent for a full problem description. */ + if (wow64_has_64bit_parent && !dynamically_loaded) + { + /* Must be static since it's referenced after the stack pointers have + been moved. */ + static PVOID allocationbase = 0; + + /* Check if we just move the stack. See comment in + wow64_revert_to_original_stack for the gory details. */ + PVOID stackaddr = wow64_revert_to_original_stack (allocationbase); + if (stackaddr) + { + /* 2nd half of the stack move. First set stack pointers to + our new address. */ + __asm__ ("\n\ + movl %[ADDR], %%esp \n\ + movl %%esp, %%ebp \n" + : : [ADDR] "r" (stackaddr)); + /* Now we're back on the original stack. Free up space taken by the + former main thread stack and... */ + VirtualFree (NtCurrentTeb ()->DeallocationStack, + 0, MEM_RELEASE); + /* ...set DeallocationStack correctly. */ + NtCurrentTeb ()->DeallocationStack = allocationbase; + } + else + /* Fall back to respawn if wow64_revert_to_original_stack fails. */ + wow64_respawn_process (); + } #ifdef __i386__ _feinitialise (); #endif diff --git a/winsup/cygwin/wow64.cc b/winsup/cygwin/wow64.cc new file mode 100644 index 000000000..6908cf8a4 --- /dev/null +++ b/winsup/cygwin/wow64.cc @@ -0,0 +1,165 @@ +/* wow64.cc + + Copyright 2011 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 "cygtls.h" +#include "ntdll.h" + +#define PTR_ADD(p,o) ((PVOID)((PBYTE)(p)+(o))) + +bool NO_COPY wow64_has_64bit_parent = false; + +bool +wow64_test_for_64bit_parent () +{ + /* On Windows XP 64 and 2003 64 there's a problem with processes running + under WOW64. The first process started from a 64 bit process has an + unusual stack address for the main thread. That is, an address which + is in the usual space occupied by the process image, but below the auto + load address of DLLs. If this process forks, the child has its stack + in the usual memory slot again, thus we have to "alloc_stack_hard_way". + However, this fails in almost all cases because the stack slot of the + parent process is taken by something else in the child process. + + If we encounter this situation, check if we really have been started + from a 64 bit process here. If so, we note this fact in + wow64_has_64bit_parent so we can workaround the stack problem in + _dll_crt0. See there for how we go along. */ + NTSTATUS ret; + PROCESS_BASIC_INFORMATION pbi; + HANDLE parent; + + ULONG wow64 = TRUE; /* Opt on the safe side. */ + + /* First check if the stack is where it belongs. If so, we don't have to + do anything special. This is the case on Vista and later. */ + if (&wow64 < (PULONG) 0x400000) + return false; + /* Check if the parent is a native 64 bit process. Unfortunately there's + no simpler way to retrieve the parent process in NT, as far as I know. + Hints welcome. */ + ret = NtQueryInformationProcess (NtCurrentProcess (), + ProcessBasicInformation, + &pbi, sizeof pbi, NULL); + if (NT_SUCCESS (ret) + && (parent = OpenProcess (PROCESS_QUERY_INFORMATION, + FALSE, + pbi.InheritedFromUniqueProcessId))) + { + NtQueryInformationProcess (parent, ProcessWow64Information, + &wow64, sizeof wow64, NULL); + CloseHandle (parent); + } + return !wow64; +} + +PVOID +wow64_revert_to_original_stack (PVOID &allocationbase) +{ + /* Test if the original stack exists and has been set up as usual. Even + though the stack of the WOW64 process is at an unusual address, it appears + that the "normal" stack has been created as usual. It's partially in use + by the 32->64 bit transition layer of the OS to help along the WOW64 + process, but it's otherwise mostly unused. + The original stack is expected to be located at 0x30000, up to 0x230000. + The assumption here is that the default main thread stack size is 2 Megs, + but we expect lower stacksizes up to 1 Megs. What we do here is to start + about in the middle, but below the 1 Megs stack size. The stack is + allocated in a single call, so the entire stack has the same + AllocationBase. */ + MEMORY_BASIC_INFORMATION mbi; + PVOID addr = (PVOID) 0x100000; + + /* First fetch the AllocationBase. */ + VirtualQuery (addr, &mbi, sizeof mbi); + allocationbase = mbi.AllocationBase; + /* At the start we expect a reserved region big enough still to host as + the main stack. 512K should be ok (knock on wood). */ + VirtualQuery (allocationbase, &mbi, sizeof mbi); + if (mbi.State != MEM_RESERVE || mbi.RegionSize < 512 * 1024) + return NULL; + + addr = PTR_ADD (mbi.BaseAddress, mbi.RegionSize); + /* Next we expect a guard page. */ + VirtualQuery (addr, &mbi, sizeof mbi); + if (mbi.AllocationBase != allocationbase + || mbi.State != MEM_COMMIT + || !(mbi.Protect & PAGE_GUARD)) + return NULL; + + PVOID guardaddr = mbi.BaseAddress; + SIZE_T guardsize = mbi.RegionSize; + addr = PTR_ADD (mbi.BaseAddress, mbi.RegionSize); + /* Next we expect a committed R/W region, the in-use area of that stack. */ + VirtualQuery (addr, &mbi, sizeof mbi); + if (mbi.AllocationBase != allocationbase + || mbi.State != MEM_COMMIT + || mbi.Protect != PAGE_READWRITE) + return NULL; + + /* The original stack is used by the OS. Leave enough space for the OS + to be happy (another 64K) and constitute a second stack within the so + far reserved stack area. */ + PVOID newbase = PTR_ADD (guardaddr, -wincap.allocation_granularity ()); + PVOID newtop = PTR_ADD (newbase, -wincap.allocation_granularity ()); + guardaddr = PTR_ADD (newtop, -guardsize); + if (!VirtualAlloc (newtop, wincap.allocation_granularity (), + MEM_COMMIT, PAGE_READWRITE)) + return NULL; + if (!VirtualAlloc (guardaddr, guardsize, MEM_COMMIT, + PAGE_READWRITE | PAGE_GUARD)) + return NULL; + + /* We're going to reuse the original stack. Yay, no more respawn! + Set the StackBase and StackLimit values in the TEB, set _main_tls + accordingly, and return the new address for the stack pointer. + The second half of the stack move is done by the caller _dll_crt0. */ + _tlsbase = (char *) newbase; + _tlstop = (char *) newtop; + _main_tls = &_my_tls; + return PTR_ADD (_main_tls, -4); +} + +/* Respawn WOW64 process. This is only called if we can't reuse the original + stack. See comment in wow64_revert_to_original_stack for details. See + _dll_crt0 for the call of this function. + + Historical note: + + Originally we just always respawned, right from dll_entry. This stopped + working with Cygwin 1.7.10 on Windows 2003 R2 64. Starting with Cygwin + 1.7.10 we don't link against advapi32.dll anymore. However, any process + linked against advapi32, directly or indirectly, failed to respawn when + trying respawning during DLL_PROCESS_ATTACH initialization. In that + case CreateProcessW returns with ERROR_ACCESS_DENIED for some reason. */ +void +wow64_respawn_process () +{ + WCHAR path[PATH_MAX]; + PROCESS_INFORMATION pi; + STARTUPINFOW si; + DWORD ret = 0; + + GetModuleFileNameW (NULL, path, PATH_MAX); + GetStartupInfoW (&si); + if (!CreateProcessW (path, GetCommandLineW (), NULL, NULL, TRUE, + CREATE_DEFAULT_ERROR_MODE + | GetPriorityClass (GetCurrentProcess ()), + NULL, NULL, &si, &pi)) + api_fatal ("Failed to create process <%W> <%W>, %E", + path, GetCommandLineW ()); + CloseHandle (pi.hThread); + if (WaitForSingleObject (pi.hProcess, INFINITE) == WAIT_FAILED) + api_fatal ("Waiting for process %d failed, %E", pi.dwProcessId); + GetExitCodeProcess (pi.hProcess, &ret); + CloseHandle (pi.hProcess); + TerminateProcess (GetCurrentProcess (), ret); + ExitProcess (ret); +} diff --git a/winsup/cygwin/wow64.h b/winsup/cygwin/wow64.h new file mode 100644 index 000000000..bd0564f1f --- /dev/null +++ b/winsup/cygwin/wow64.h @@ -0,0 +1,15 @@ +/* wow64.h + + Copyright 2011 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. */ + +extern bool NO_COPY wow64_has_64bit_parent; + +extern bool wow64_test_for_64bit_parent (); +extern PVOID wow64_revert_to_original_stack (PVOID &allocationbase); +extern void wow64_respawn_process ();