https://bugs.winehq.org/show_bug.cgi?id=33159
Ken Thomases ken@codeweavers.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |ken@codeweavers.com
--- Comment #15 from Ken Thomases ken@codeweavers.com --- Here's my understanding of the issue:
* On OS X, Wine reserves the WINE_DOS area from 0x00001000 to 0x40001000 by defining a zero-fill segment in the loader executable. It also reserves the WINE_SHAREDHEAP area from 0x7f000000 to 0x82000000, but that's not particularly relevant in this case.
* The game's Activation.dll can only be loaded at 0x40000000 to 0x4010D000.
* It would not actually be a problem if the DLL's address range were completely inside Wine's reserved area nor completely outside it. The problem is that it straddles the boundary.
* The size of the WINE_DOS area was chosen somewhat arbitrarily. It could be adjusted to accommodate this particular DLL, but that is as likely to cause problems for other DLLs as fix them.
* On Linux, when running 32-bit Wine, the preloader reserves:
0x00000000 - 0x00010000 low 64K 0x00010000 - 0x00110000 DOS Area 0x00110000 - 0x68000000 low-memory area 0x7f000000 - 0x82000000 top-down allocations + shared heap + virtual heap
plus whatever address range the main executable needs to load into.
As you can see, this includes everything that's reserved on OS X and substantially more.
* The difference comes in because, when ntdll has finished loading and initializing the app, it calls virtual_release_address_space() and that unreserves much of that area on Linux but not OS X. (Things are somewhat different for large-address-space-aware apps.)
This can't be done on OS X because the reserved areas are "owned" by dyld, the dynamic loader. It wouldn't be a problem if these unreserved areas were used for loading a Windows-style DLL. But once they are unreserved, they could be used to load platform-native dynamic libraries. Since loading those is a job for dyld and it thought it already owned that memory and dedicated it to the segments of the main executable, it freaks out if a new library is loaded into that same address range.
---
I have looked into trying to find ways to convince the kernel and/or dyld to map the ranges we want reserved early in the process lifetime but leave them out of dyld's record-keeping so it wouldn't freak out if they later get used.
At one point, I thought of including an LC_SEGMENT_64 load command, that's normally only used in 64-bit images, in our 32-bit loader. At the time, it seemed that the kernel would map it but dyld would ignore it. However, both the kernel and dyld have tightened up their sanity checking, probably for security, and refuse to load such an executable.
Unfortunately, I think that it probably won't be feasible to implement a preloader on OS X, either. The kernel and dyld are fairly intimately intertwined. Loading an executable is a complicated process that's split between them. The source code for both is theoretically open, but I'm pretty sure that dyld's is incomplete. (For example, it seems to have some capability to load PE executables, but the code for that is not in the source base.)
Also, any preloader would have to do mmap() or mach_vm_allocate() calls to reserve the desired address ranges in a way that they could be subsequently released. On Linux, the preloader just uses a syscall to do the mmap() call. However, on OS X, the syscall interface is not the public interface and Apple does not commit to maintaining binary compatibility across versions of the OS. Instead, Apple states that the system library interfaces are the public and stable interfaces. But we need to reserve our address ranges before the system libraries are initialized because they will use those ranges if they get a chance. So, there's no good way to actually map/reserve the address ranges we want.
This inability to reliably use mmap() without loading the system library also defeats another scheme I had considered. I was thinking that we could craft a dynamic library that didn't link with _anything_ else. It would be the first library in the loader's dependencies. It would have an initializer function that would reserve the desired address ranges. That could theoretically be run before the initializers of the system library. Except it can't, because it would have to mmap() and that a) can't be done until after the system library is initialized, and b) would create a dependency on the system library that would force it to initialize first, anyway.