On 64 bit, only create new thread stack if started from 32 bit process on affected platforms

* dcrt0.cc (dll_crt0_0): On 64 bit, set wow64_needs_stack_adjustment
        if not started from a 64 bit process.
        (_dll_crt0): Enable wow64_needs_stack_adjustment branch on 64 bit
        as well.  Remove 64 bit only code.  Introduce CREATE_STACK and
        FIX_STACK macros to conditionalize the code.  Rearrange and
        partially rewrite comments.
        * wincap.h (wincaps::has_3264_stack_broken): New element.
        * wincap.cc: Implement above element throughout.
        (wincapc::init): Set has_3264_stack_broken to false on 32 bit.
        * wow64.cc: Enable functionality on 64 bit architecture, except for
        wow64_revert_to_original_stack.  Enhance comments to explain.
        (wow64_eval_expected_main_stack): Make 64 bit clean.
        (wow64_test_for_64bit_parent): Ditto.
        * wow64.h: Export wow64_revert_to_original_stack on 32 bit only,
        everything else on all architectures.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2015-12-03 22:56:44 +01:00
parent 8f4da28eb6
commit 12743c2d5d
6 changed files with 90 additions and 60 deletions

View File

@ -1,3 +1,21 @@
2015-12-03 Corinna Vinschen <corinna@vinschen.de>
* dcrt0.cc (dll_crt0_0): On 64 bit, set wow64_needs_stack_adjustment
if not started from a 64 bit process.
(_dll_crt0): Enable wow64_needs_stack_adjustment branch on 64 bit
as well. Remove 64 bit only code. Introduce CREATE_STACK and
FIX_STACK macros to conditionalize the code. Rearrange and
partially rewrite comments.
* wincap.h (wincaps::has_3264_stack_broken): New element.
* wincap.cc: Implement above element throughout.
(wincapc::init): Set has_3264_stack_broken to false on 32 bit.
* wow64.cc: Enable functionality on 64 bit architecture, except for
wow64_revert_to_original_stack. Enhance comments to explain.
(wow64_eval_expected_main_stack): Make 64 bit clean.
(wow64_test_for_64bit_parent): Ditto.
* wow64.h: Export wow64_revert_to_original_stack on 32 bit only,
everything else on all architectures.
2015-12-03 Corinna Vinschen <corinna@vinschen.de> 2015-12-03 Corinna Vinschen <corinna@vinschen.de>
* fhandler_process.cc (thread_info::thread_info): Accommodate the fact * fhandler_process.cc (thread_info::thread_info): Accommodate the fact

View File

@ -782,6 +782,11 @@ dll_crt0_0 ()
description in wow64_test_for_64bit_parent. */ description in wow64_test_for_64bit_parent. */
if (wincap.wow64_has_secondary_stack ()) if (wincap.wow64_has_secondary_stack ())
wow64_needs_stack_adjustment = wow64_test_for_64bit_parent (); wow64_needs_stack_adjustment = wow64_test_for_64bit_parent ();
#else
/* Windows 10 1511 has a stack move when a 64 bit process is started from
a 32 bit process, just as it was vice versa in XP/2003. */
if (wincap.has_3264_stack_broken ())
wow64_needs_stack_adjustment = !wow64_test_for_64bit_parent ();
#endif /* !__x86_64__ */ #endif /* !__x86_64__ */
} }
else else
@ -1062,67 +1067,50 @@ __cygwin_exit_return: \n\
extern "C" void __stdcall extern "C" void __stdcall
_dll_crt0 () _dll_crt0 ()
{ {
#ifndef __x86_64__ #ifdef __x86_64__
/* Handle 64 bit process on Windows 10 rel 1511 which has been started from
32 bit WOW64 process. See comment in wow64_test_for_64bit_parent for a
problem description. Unfortunately the areas the stacks would have to
be moved to are both taken by "something else"(tm) in both, forker and
forkee, so we can't use the wow64_revert_to_original_stack method as in
the 32 bit case. Rather, we move the main thread stack to the stack area
reserved for pthread stacks for this process. */
#define CREATE_STACK(a) create_new_main_thread_stack(a)
#define FIX_STACK(s) __asm__ ("\n" \
"movq %[ADDR], %%rsp \n" \
"movq %%rsp, %%rbp \n" \
: : [ADDR] "r" (s))
#else
/* Handle WOW64 process on XP/2K3 which has been started from native 64 bit /* Handle WOW64 process on XP/2K3 which has been started from native 64 bit
process. See comment in wow64_test_for_64bit_parent for a full problem process. See comment in wow64_test_for_64bit_parent for a full problem
description. */ description. */
#define CREATE_STACK(a) wow64_revert_to_original_stack(a)
#define FIX_STACK(s) __asm__ ("\n" \
"movl %[ADDR], %%esp \n" \
"xorl %%ebp, %%ebp \n" \
: : [ADDR] "r" (s))
#endif
if (wow64_needs_stack_adjustment && !dynamically_loaded) if (wow64_needs_stack_adjustment && !dynamically_loaded)
{ {
/* Must be static since it's referenced after the stack and frame /* Must be static since it's referenced after the stack and frame
pointer registers have been changed. */ pointer registers have been changed. */
static PVOID allocationbase = 0; static PVOID allocationbase;
/* Check if we just move the stack. If so, wow64_revert_to_original_stack PVOID stackaddr = CREATE_STACK (allocationbase);
returns a non-NULL, 16 byte aligned address. See comments in
wow64_revert_to_original_stack for the gory details. */
PVOID stackaddr = wow64_revert_to_original_stack (allocationbase);
if (stackaddr) if (stackaddr)
{ {
/* 2nd half of the stack move. Set stack pointer to new address. /* 2nd half of the stack move. Set stack pointer to new address.
Set frame pointer to 0. */ Set frame pointer to 0. */
__asm__ ("\n\ FIX_STACK (stackaddr);
movl %[ADDR], %%esp \n\
xorl %%ebp, %%ebp \n"
: : [ADDR] "r" (stackaddr));
/* Now we're back on the original stack. Free up space taken by the /* Now we're back on the original stack. Free up space taken by the
former main thread stack and set DeallocationStack correctly. */ former main thread stack and set DeallocationStack correctly. */
VirtualFree (NtCurrentTeb ()->DeallocationStack, 0, MEM_RELEASE); VirtualFree (NtCurrentTeb ()->DeallocationStack, 0, MEM_RELEASE);
NtCurrentTeb ()->DeallocationStack = allocationbase; NtCurrentTeb ()->DeallocationStack = allocationbase;
} }
else else
/* Fall back to respawn if wow64_revert_to_original_stack fails. */ /* Fall back to respawning if creating a new stack fails. */
wow64_respawn_process (); wow64_respawn_process ();
} }
#else
/* Starting with Windows 10 rel 1511, the main stack of an application is
not reproducible if a 64 bit process has been started from a 32 bit
process. Given that we have enough virtual address space on 64 bit
anyway, we now move the main thread stack to the stack area reserved for
pthread stacks. This allows a reproducible stack space under our own
control and avoids collision with the OS. */
if (!in_forkee && !dynamically_loaded)
{
/* Must be static since it's referenced after the stack and frame
pointer registers have been changed. */
static PVOID allocationbase;
PVOID stackaddr = create_new_main_thread_stack (allocationbase);
if (stackaddr)
{
/* 2nd half of the stack move. Set stack pointer to new address.
Don't set frame pointer to 0 since x86_64 uses the stack while
evaluating NtCurrentTeb (). */
__asm__ ("\n\
movq %[ADDR], %%rsp \n\
movq %%rsp, %%rbp \n"
: : [ADDR] "r" (stackaddr));
/* Now we're back on the new stack. Free up space taken by the
former main thread stack and set DeallocationStack correctly. */
VirtualFree (NtCurrentTeb ()->DeallocationStack, 0, MEM_RELEASE);
NtCurrentTeb ()->DeallocationStack = allocationbase;
}
}
#endif /* !__x86_64__ */
_feinitialise (); _feinitialise ();
#ifndef __x86_64__ #ifndef __x86_64__
main_environ = user_data->envptr; main_environ = user_data->envptr;

View File

@ -52,6 +52,7 @@ wincaps wincap_xpsp2 __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:false, has_processor_groups:false,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_2003 __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_2003 __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -86,6 +87,7 @@ wincaps wincap_2003 __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:false, has_processor_groups:false,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_vista __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_vista __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -120,6 +122,7 @@ wincaps wincap_vista __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:false, has_processor_groups:false,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_7 __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_7 __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -154,6 +157,7 @@ wincaps wincap_7 __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:true, has_processor_groups:true,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_8 __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_8 __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -188,6 +192,7 @@ wincaps wincap_8 __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:true, has_processor_groups:true,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_10 __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_10 __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -222,6 +227,7 @@ wincaps wincap_10 __attribute__((section (".cygwin_dll_common"), shared)) = {
has_processor_groups:true, has_processor_groups:true,
has_broken_prefetchvm:true, has_broken_prefetchvm:true,
has_new_pebteb_region:false, has_new_pebteb_region:false,
has_3264_stack_broken:false,
}; };
wincaps wincap_10_1511 __attribute__((section (".cygwin_dll_common"), shared)) = { wincaps wincap_10_1511 __attribute__((section (".cygwin_dll_common"), shared)) = {
@ -256,6 +262,7 @@ wincaps wincap_10_1511 __attribute__((section (".cygwin_dll_common"), shared)) =
has_processor_groups:true, has_processor_groups:true,
has_broken_prefetchvm:false, has_broken_prefetchvm:false,
has_new_pebteb_region:true, has_new_pebteb_region:true,
has_3264_stack_broken:true,
}; };
wincapc wincap __attribute__((section (".cygwin_dll_common"), shared)); wincapc wincap __attribute__((section (".cygwin_dll_common"), shared));
@ -320,6 +327,10 @@ wincapc::init ()
target process on 64 bit XP/2003 in native 64 bit mode only. Reset the target process on 64 bit XP/2003 in native 64 bit mode only. Reset the
flag here for 32 bit. */ flag here for 32 bit. */
((wincaps *)caps)->has_broken_rtl_query_process_debug_information = false; ((wincaps *)caps)->has_broken_rtl_query_process_debug_information = false;
/* Windows 10 1511 has a stack move when a 64 bit process is started from
a 32 bit process, just as it was vice versa in XP/2003. Reset the flag
here for 32 bit. */
((wincaps *)caps)->has_3264_stack_broken = false;
if (NT_SUCCESS (NtQueryInformationProcess (NtCurrentProcess (), if (NT_SUCCESS (NtQueryInformationProcess (NtCurrentProcess (),
ProcessWow64Information, ProcessWow64Information,
&wow64, sizeof wow64, NULL)) &wow64, sizeof wow64, NULL))

View File

@ -45,6 +45,7 @@ struct wincaps
unsigned has_processor_groups : 1; unsigned has_processor_groups : 1;
unsigned has_broken_prefetchvm : 1; unsigned has_broken_prefetchvm : 1;
unsigned has_new_pebteb_region : 1; unsigned has_new_pebteb_region : 1;
unsigned has_3264_stack_broken : 1;
}; };
class wincapc class wincapc
@ -104,6 +105,7 @@ public:
bool IMPLEMENT (has_processor_groups) bool IMPLEMENT (has_processor_groups)
bool IMPLEMENT (has_broken_prefetchvm) bool IMPLEMENT (has_broken_prefetchvm)
bool IMPLEMENT (has_new_pebteb_region) bool IMPLEMENT (has_new_pebteb_region)
bool IMPLEMENT (has_3264_stack_broken)
#undef IMPLEMENT #undef IMPLEMENT
}; };

View File

@ -8,10 +8,6 @@ This software is a copyrighted work licensed under the terms of the
Cygwin license. Please consult the file "CYGWIN_LICENSE" for Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */ details. */
#ifndef __x86_64__
/* WOW64 only plays a role in the 32 bit version. Don't use any of this
in the 64 bit version. */
#include "winsup.h" #include "winsup.h"
#include "cygtls.h" #include "cygtls.h"
#include "ntdll.h" #include "ntdll.h"
@ -25,11 +21,11 @@ static void
wow64_eval_expected_main_stack (PVOID &allocbase, PVOID &stackbase) wow64_eval_expected_main_stack (PVOID &allocbase, PVOID &stackbase)
{ {
PIMAGE_DOS_HEADER dosheader; PIMAGE_DOS_HEADER dosheader;
PIMAGE_NT_HEADERS32 ntheader; PIMAGE_NT_HEADERS ntheader;
DWORD size; SIZE_T size;
dosheader = (PIMAGE_DOS_HEADER) GetModuleHandle (NULL); dosheader = (PIMAGE_DOS_HEADER) GetModuleHandle (NULL);
ntheader = (PIMAGE_NT_HEADERS32) ((PBYTE) dosheader + dosheader->e_lfanew); ntheader = (PIMAGE_NT_HEADERS) ((PBYTE) dosheader + dosheader->e_lfanew);
/* The main thread stack is expected to be located at 0x30000, which is the /* The main thread stack is expected to be located at 0x30000, which is the
case for all observed NT systems up to Server 2003 R2, unless the case for all observed NT systems up to Server 2003 R2, unless the
stacksize requested by the StackReserve field in the PE/COFF header is stacksize requested by the StackReserve field in the PE/COFF header is
@ -41,8 +37,15 @@ wow64_eval_expected_main_stack (PVOID &allocbase, PVOID &stackbase)
stack address on Vista/2008 64 bit is 0x80000 and on W7/2K8R2 64 bit stack address on Vista/2008 64 bit is 0x80000 and on W7/2K8R2 64 bit
it is 0x90000. However, this is no problem because the system sticks it is 0x90000. However, this is no problem because the system sticks
to that address for all WOW64 processes, not only for the first one to that address for all WOW64 processes, not only for the first one
started from a 64 bit parent. */ started from a 64 bit parent.
On 64 bit W10 1511 the stack starts at 0x400000 by default. See comment
in wow64_test_for_64bit_parent. */
#ifdef __x86_64__
allocbase = (PVOID) 0x400000;
#else
allocbase = (PVOID) 0x30000; allocbase = (PVOID) 0x30000;
#endif
/* Stack size. The OS always rounds the size up to allocation granularity /* Stack size. The OS always rounds the size up to allocation granularity
and it never allocates less than 256K. */ and it never allocates less than 256K. */
size = roundup2 (ntheader->OptionalHeader.SizeOfStackReserve, size = roundup2 (ntheader->OptionalHeader.SizeOfStackReserve,
@ -71,11 +74,19 @@ wow64_test_for_64bit_parent ()
we have to "alloc_stack_hard_way". However, this fails in almost all 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 cases because the stack slot of the parent process is taken by something
else in the child process. else in the child process.
What we do here is to check if the current stack is the excpected main What we do here is to check if the current stack is the expected main
thread stack and if not, if we really have been started from a 64 bit thread stack and if not, if we really have been started from a 64 bit
process here. If so, we note this fact in wow64_needs_stack_adjustment process here. If so, we note this fact in wow64_needs_stack_adjustment
so we can workaround the stack problem in _dll_crt0. See there for how so we can workaround the stack problem in _dll_crt0. See there for how
we go along. */ we go along. */
/* Amazing but true: Starting with Windows 10 1511 this problem has been
reintroduced, just in the opposite direction: If a 64 bit process is
created from a 32 bit WOW64 process, the main thread stack in the 64
bit child gets moved to another location than the default. In the
forked child, the stack is back where it usually is when started from
another 64 bit process. Therefore we have to be able to recognize
this scenarion now on 64 bit as well. We I don't believe it... */
NTSTATUS ret; NTSTATUS ret;
PROCESS_BASIC_INFORMATION pbi; PROCESS_BASIC_INFORMATION pbi;
HANDLE parent; HANDLE parent;
@ -86,7 +97,7 @@ wow64_test_for_64bit_parent ()
/* First check if the current stack is where it belongs. If so, we don't /* First check if the current stack is where it belongs. If so, we don't
have to do anything special. This is the case on Vista and later. */ have to do anything special. This is the case on Vista and later. */
wow64_eval_expected_main_stack (allocbase, stackbase); wow64_eval_expected_main_stack (allocbase, stackbase);
if (&wow64 >= (PULONG) allocbase && &wow64 < (PULONG) stackbase) if (&wow64 >= (PULONG_PTR) allocbase && &wow64 < (PULONG_PTR) stackbase)
return false; return false;
/* Check if the parent is a native 64 bit process. Unfortunately there's /* Check if the parent is a native 64 bit process. Unfortunately there's
@ -107,6 +118,8 @@ wow64_test_for_64bit_parent ()
return !wow64; return !wow64;
} }
#ifndef __x86_64__
PVOID PVOID
wow64_revert_to_original_stack (PVOID &allocationbase) wow64_revert_to_original_stack (PVOID &allocationbase)
{ {
@ -177,6 +190,8 @@ wow64_revert_to_original_stack (PVOID &allocationbase)
return PTR_ADD (NtCurrentTeb()->Tib.StackBase, -16); return PTR_ADD (NtCurrentTeb()->Tib.StackBase, -16);
} }
#endif /* !__x86_64__ */
/* Respawn WOW64 process. This is only called if we can't reuse the original /* 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 stack. See comment in wow64_revert_to_original_stack for details. See
_dll_crt0 for the call of this function. _dll_crt0 for the call of this function.
@ -211,5 +226,3 @@ wow64_respawn_process ()
TerminateProcess (GetCurrentProcess (), ret); TerminateProcess (GetCurrentProcess (), ret);
ExitProcess (ret); ExitProcess (ret);
} }
#endif /* !__x86_64__ */

View File

@ -1,6 +1,6 @@
/* wow64.h /* wow64.h
Copyright 2011, 2012 Red Hat, Inc. Copyright 2011, 2012, 2015 Red Hat, Inc.
This file is part of Cygwin. This file is part of Cygwin.
@ -8,14 +8,12 @@ This software is a copyrighted work licensed under the terms of the
Cygwin license. Please consult the file "CYGWIN_LICENSE" for Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */ details. */
#ifndef __x86_64__
/* WOW64 only plays a role in the 32 bit version. Don't use any of this
in the 64 bit version. */
extern bool NO_COPY wow64_needs_stack_adjustment; extern bool NO_COPY wow64_needs_stack_adjustment;
extern bool wow64_test_for_64bit_parent (); extern bool wow64_test_for_64bit_parent ();
extern PVOID wow64_revert_to_original_stack (PVOID &allocationbase);
extern void wow64_respawn_process () __attribute__ ((noreturn)); extern void wow64_respawn_process () __attribute__ ((noreturn));
#ifndef __x86_64__
extern PVOID wow64_revert_to_original_stack (PVOID &allocationbase);
#endif /* !__x86_64__ */ #endif /* !__x86_64__ */