Add a '__program_vars' section to the preloader, to make it a correct macOS 10.7 binary. This fixes Wine crashing on macOS Sonoma, and GStreamer crashing when initialized by Wine (on macOS Monterey and later).
However this causes dyld to initialize libSystem before the preloader runs, resulting in system allocations throughout the ranges that need to be reserved.
To prevent this, use a zerofill section to reserve the entire low 8GB of address space for use by Wine.
This also has the benefit of reserving as much address space as possible for Wow64. Additionally, this MR is a step towards not using the preloader entirely on 64-bit macOS (it relies on very unsupported APIs and methods).
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/ntdll/unix/virtual.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index 392ac4d9966..62fc1c9dd1f 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -4128,6 +4128,13 @@ static void virtual_release_address_space(void) char *base = (char *)0x82000000; char *limit = get_wow_user_space_limit();
+#if defined(__APPLE__) && !defined(__i386__) + /* On 64-bit macOS, don't release any address space. + * It needs to be reserved for use by Wow64 + */ + return; +#endif + if (limit > (char *)0xfffff000) return; /* 64-bit limit, nothing to do */
if (limit > base)
From: Brendan Shanks bshanks@codeweavers.com
--- configure.ac | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/configure.ac b/configure.ac index c9ed1c8c431..da31051d8de 100644 --- a/configure.ac +++ b/configure.ac @@ -678,20 +678,23 @@ case $host_os in
WINELOADER_LDFLAGS="-Wl,-pie,-segalign,0x1000,-pagezero_size,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist"
- wine_can_build_preloader=yes - WINEPRELOADER_LDFLAGS="-nostartfiles -nodefaultlibs -e _start -ldylib1.o -Wl,-image_base,0x7d400000,-segalign,0x1000,-pagezero_size,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist,-segaddr,WINE_4GB_RESERVE,0x100000000" - WINE_TRY_CFLAGS([-Wl,-no_new_main -e _main], - [WINEPRELOADER_LDFLAGS="-Wl,-no_new_main $WINEPRELOADER_LDFLAGS" - WINE_TRY_CFLAGS([-Wl,-no_new_main -e _main -mmacosx-version-min=10.7 -nostartfiles -nodefaultlibs], - [WINEPRELOADER_LDFLAGS="-mmacosx-version-min=10.7 $WINEPRELOADER_LDFLAGS"], - [wine_can_build_preloader=no])], - [WINE_TRY_CFLAGS([-mmacosx-version-min=10.7 -nostartfiles -nodefaultlibs], - [WINEPRELOADER_LDFLAGS="-mmacosx-version-min=10.7 $WINEPRELOADER_LDFLAGS"], - [wine_can_build_preloader=no])]) + case $host_cpu in + *i[[3456]]86*|*x86_64*) wine_can_build_preloader=yes ;; + *) wine_can_build_preloader=no ;; + esac + if test "$wine_can_build_preloader" = "yes" then + WINEPRELOADER_LDFLAGS="-nostartfiles -nodefaultlibs -e _start -ldylib1.o -mmacosx-version-min=10.7 -Wl,-no_new_main,-image_base,0x7d400000,-segalign,0x1000,-pagezero_size,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist" WINE_TRY_CFLAGS([-Wl,-no_pie], [WINEPRELOADER_LDFLAGS="-Wl,-no_pie $WINEPRELOADER_LDFLAGS"]) + case $host_cpu in + *i[[3456]]86*) + ;; + *x86_64*) + WINEPRELOADER_LDFLAGS="-Wl,-segalign,0x1000,-segaddr,WINE_4GB_RESERVE,0x100000000 $WINEPRELOADER_LDFLAGS" + ;; + esac dnl If preloader is used, the loader needs to be an LC_UNIXTHREAD binary to avoid AppKit/Core Animation problems. WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -mmacosx-version-min=10.7" else
From: Brendan Shanks bshanks@codeweavers.com
A zerofill section is the only way to reserve address space and prevent system frameworks from using it, including preventing allocations before any preloader code runs: - starting with Ventura, dyld allocates private memory from 0x1000-0x81000. This breaks EXEs that have an image base of 0x10000. - Rosetta allocates memory starting at 0x100000000, which breaks EXEs based there. - starting with Monterey, for proper 10.7 binaries (which include a __program_vars section), libSystem initializes itself before the preloader runs. This fragments the <4GB address space which is needed for Wow64.
This will need to be adjusted if any EXEs based at 0x200000000 or higher are found. --- configure.ac | 5 +++-- loader/preloader_mac.c | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/configure.ac b/configure.ac index da31051d8de..71c9340b30d 100644 --- a/configure.ac +++ b/configure.ac @@ -685,14 +685,15 @@ case $host_os in
if test "$wine_can_build_preloader" = "yes" then - WINEPRELOADER_LDFLAGS="-nostartfiles -nodefaultlibs -e _start -ldylib1.o -mmacosx-version-min=10.7 -Wl,-no_new_main,-image_base,0x7d400000,-segalign,0x1000,-pagezero_size,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist" + WINEPRELOADER_LDFLAGS="-nostartfiles -nodefaultlibs -e _start -ldylib1.o -mmacosx-version-min=10.7 -Wl,-no_new_main,-segalign,0x1000,-pagezero_size,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist" WINE_TRY_CFLAGS([-Wl,-no_pie], [WINEPRELOADER_LDFLAGS="-Wl,-no_pie $WINEPRELOADER_LDFLAGS"]) case $host_cpu in *i[[3456]]86*) + WINEPRELOADER_LDFLAGS="-Wl,-image_base,0x7d400000 $WINEPRELOADER_LDFLAGS" ;; *x86_64*) - WINEPRELOADER_LDFLAGS="-Wl,-segalign,0x1000,-segaddr,WINE_4GB_RESERVE,0x100000000 $WINEPRELOADER_LDFLAGS" + WINEPRELOADER_LDFLAGS="-Wl,-image_base,0x200000000,-segalign,0x1000,-segaddr,WINE_RESERVE,0x1000 $WINEPRELOADER_LDFLAGS" ;; esac dnl If preloader is used, the loader needs to be an LC_UNIXTHREAD binary to avoid AppKit/Core Animation problems. diff --git a/loader/preloader_mac.c b/loader/preloader_mac.c index 4e91128c575..11723cdaac9 100644 --- a/loader/preloader_mac.c +++ b/loader/preloader_mac.c @@ -49,18 +49,15 @@ #include "main.h"
#if defined(__x86_64__) -/* Rosetta on Apple Silicon allocates memory starting at 0x100000000 (the 4GB line) - * before the preloader runs, which prevents any nonrelocatable EXEs with that - * base address from running. - * - * This empty linker section forces Rosetta's allocations (currently ~132 MB) - * to start at 0x114000000, and they should end below 0x120000000. +/* Reserve the low 8GB using a zero-fill section, this is the only way to + * prevent system frameworks from using any of it (including allocations + * before any preloader code runs) */ -__asm__(".zerofill WINE_4GB_RESERVE,WINE_4GB_RESERVE,___wine_4gb_reserve,0x14000000"); +__asm__(".zerofill WINE_RESERVE,WINE_RESERVE,___wine_reserve,0x1fffff000");
static const struct wine_preload_info zerofill_sections[] = { - { (void *)0x000100000000, 0x14000000 }, /* WINE_4GB_RESERVE section */ + { (void *)0x000000001000, 0x1fffff000 }, /* WINE_RESERVE section */ { 0, 0 } /* end of list */ }; #else @@ -92,10 +89,7 @@ static struct wine_preload_info preload_info[] = { (void *)0x00110000, 0x67ef0000 }, /* low memory area */ { (void *)0x7f000000, 0x03000000 }, /* top-down allocations + shared user data + virtual heap */ #else /* __i386__ */ - { (void *)0x000000010000, 0x00100000 }, /* DOS area */ - { (void *)0x000000110000, 0x67ef0000 }, /* low memory area */ - { (void *)0x00007f000000, 0x00ff0000 }, /* 32-bit top-down allocations + shared user data */ - { (void *)0x000100000000, 0x14000000 }, /* WINE_4GB_RESERVE section */ + { (void *)0x000000001000, 0x1fffff000 }, /* WINE_RESERVE section */ { (void *)0x7ff000000000, 0x01ff0000 }, /* top-down allocations + virtual heap */ #endif /* __i386__ */ { 0, 0 }, /* PE exe range set with WINEPRELOADRESERVE */ @@ -439,7 +433,7 @@ static int preloader_overlaps_range( const void *start, const void *end ) struct target_segment_command *seg = (struct target_segment_command*)cmd; const void *seg_start = (const void*)(seg->vmaddr + slide); const void *seg_end = (const char*)seg_start + seg->vmsize; - static const char reserved_segname[] = "WINE_4GB_RESERVE"; + static const char reserved_segname[] = "WINE_RESERVE";
if (!wld_strncmp( seg->segname, reserved_segname, sizeof(reserved_segname)-1 )) continue; @@ -653,7 +647,9 @@ static void set_program_vars( void *stack, void *mod )
void *wld_start( void *stack, int *is_unix_thread ) { +#ifdef __i386__ struct wine_preload_info builtin_dlls = { (void *)0x7a000000, 0x02000000 }; +#endif struct wine_preload_info **wine_main_preload_info; char **argv, **p, *reserve = NULL; struct target_mach_header *mh; @@ -692,15 +688,19 @@ void *wld_start( void *stack, int *is_unix_thread ) } }
+#ifdef __i386__ if (!map_region( &builtin_dlls )) builtin_dlls.size = 0; +#endif
/* load the main binary */ if (!(mod = pdlopen( argv[1], RTLD_NOW ))) fatal_error( "%s: could not load binary\n", argv[1] );
+#ifdef __i386__ if (builtin_dlls.size) wld_munmap( builtin_dlls.addr, builtin_dlls.size ); +#endif
/* store pointer to the preload info into the appropriate main binary variable */ wine_main_preload_info = pdlsym( mod, "wine_main_preload_info" );
From: Brendan Shanks bshanks@codeweavers.com
This is needed to be a correct macOS 10.7 binary. --- loader/preloader_mac.c | 47 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-)
diff --git a/loader/preloader_mac.c b/loader/preloader_mac.c index 11723cdaac9..e35444fd133 100644 --- a/loader/preloader_mac.c +++ b/loader/preloader_mac.c @@ -106,6 +106,39 @@ void *__stack_chk_guard = 0; void __stack_chk_fail_local(void) { return; } void __stack_chk_fail(void) { return; }
+/* Binaries targeting 10.6 and 10.7 contain the __program_vars section, and + * dyld4 (starting in Monterey) does not like it to be missing: + * - running vmmap on a Wine process prints this warning: + * "Process exists but has not fully started -- dyld has initialized but libSystem has not" + * - because libSystem is not initialized, dlerror() always returns NULL (causing GStreamer + * to crash on init). + * - starting with macOS Sonoma, Wine crashes on launch if libSystem is not initialized. + * + * Adding __program_vars fixes those issues, and also allows more of the vars to + * be set correctly by the preloader for the loaded binary. + * + * See also: + * https://github.com/apple-oss-distributions/Csu/blob/Csu-88/crt.c#L42 + * https://github.com/apple-oss-distributions/dyld/blob/dyld-1042.1/common/MachOAnalyzer.cpp#L2185 + */ +int NXArgc = 0; +const char** NXArgv = NULL; +const char** environ = NULL; +const char* __progname = NULL; + +extern void* __dso_handle; +struct ProgramVars +{ + void* mh; + int* NXArgcPtr; + const char*** NXArgvPtr; + const char*** environPtr; + const char** __prognamePtr; +}; +__attribute__((used)) static struct ProgramVars pvars +__attribute__ ((section ("__DATA,__program_vars"))) = { &__dso_handle, &NXArgc, &NXArgv, &environ, &__progname }; + + /* * When 'start' is called, stack frame looks like: * @@ -620,15 +653,16 @@ static void fixup_stack( void *stack ) static void set_program_vars( void *stack, void *mod ) { int *pargc; - char **argv, **env; + const char **argv, **env; int *wine_NXArgc = pdlsym( mod, "NXArgc" ); - char ***wine_NXArgv = pdlsym( mod, "NXArgv" ); - char ***wine_environ = pdlsym( mod, "environ" ); + const char ***wine_NXArgv = pdlsym( mod, "NXArgv" ); + const char ***wine_environ = pdlsym( mod, "environ" );
pargc = stack; - argv = (char **)pargc + 1; + argv = (const char **)pargc + 1; env = &argv[*pargc-1] + 2;
+ /* set vars in the loaded binary */ if (wine_NXArgc) *wine_NXArgc = *pargc; else @@ -643,6 +677,11 @@ static void set_program_vars( void *stack, void *mod ) *wine_environ = env; else wld_printf( "preloader: Warning: failed to set environ\n" ); + + /* set vars in the __program_vars section */ + NXArgc = *pargc; + NXArgv = argv; + environ = env; }
void *wld_start( void *stack, int *is_unix_thread )