From a42b659af9cb76dbf69c270d642fd37a59454224 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Fri, 28 Oct 2022 02:30:18 -0700 Subject: [PATCH] ELFCodeLoader: Fixes Proton Experimental on 48-bit VA systems This is a tricky situation that wine-preloader allocates the lower 32MB of stack space through fixed address mmap with MAP_FIXED. They can't use mmap with an address hint nor MAP_FIXED_NOREPLACE because it changes behaviour. mmap won't give you the allocation inside the stack space even if you check `/proc/self/maps` that space isn't yet allocated. The growable space of the stack blocks those allocations. So the wine peeps might be SOL if they actually require this allocation to exist. To replicate this, allocate the application stack at the same location using an address hint. This will give us the correct region on a 48-bit VA system, while also letting it select a different region on a 36-bit VA system. --- Source/Tests/ELFCodeLoader2.h | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Source/Tests/ELFCodeLoader2.h b/Source/Tests/ELFCodeLoader2.h index a95a3c795..1cc375c6b 100644 --- a/Source/Tests/ELFCodeLoader2.h +++ b/Source/Tests/ELFCodeLoader2.h @@ -353,7 +353,36 @@ class ELFCodeLoader2 final : public FEXCore::CodeLoader { // // This is still technically a memory leak if the stack grows, but since the primary thread's stack only gets destroyed on process close, this is // fine. - StackPointer = reinterpret_cast(Mapper(nullptr, StackSize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0)); + + // Stacks need to be allocated at the hint location just like on a real x86 system. + // These are 128MB regions on both x86-64 and x86. + // + // These are required to be in the correct location taking up the appropriate 128MB of space, otherwise the wine preloader crashes FEX. + // This is due to the wine-preloader hardcoding addresses [0x7FFFFE000000 - 0x7FFFFFFF0000) as a top-down + // allocation region. They use mmap with MAP_FIXED, ignoring any previously mapped area at that location and overwriting it. + // Wine-preloader is expecting to allocate 32MB out of the total 128MB stack space in this case. Leaving 96MB for the application. + // + // If FEX doesn't allocate the stack in this region (nullptr mmap hint) then later allocations that FEX does will /eventually/ + // end up inside of this address space that wine allocates. This usually ends up being a JIT CodeBuffer, which zeroes the memory and faults with a + // SIGILL. + // + // On the upside, this more accurately emulates how the kernel allocates stack space for the application when hinting at the location. + // + void* StackPointerBase{}; + uint64_t StackHint = Is64BitMode() ? STACK_HINT_64 : STACK_HINT_32; + + // Allocate the base of the full 128MB stack range. + StackPointerBase = Mapper(reinterpret_cast(StackHint), FULL_STACK_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN | MAP_NORESERVE, -1, 0); + + if (StackPointerBase == reinterpret_cast(~0ULL)) { + LogMan::Msg::EFmt("Allocating stack failed"); + return false; + } + + // Allocate with permissions the 8MB of regular stack size. + StackPointer = reinterpret_cast(Mapper( + reinterpret_cast(reinterpret_cast(StackPointerBase) + FULL_STACK_SIZE - StackSize()), + StackSize(), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0)); if (StackPointer == ~0ULL) { LogMan::Msg::EFmt("Allocating stack failed"); @@ -770,6 +799,9 @@ class ELFCodeLoader2 final : public FEXCore::CodeLoader { constexpr static uint64_t BRK_SIZE = 8 * 1024 * 1024; constexpr static uint64_t STACK_SIZE = 8 * 1024 * 1024; + constexpr static uint64_t FULL_STACK_SIZE = 128 * 1024 * 1024; + constexpr static uint64_t STACK_HINT_32 = 0xFFFFE000 - FULL_STACK_SIZE; + constexpr static uint64_t STACK_HINT_64 = 0x7FFFFFFFF000 - FULL_STACK_SIZE; std::vector Args; std::vector EnvironmentVariables;