* 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.
This commit is contained in:
Corinna Vinschen 2011-12-16 11:58:03 +00:00
parent 6625879aa6
commit 344e68b166
5 changed files with 258 additions and 87 deletions

View File

@ -1,3 +1,28 @@
2011-12-16 Corinna Vinschen <vinschen@redhat.com>
* 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 <me.cygwin2011@cgf.cx>
* exceptions.cc (_cygtls::call_signal_handler): Fix debugging to not go

View File

@ -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=,\

View File

@ -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

165
winsup/cygwin/wow64.cc Normal file
View File

@ -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);
}

15
winsup/cygwin/wow64.h Normal file
View File

@ -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 ();