From cfa0dd9dd91eea0e401347ba41141b9c0c23131d Mon Sep 17 00:00:00 2001 From: Brendan Shanks Date: Tue, 20 Jun 2023 14:20:02 -0700 Subject: [PATCH] loader: On 64-bit macOS, reserve the low 8GB using a zerofill section. 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 | 5 +++-- configure.ac | 5 +++-- loader/preloader_mac.c | 26 +++++++++++++------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/configure b/configure index 7fb1685316b..4e021f19b5d 100755 --- a/configure +++ b/configure @@ -9487,7 +9487,7 @@ fi ;; 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" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports -Wl,-no_pie" >&5 printf %s "checking whether the compiler supports -Wl,-no_pie... " >&6; } if test ${ac_cv_cflags__Wl__no_pie+y} @@ -9518,9 +9518,10 @@ then : fi 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 WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -mmacosx-version-min=10.7" 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" );