newlib-cygwin/winsup/cygwin/wow64.cc

166 lines
6.5 KiB
C++

/* 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);
}