[PATCH v4 0/4] MR11638: Mac: Support Apple silicon mac for running native windows on arm softwares.
The kernel on Apple Silicon enforces a default 4GB page zero for executables and rejects smaller ones at exec time, so the loader is built as a standard PIE executable instead of a dlopen-style image (no fixed load address, following the OS ASLR policy). The Windows shared user data and TEB blocks cannot live at their usual fixed low addresses, so they are allocated at OS-chosen addresses and the PE side fetches the shared user data address through a new unix call. The PE image section protections are applied in two passes so that, on hosts with a 16KB page size, an executable page is never also writable (macOS W^X enforcement). Finally, the ARM64 syscall dispatcher returns into PE code with br instead of ret, since ret x16/x17 on arm64e clears the x18 TEB register, which crashed the first PE execution with a NULL TEB. ntdll: Make the ARM64 syscall path and TEB access reliable on Apple Silicon. The syscall and unix call dispatchers relied on the x18 register holding the TEB when entered from PE code, but on Apple Silicon x18 is cleared when the CPU first executes a fresh code page, and TPIDR_EL0 is reset by macOS on signal delivery. Both could fault or dispatch a syscall to the wrong thread's frame. The dispatchers now find the current thread's syscall frame through the pthread thread data, which is reliable, and save the full context there. The context save previously clobbered x19 (it held the user stack pointer for the argument marshalling but was also written to the frame), which corrupted heap operations in the caller. The PE-side NtCurrentTeb is now a real ntdll export instead of an inline x18 read. It uses the per-thread TPIDR_EL0 value when valid, otherwise it validates the cached TEB against the current stack pointer and falls back to a unix call to get the current thread's TEB from the pthread data. Finally, loader_init now always refreshes the shared user data address and the TEB on Apple Silicon, fixing the unmapped 0x7ffe0000 dereferences. ntdll: Fix the unix call dispatcher and syscall return on Apple Silicon. The unix call dispatcher found its syscall frame through the racy cached TEB, which could select another thread's frame when TPIDR_EL0 was reset; it now uses the same pthread-data based helper as the syscall dispatcher. The syscall return stub reloaded the TEB from the saved frame, but a nested syscall performed by the service handler overwrites frame->x18 with its own value, so the stub now reads the current TEB directly. With these fixes wineboot completes the prefix initialization. -- v4: [win32u] nothing should be before config.h https://gitlab.winehq.org/wine/wine/-/merge_requests/11638
From: trcrsired <uwgghhbcad@gmail.com> The kernel on Apple Silicon enforces a default 4GB page zero for executables and rejects smaller ones at exec time, so the loader is built as a standard PIE executable instead of a dlopen-style image (no fixed load address, following the OS ASLR policy). The Windows shared user data and TEB blocks cannot live at their usual fixed low addresses, so they are allocated at OS-chosen addresses and the PE side fetches the shared user data address through a new unix call. The PE image section protections are applied in two passes so that, on hosts with a 16KB page size, an executable page is never also writable (macOS W^X enforcement). Finally, the ARM64 syscall dispatcher returns into PE code with br instead of ret, since ret x16/x17 on arm64e clears the x18 TEB register, which crashed the first PE execution with a NULL TEB. ntdll: Make the ARM64 syscall path and TEB access reliable on Apple Silicon. The syscall and unix call dispatchers relied on the x18 register holding the TEB when entered from PE code, but on Apple Silicon x18 is cleared when the CPU first executes a fresh code page, and TPIDR_EL0 is reset by macOS on signal delivery. Both could fault or dispatch a syscall to the wrong thread's frame. The dispatchers now find the current thread's syscall frame through the pthread thread data, which is reliable, and save the full context there. The context save previously clobbered x19 (it held the user stack pointer for the argument marshalling but was also written to the frame), which corrupted heap operations in the caller. The PE-side NtCurrentTeb is now a real ntdll export instead of an inline x18 read. It uses the per-thread TPIDR_EL0 value when valid, otherwise it validates the cached TEB against the current stack pointer and falls back to a unix call to get the current thread's TEB from the pthread data. Finally, loader_init now always refreshes the shared user data address and the TEB on Apple Silicon, fixing the unmapped 0x7ffe0000 dereferences. ntdll: Fix the unix call dispatcher and syscall return on Apple Silicon. The unix call dispatcher found its syscall frame through the racy cached TEB, which could select another thread's frame when TPIDR_EL0 was reset; it now uses the same pthread-data based helper as the syscall dispatcher. The syscall return stub reloaded the TEB from the saved frame, but a nested syscall performed by the service handler overwrites frame->x18 with its own value, so the stub now reads the current TEB directly. With these fixes wineboot completes the prefix initialization. --- configure | 25 ++++ configure.ac | 46 ++++++++ dlls/kernel32/kernel_main.c | 11 ++ dlls/kernel32/kernel_private.h | 2 + dlls/kernel32/process.c | 2 - dlls/kernel32/sync.c | 2 - dlls/kernelbase/sync.c | 24 ++-- dlls/ntdll/loader.c | 18 +++ dlls/ntdll/ntdll.spec | 2 + dlls/ntdll/signal_arm64.c | 45 +++++++ dlls/ntdll/thread.c | 18 +++ dlls/ntdll/unix/loader.c | 20 ++++ dlls/ntdll/unix/signal_arm64.c | 209 ++++++++++++++++++++++++++++----- dlls/ntdll/unix/unix_private.h | 4 + dlls/ntdll/unix/virtual.c | 99 +++++++++++++++- dlls/ntdll/unixlib.h | 18 +++ dlls/ntoskrnl.exe/ntoskrnl.c | 2 +- dlls/win32u/message.c | 6 + include/config.h.in | 4 + include/winnt.h | 6 + 20 files changed, 513 insertions(+), 50 deletions(-) diff --git a/configure b/configure index f6cfa96291a..cff47fab188 100755 --- a/configure +++ b/configure @@ -4743,6 +4743,11 @@ case $host_os in *\ *) host_os=`printf '%s\n' "$host_os" | sed 's/ /-/g'`;; esac +case $host_cpu-$host_os in + aarch64-*darwin*) wine_use_dynamic_user_shared_data=yes ;; + *) wine_use_dynamic_user_shared_data=no ;; +esac + if test "x$enable_sast" = xyes then : CC=${CC:-clang} @@ -10487,6 +10492,11 @@ then : as_fn_append ${wine_arch}_EXTRACFLAGS " -fasync-exceptions" fi } + if test "$wine_arch" = "aarch64" -a "$wine_use_dynamic_user_shared_data" = "yes" + then + as_fn_append ${wine_arch}_EXTRACFLAGS " -DHAVE_DYNAMIC_USER_SHARED_DATA" + fi + done CC=$saved_CC @@ -12431,6 +12441,12 @@ else case e in #( esac fi ;; + aarch64) + WINELOADER_LDFLAGS="-mmacosx-version-min=12 -Wl,-segalign,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist" + CFLAGS="$CFLAGS -mmacosx-version-min=12" + LDFLAGS="$LDFLAGS -mmacosx-version-min=12" + wine_use_preloader=no + ;; *) wine_use_preloader=no ;; esac @@ -20782,6 +20798,7 @@ else case e in #( e) case $HOST_ARCH in i386) wine_cv_64bit_compare_swap="-march=i586" ;; arm) wine_cv_64bit_compare_swap="-march=armv7-a" ;; + aarch64) wine_cv_64bit_compare_swap="none needed" ;; *) wine_cv_64bit_compare_swap="unknown" ;; esac if test "x$wine_cv_64bit_compare_swap" != xunknown @@ -22307,6 +22324,14 @@ then : fi ;; esac +case $host_cpu-$host_os in + aarch64-*darwin*) + +printf '%s\n' "#define HAVE_DYNAMIC_USER_SHARED_DATA 1" >>confdefs.h + + ;; +esac + enable_vcruntime140_1=${enable_vcruntime140_1:-x86_64,arm64ec} diff --git a/configure.ac b/configure.ac index a31d302dd4b..fdaa2ca7a8b 100644 --- a/configure.ac +++ b/configure.ac @@ -93,6 +93,15 @@ AC_ARG_WITH(wine64, AS_HELP_STRING([--with-wine64=DIR],[use the 64-bit Wine i AC_CANONICAL_HOST AC_SUBST(srcdir) +dnl On Apple Silicon the fixed Windows KUSER_SHARED_DATA address 0x7ffe0000 is +dnl inside the kernel page zero and cannot be mapped, so PE code must fetch the +dnl address through ntdll's __wine_get_user_shared_data() export instead of a +dnl hardcoded pointer. +case $host_cpu-$host_os in + aarch64-*darwin*) wine_use_dynamic_user_shared_data=yes ;; + *) wine_use_dynamic_user_shared_data=no ;; +esac + AS_IF([test "x$enable_sast" = xyes],[CC=${CC:-clang} with_mingw=${with_mingw:-clang} AC_CHECK_PROGS(SARIF_CONVERTER,sarif-converter,false)]) @@ -683,6 +692,14 @@ Please install mingw-w64, clang/llvm-dlltool/lld, or llvm-mingw.]) WINE_TRY_PE_CFLAGS([-Wabsolute-value]) WINE_TRY_PE_CFLAGS([-fasync-exceptions -DMIN_CLANG_VERSION=19], AS_VAR_APPEND([${wine_arch}_EXTRACFLAGS],[" -fasync-exceptions"])) + dnl On Apple Silicon the fixed Windows KUSER_SHARED_DATA address 0x7ffe0000 + dnl cannot be mapped, so the PE code must use ntdll's + dnl __wine_get_user_shared_data() export instead of a hardcoded pointer. + if test "$wine_arch" = "aarch64" -a "$wine_use_dynamic_user_shared_data" = "yes" + then + AS_VAR_APPEND([${wine_arch}_EXTRACFLAGS],[" -DHAVE_DYNAMIC_USER_SHARED_DATA"]) + fi + done CC=$saved_CC @@ -992,6 +1009,22 @@ case $host_os in WINELOADER_LDFLAGS="$WINELOADER_LDFLAGS -Wl,-no_pie,-image_base,0x200000000,-no_huge,-no_fixup_chains,-segalign,0x1000,-segaddr,WINE_RESERVE,0x1000,-segaddr,WINE_TOP_DOWN,0x7ff000000000"], [wine_use_preloader=yes]) ;; + aarch64) + dnl On Apple Silicon the kernel enforces a default 4GB __PAGEZERO for + dnl executables and rejects smaller ones with SIGKILL at exec time, so + dnl we cannot build the loader as a dlopen-style image with a reduced + dnl page zero. Build it as a standard PIE executable instead, and let + dnl ntdll reserve the address space needed by Windows apps at runtime + dnl (following the OS ASLR policy, no fixed load addresses). + dnl Also build with a macOS 12 deployment target so the kernel keeps + dnl x18 (the Windows ARM64 TEB register) preserved across syscalls and + dnl signals; macOS 13+ would otherwise clobber it for binaries built + dnl against a newer SDK. + WINELOADER_LDFLAGS="-mmacosx-version-min=12 -Wl,-segalign,0x1000,-sectcreate,__TEXT,__info_plist,loader/wine_info.plist" + CFLAGS="$CFLAGS -mmacosx-version-min=12" + LDFLAGS="$LDFLAGS -mmacosx-version-min=12" + wine_use_preloader=no + ;; *) wine_use_preloader=no ;; esac @@ -1997,6 +2030,7 @@ then [case $HOST_ARCH in i386) wine_cv_64bit_compare_swap="-march=i586" ;; arm) wine_cv_64bit_compare_swap="-march=armv7-a" ;; + aarch64) wine_cv_64bit_compare_swap="none needed" ;; *) wine_cv_64bit_compare_swap="unknown" ;; esac if test "x$wine_cv_64bit_compare_swap" != xunknown @@ -2392,6 +2426,18 @@ case $host_cpu in *arm*) WINE_CHECK_DEFINE([__arm__]) ;; esac +dnl On Apple Silicon the fixed Windows KUSER_SHARED_DATA address 0x7ffe0000 is +dnl inside the kernel page zero and cannot be mapped, so PE code must fetch the +dnl address through ntdll's __wine_get_user_shared_data() export instead of a +dnl hardcoded pointer. +case $host_cpu-$host_os in + aarch64-*darwin*) + AC_DEFINE(HAVE_DYNAMIC_USER_SHARED_DATA, 1, + [Define to 1 on Apple Silicon, where the fixed KUSER_SHARED_DATA + address cannot be mapped and must be fetched through ntdll.]) + ;; +esac + dnl *** Check for modules to disable by default enable_vcruntime140_1=${enable_vcruntime140_1:-x86_64,arm64ec} diff --git a/dlls/kernel32/kernel_main.c b/dlls/kernel32/kernel_main.c index edf54e84b72..c884b8481c7 100644 --- a/dlls/kernel32/kernel_main.c +++ b/dlls/kernel32/kernel_main.c @@ -36,6 +36,13 @@ WINE_DEFAULT_DEBUG_CHANNEL(process); static STARTUPINFOA startup_infoA; +#ifdef HAVE_DYNAMIC_USER_SHARED_DATA +extern void * __cdecl __wine_get_user_shared_data(void); +const struct _KUSER_SHARED_DATA *user_shared_data; +#else +const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; +#endif + /*********************************************************************** * set_entry_point */ @@ -130,6 +137,10 @@ static BOOL process_attach( HMODULE module ) { RtlSetUnhandledExceptionFilter( UnhandledExceptionFilter ); +#ifdef HAVE_DYNAMIC_USER_SHARED_DATA + user_shared_data = __wine_get_user_shared_data(); +#endif + NtQuerySystemInformation( SystemBasicInformation, &system_info, sizeof(system_info), NULL ); kernelbase_global_data = KernelBaseGetGlobalData(); diff --git a/dlls/kernel32/kernel_private.h b/dlls/kernel32/kernel_private.h index 3c35a4b1bba..4faff34ce6a 100644 --- a/dlls/kernel32/kernel_private.h +++ b/dlls/kernel32/kernel_private.h @@ -42,6 +42,8 @@ static inline BOOL set_ntstatus( NTSTATUS status ) extern SYSTEM_BASIC_INFORMATION system_info; +extern const struct _KUSER_SHARED_DATA *user_shared_data; + extern WCHAR *FILE_name_AtoW( LPCSTR name, BOOL alloc ); extern DWORD FILE_name_WtoA( LPCWSTR src, INT srclen, LPSTR dest, INT destlen ); diff --git a/dlls/kernel32/process.c b/dlls/kernel32/process.c index 9bbb18d89dc..925e1382337 100644 --- a/dlls/kernel32/process.c +++ b/dlls/kernel32/process.c @@ -39,8 +39,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(process); -static const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; - typedef struct { LPSTR lpEnvAddress; diff --git a/dlls/kernel32/sync.c b/dlls/kernel32/sync.c index 5b2a8489a01..234c1803b1c 100644 --- a/dlls/kernel32/sync.c +++ b/dlls/kernel32/sync.c @@ -42,8 +42,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(sync); -static const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; - static void get_create_object_attributes( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nameW, SECURITY_ATTRIBUTES *sa, const WCHAR *name ) diff --git a/dlls/kernelbase/sync.c b/dlls/kernelbase/sync.c index 94036acfad4..9c06f9d997d 100644 --- a/dlls/kernelbase/sync.c +++ b/dlls/kernelbase/sync.c @@ -39,7 +39,15 @@ WINE_DEFAULT_DEBUG_CHANNEL(sync); -static const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; +extern void * __cdecl __wine_get_user_shared_data(void); + +static const struct _KUSER_SHARED_DATA *get_user_shared_data(void) +{ + static const struct _KUSER_SHARED_DATA *user_shared_data; + + if (!user_shared_data) user_shared_data = __wine_get_user_shared_data(); + return user_shared_data; +} /* check if current version is NT or Win95 */ static inline BOOL is_version_nt(void) @@ -181,7 +189,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH GetSystemTimes( FILETIME *idle, FILETIME *kernel, ULONG WINAPI DECLSPEC_HOTPATCH GetTickCount(void) { /* note: we ignore TickCountMultiplier */ - return user_shared_data->TickCount.LowPart; + return get_user_shared_data()->TickCount.LowPart; } @@ -194,10 +202,10 @@ ULONGLONG WINAPI DECLSPEC_HOTPATCH GetTickCount64(void) do { - high = user_shared_data->TickCount.High1Time; - low = user_shared_data->TickCount.LowPart; + high = get_user_shared_data()->TickCount.High1Time; + low = get_user_shared_data()->TickCount.LowPart; } - while (high != user_shared_data->TickCount.High2Time); + while (high != get_user_shared_data()->TickCount.High2Time); /* note: we ignore TickCountMultiplier */ return (ULONGLONG)high << 32 | low; } @@ -212,10 +220,10 @@ void WINAPI DECLSPEC_HOTPATCH QueryInterruptTime( ULONGLONG *time ) do { - high = user_shared_data->InterruptTime.High1Time; - low = user_shared_data->InterruptTime.LowPart; + high = get_user_shared_data()->InterruptTime.High1Time; + low = get_user_shared_data()->InterruptTime.LowPart; } - while (high != user_shared_data->InterruptTime.High2Time); + while (high != get_user_shared_data()->InterruptTime.High2Time); *time = (ULONGLONG)high << 32 | low; } diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c index d8b7a92bade..4c188ccfdb2 100644 --- a/dlls/ntdll/loader.c +++ b/dlls/ntdll/loader.c @@ -4444,10 +4444,28 @@ void loader_init( CONTEXT *context, void **entry ) ULONG_PTR cookie, port = 0; WINE_MODREF *wm; +#if defined(__aarch64__) && !defined(__arm64ec__) + __asm__ volatile( "msr tpidr_el0, %0" :: "r"(context->X18) ); +#endif + if (process_detaching) NtTerminateThread( GetCurrentThread(), 0 ); if (NtCurrentTeb()->SkipLoaderInit) return; +#ifdef HAVE_DYNAMIC_USER_SHARED_DATA + /* fetch the address of the shared user data page chosen by the unix side */ + { + struct get_shared_user_data_params params; + + if (!user_shared_data || user_shared_data == (void *)0x7ffe0000) + { + WINE_UNIX_CALL( unix_get_shared_user_data, ¶ms ); + if (*params.data) user_shared_data = *params.data; + else ERR( "failed to get the shared user data address\n" ); + } + } +#endif + RtlEnterCriticalSection( &loader_section ); if (!imports_fixup_done) diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 10d42eeb138..1b74402a4b1 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -214,6 +214,7 @@ @ stdcall -syscall NtCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) # @ stub NtCreateWaitablePort @ stdcall -arch=i386 NtCurrentTeb() +@ stdcall -arch=arm64 NtCurrentTeb() @ stdcall -syscall NtDebugActiveProcess(long long) @ stdcall -syscall NtDebugContinue(long ptr long) @ stdcall -syscall=0x0034 NtDelayExecution(long ptr) @@ -1781,3 +1782,4 @@ @ cdecl wine_get_version() @ cdecl wine_get_build_id() @ cdecl wine_get_host_version(ptr ptr) +@ cdecl -norelay __wine_get_user_shared_data() diff --git a/dlls/ntdll/signal_arm64.c b/dlls/ntdll/signal_arm64.c index e560718c770..d1dea21d421 100644 --- a/dlls/ntdll/signal_arm64.c +++ b/dlls/ntdll/signal_arm64.c @@ -837,8 +837,53 @@ __ASM_GLOBAL_FUNC( RtlUserThreadStart, /****************************************************************** * LdrInitializeThunk (NTDLL.@) */ +volatile ULONG_PTR __wine_current_teb; + +#ifdef __WINE_PE_BUILD +/*********************************************************************** + * NtCurrentTeb (NTDLL.@) + */ +struct _TEB * WINAPI NtCurrentTeb(void) +{ + ULONG_PTR t, sp; + struct _TEB *teb; + + __asm__ volatile( "mrs %0, tpidr_el0" : "=r"(t) ); + if (t > 0x100000000) /* per-thread TEB in the EL0 thread ID register */ + { + __wine_current_teb = t; + return (struct _TEB *)t; + } + + __asm__ volatile( "mov %0, sp" : "=r"(sp) ); + teb = (struct _TEB *)__wine_current_teb; + if (teb && sp >= (ULONG_PTR)teb->Tib.StackLimit && sp <= (ULONG_PTR)teb->Tib.StackBase) + return teb; /* cached TEB belongs to the current thread */ + + /* tpidr_el0 was reset (fresh code page or signal) and the cached TEB + * belongs to another thread; ask the unix side for the current one. */ + { + struct get_current_teb_params params; + WINE_UNIX_CALL( unix_get_current_teb, ¶ms ); + if (*params.teb) + { + __wine_current_teb = (ULONG_PTR)*params.teb; + return *params.teb; + } + } + return (struct _TEB *)__wine_current_teb; +} +#endif + void WINAPI LdrInitializeThunk( CONTEXT *context, ULONG_PTR unk2, ULONG_PTR unk3, ULONG_PTR unk4 ) { + extern volatile ULONG_PTR __wine_current_teb; + __wine_current_teb = context->X18; + __asm__ volatile( "msr tpidr_el0, %0" :: "r"(context->X18) ); + { + struct register_teb_ptr_params params = { (struct _TEB **)&__wine_current_teb }; + WINE_UNIX_CALL( unix_register_teb_ptr, ¶ms ); + } loader_init( context, (void **)&context->X0 ); TRACE_(relay)( "\1Starting thread proc %p (arg=%p)\n", (void *)context->X0, (void *)context->X1 ); NtContinue( context, TRUE ); diff --git a/dlls/ntdll/thread.c b/dlls/ntdll/thread.c index fe083befbca..c410d95b379 100644 --- a/dlls/ntdll/thread.c +++ b/dlls/ntdll/thread.c @@ -35,7 +35,25 @@ WINE_DECLARE_DEBUG_CHANNEL(relay); WINE_DECLARE_DEBUG_CHANNEL(pid); WINE_DECLARE_DEBUG_CHANNEL(timestamp); +#ifdef HAVE_DYNAMIC_USER_SHARED_DATA +/* On Apple Silicon the fixed Windows address 0x7ffe0000 is inside the kernel + * page zero and cannot be mapped; loader_init() will set this to the address + * chosen by the unix side. */ +struct _KUSER_SHARED_DATA *user_shared_data; +#else struct _KUSER_SHARED_DATA *user_shared_data = (void *)0x7ffe0000; +#endif + +/*********************************************************************** + * __wine_get_user_shared_data (NTDLL.@) + * + * Return the address of the shared user data page, for use by other system + * dlls that need to read it (the fixed Windows address is not always usable). + */ +void * __cdecl __wine_get_user_shared_data(void) +{ + return user_shared_data; +} struct debug_info { diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 2b6dbf248f0..7c0a7b90b2e 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -998,6 +998,23 @@ static NTSTATUS load_so_dll( void *args ) return STATUS_INVALID_IMAGE_FORMAT; } +struct _TEB * volatile *current_teb_ptr; + +NTSTATUS unixcall_register_teb_ptr( void *args ) +{ + struct register_teb_ptr_params *params = args; + current_teb_ptr = params->teb_ptr; + return STATUS_SUCCESS; +} + +NTSTATUS unixcall_get_current_teb( void *args ) +{ + struct get_current_teb_params *params = args; + struct thread_data *data = get_thread_data(); + *params->teb = data ? data->teb : NULL; + return STATUS_SUCCESS; +} + static NTSTATUS unwind_builtin_dll( void *args ) { return STATUS_UNSUCCESSFUL; @@ -1016,6 +1033,9 @@ static const unixlib_entry_t unix_call_funcs[] = unixcall_wine_server_handle_to_fd, unixcall_wine_spawnvp, system_time_precise, + unixcall_get_shared_user_data, + unixcall_register_teb_ptr, + unixcall_get_current_teb, }; diff --git a/dlls/ntdll/unix/signal_arm64.c b/dlls/ntdll/unix/signal_arm64.c index 522ade368f0..d60468a3a20 100644 --- a/dlls/ntdll/unix/signal_arm64.c +++ b/dlls/ntdll/unix/signal_arm64.c @@ -34,6 +34,10 @@ #include <stdio.h> #include <sys/types.h> #include <unistd.h> + +struct syscall_frame * volatile current_syscall_frame; +struct _TEB * volatile current_teb; + #ifdef HAVE_SYS_PARAM_H # include <sys/param.h> #endif @@ -776,6 +780,10 @@ static void setup_raise_exception( struct thread_data *data, ucontext_t *sigcont SP_sig(sigcontext) = (ULONG_PTR)stack; PC_sig(sigcontext) = (ULONG_PTR)pKiUserExceptionDispatcher; REGn_sig(18, sigcontext) = (ULONG_PTR)data->teb; + __asm__ volatile( "msr tpidr_el0, %0" :: "r"(data->teb) ); + if (current_teb_ptr) *current_teb_ptr = data->teb; + current_teb = data->teb; + current_syscall_frame = get_syscall_frame( data ); } @@ -1113,6 +1121,7 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) EXCEPTION_RECORD rec = { .ExceptionAddress = (void *)PC_sig(sigcontext) }; DWORD64 esr = get_fault_esr( sigcontext ); + switch (ESR_ELx_EC(esr)) { case ESR_ELx_EC_IABT_LOW: @@ -1157,6 +1166,7 @@ static void segv_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void ill_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); CONTEXT context; @@ -1189,6 +1199,7 @@ static void ill_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void trap_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); CONTEXT context; @@ -1256,6 +1267,7 @@ static void trap_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void fpe_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); CONTEXT context; @@ -1317,6 +1329,7 @@ static void fpe_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void int_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + HANDLE handle; if (!p__wine_ctrl_routine) return; @@ -1333,6 +1346,7 @@ static void int_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void abrt_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); CONTEXT context; @@ -1352,6 +1366,7 @@ static void abrt_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void quit_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); @@ -1367,6 +1382,7 @@ static void quit_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void usr1_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); CHPE_V2_CPU_AREA_INFO *chpe; @@ -1411,6 +1427,7 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) */ static void usr2_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { + ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); struct syscall_frame *frame = get_syscall_frame( data ); @@ -1579,6 +1596,10 @@ void init_syscall_frame( LPTHREAD_START_ROUTINE entry, void *arg, TEB *teb ) frame->pc = (ULONG64)pLdrInitializeThunk; frame->x[0] = (ULONG64)ctx; frame->x[18] = (ULONG64)teb; + __asm__ volatile( "msr tpidr_el0, %0" :: "r"(teb) ); /* keep TEB in EL0 thread ID register too */ + if (current_teb_ptr) *current_teb_ptr = teb; + current_teb = teb; + current_syscall_frame = frame; syscall_frame_fixup_for_fastpath( frame ); pthread_sigmask( SIG_UNBLOCK, &server_block_set, NULL ); @@ -1629,41 +1650,116 @@ __ASM_GLOBAL_FUNC( signal_start_thread, */ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "hint 34\n\t" /* bti c */ - "ldr x10, [x18, #0x378]\n\t" /* thread_data->syscall_frame */ - "stp x18, x19, [x10, #0x90]\n\t" + "sub sp, sp, #0x2f0\n\t" /* scratch area on the user stack */ + "stp x0, x1, [sp]\n\t" + "stp x2, x3, [sp, #0x10]\n\t" + "stp x4, x5, [sp, #0x20]\n\t" + "stp x6, x7, [sp, #0x30]\n\t" + "stp x8, x9, [sp, #0x40]\n\t" + "stp x10, x11, [sp, #0x50]\n\t" + "stp x12, x13, [sp, #0x60]\n\t" + "stp x14, x15, [sp, #0x70]\n\t" + "stp x16, x17, [sp, #0x80]\n\t" + "str x30, [sp, #0x90]\n\t" + "mrs x17, NZCV\n\t" + "str x17, [sp, #0x98]\n\t" + "mrs x17, FPCR\n\t" + "str w17, [sp, #0xa0]\n\t" + "mrs x17, FPSR\n\t" + "str w17, [sp, #0xa4]\n\t" + "stp q0, q1, [sp, #0xb0]\n\t" + "stp q2, q3, [sp, #0xd0]\n\t" + "stp q4, q5, [sp, #0xf0]\n\t" + "stp q6, q7, [sp, #0x110]\n\t" + "stp q8, q9, [sp, #0x130]\n\t" + "stp q10, q11, [sp, #0x150]\n\t" + "stp q12, q13, [sp, #0x170]\n\t" + "stp q14, q15, [sp, #0x190]\n\t" + "stp q16, q17, [sp, #0x1b0]\n\t" + "stp q18, q19, [sp, #0x1d0]\n\t" + "stp q20, q21, [sp, #0x1f0]\n\t" + "stp q22, q23, [sp, #0x210]\n\t" + "stp q24, q25, [sp, #0x230]\n\t" + "stp q26, q27, [sp, #0x250]\n\t" + "stp q28, q29, [sp, #0x270]\n\t" + "stp q30, q31, [sp, #0x290]\n\t" + "bl " __ASM_NAME("__wine_get_syscall_frame_c") "\n\t" /* x0 = frame (via pthread thread data) */ + "mov x10, x0\n\t" + /* x18-x29 are callee-saved, still intact */ + "stp x18, x19, [x10, #0x90]\n\t" /* x[18], x[19]=user sp */ "stp x20, x21, [x10, #0xa0]\n\t" "stp x22, x23, [x10, #0xb0]\n\t" "stp x24, x25, [x10, #0xc0]\n\t" "stp x26, x27, [x10, #0xd0]\n\t" "stp x28, x29, [x10, #0xe0]\n\t" - "mov x19, sp\n\t" - "stp x9, x19, [x10, #0xf0]\n\t" - "mrs x9, NZCV\n\t" - "stp x30, x9, [x10, #0x100]\n\t" - "str w8, [x10, #0x120]\n\t" - "mrs x9, FPCR\n\t" - "str w9, [x10, #0x128]\n\t" - "mrs x9, FPSR\n\t" - "str w9, [x10, #0x12c]\n\t" + "ldp x0, x1, [sp]\n\t" + "stp x0, x1, [x10, #0x00]\n\t" + "ldp x2, x3, [sp, #0x10]\n\t" + "stp x2, x3, [x10, #0x10]\n\t" + "ldp x4, x5, [sp, #0x20]\n\t" + "stp x4, x5, [x10, #0x20]\n\t" + "ldp x6, x7, [sp, #0x30]\n\t" + "stp x6, x7, [x10, #0x30]\n\t" + "ldp x8, x9, [sp, #0x40]\n\t" + "stp x8, x9, [x10, #0x40]\n\t" /* x[8]=syscall num, x[9]=return addr */ + "ldp x12, x13, [sp, #0x60]\n\t" + "stp x12, x13, [x10, #0x60]\n\t" + "ldp x14, x15, [sp, #0x70]\n\t" + "stp x14, x15, [x10, #0x70]\n\t" + "add x19, sp, #0x2f0\n\t" /* user sp (the PE x19 is already saved above) */ + "stp x9, x19, [x10, #0xf0]\n\t" /* frame->lr = return addr, frame->sp = user sp */ + "ldr x30, [sp, #0x90]\n\t" + "ldr x17, [sp, #0x98]\n\t" /* NZCV */ + "stp x30, x17, [x10, #0x100]\n\t" /* lr, NZCV */ + "str w8, [x10, #0x120]\n\t" /* syscall_id */ + "ldr w17, [sp, #0xa0]\n\t" /* FPCR */ + "str w17, [x10, #0x128]\n\t" + "ldr w17, [sp, #0xa4]\n\t" /* FPSR */ + "str w17, [x10, #0x12c]\n\t" + "ldp q0, q1, [sp, #0xb0]\n\t" "stp q0, q1, [x10, #0x130]\n\t" + "ldp q2, q3, [sp, #0xd0]\n\t" "stp q2, q3, [x10, #0x150]\n\t" + "ldp q4, q5, [sp, #0xf0]\n\t" "stp q4, q5, [x10, #0x170]\n\t" + "ldp q6, q7, [sp, #0x110]\n\t" "stp q6, q7, [x10, #0x190]\n\t" + "ldp q8, q9, [sp, #0x130]\n\t" "stp q8, q9, [x10, #0x1b0]\n\t" + "ldp q10, q11, [sp, #0x150]\n\t" "stp q10, q11, [x10, #0x1d0]\n\t" + "ldp q12, q13, [sp, #0x170]\n\t" "stp q12, q13, [x10, #0x1f0]\n\t" + "ldp q14, q15, [sp, #0x190]\n\t" "stp q14, q15, [x10, #0x210]\n\t" + "ldp q16, q17, [sp, #0x1b0]\n\t" "stp q16, q17, [x10, #0x230]\n\t" + "ldp q18, q19, [sp, #0x1d0]\n\t" "stp q18, q19, [x10, #0x250]\n\t" + "ldp q20, q21, [sp, #0x1f0]\n\t" "stp q20, q21, [x10, #0x270]\n\t" + "ldp q22, q23, [sp, #0x210]\n\t" "stp q22, q23, [x10, #0x290]\n\t" + "ldp q24, q25, [sp, #0x230]\n\t" "stp q24, q25, [x10, #0x2b0]\n\t" + "ldp q26, q27, [sp, #0x250]\n\t" "stp q26, q27, [x10, #0x2d0]\n\t" + "ldp q28, q29, [sp, #0x270]\n\t" "stp q28, q29, [x10, #0x2f0]\n\t" + "ldp q30, q31, [sp, #0x290]\n\t" "stp q30, q31, [x10, #0x310]\n\t" - "mov x22, x10\n\t" - /* switch to kernel stack */ - "mov sp, x10\n\t" + /* restore the syscall args and number into registers */ + "ldp x0, x1, [sp]\n\t" + "ldp x2, x3, [sp, #0x10]\n\t" + "ldp x4, x5, [sp, #0x20]\n\t" + "ldp x6, x7, [sp, #0x30]\n\t" + "ldp x8, x9, [sp, #0x40]\n\t" + "ldp x16, x17, [sp, #0x80]\n\t" + "add sp, sp, #0x2f0\n\t" /* back to the user stack */ + "mov x19, sp\n\t" /* user sp for the argument marshalling */ + "mov x22, x10\n\t" + /* switch to kernel stack */ + "mov sp, x10\n\t" /* we're now on the kernel stack, stitch unwind info with previous frame */ __ASM_CFI_CFA_IS_AT2(x22, 0x98, 0x02) /* frame->syscall_cfa */ __ASM_CFI(".cfi_offset 29, -0xc0\n\t") @@ -1680,7 +1776,14 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, __ASM_CFI(".cfi_offset 28, -0x68\n\t") "and x20, x8, #0xfff\n\t" /* syscall number */ "ubfx x21, x8, #12, #2\n\t" /* syscall table number */ - "ldr x16, [x18, #0x370]\n\t" /* thread_data->syscall_table */ + "mrs x18, tpidr_el0\n\t" + "mov x17, #0x100000000\n\t" + "cmp x18, x17\n\t" + "b.hi 1f\n\t" + "adrp x18, " __ASM_NAME("current_teb") "@GOTPAGE\n\t" + "ldr x18, [x18, " __ASM_NAME("current_teb") "@GOTPAGEOFF]\n\t" + "ldr x18, [x18]\n\t" + "1:\tldr x16, [x18, #0x370]\n\t" /* thread_data->syscall_table */ "add x21, x16, x21, lsl #5\n\t" "ldr x16, [x21, #16]\n\t" /* table->ServiceLimit */ "cmp x20, x16\n\t" @@ -1747,12 +1850,13 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "msr FPCR, x17\n\t" "ldr w17, [sp, #0x12c]\n\t" "msr FPSR, x17\n" - "1:\tldp x16, x17, [sp, #0x100]\n\t" - "msr NZCV, x17\n\t" - "ldp x30, x17, [sp, #0xf0]\n\t" - /* switch to user stack */ - "mov sp, x17\n\t" - "ret x16\n" + "1:\tldp x16, x17, [sp, #0x100]\n\t" + "msr NZCV, x17\n\t" + "ldp x30, x17, [sp, #0xf0]\n\t" + /* switch to user stack */ + "mov sp, x17\n\t" + "msr tpidr_el0, x18\n\t" + "br x16\n" __ASM_LOCAL_LABEL("trace_syscall") ":\n\t" "stp x0, x1, [sp, #-0x40]!\n\t" @@ -1785,6 +1889,9 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher, "b " __ASM_LOCAL_LABEL("__wine_syscall_dispatcher_return") ) __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher_return, + "adrp x18, " __ASM_NAME("current_teb") "@GOTPAGE\n\t" + "ldr x18, [x18, " __ASM_NAME("current_teb") "@GOTPAGEOFF]\n\t" + "ldr x18, [x18]\n\t" /* TEB (frame->x18 may be clobbered by nested syscalls) */ "ldr w11, [x18, #0x380]\n\t" /* thread_data->syscall_trace */ "cbnz x11, " __ASM_LOCAL_LABEL("trace_syscall_ret") "\n\t" "b " __ASM_LOCAL_LABEL("__wine_syscall_dispatcher_return") ) @@ -1795,21 +1902,43 @@ __ASM_GLOBAL_FUNC( __wine_syscall_dispatcher_return, */ __ASM_GLOBAL_FUNC( __wine_unix_call_dispatcher, "hint 34\n\t" /* bti c */ - "ldr x10, [x18, #0x378]\n\t" /* thread_data->syscall_frame */ + "sub sp, sp, #0x160\n\t" /* scratch area on the user stack */ + "stp x0, x1, [sp]\n\t" + "str x2, [sp, #0x10]\n\t" + "str x30, [sp, #0x18]\n\t" + "mrs x17, NZCV\n\t" + "str x17, [sp, #0x20]\n\t" + "stp q8, q9, [sp, #0x30]\n\t" + "stp q10, q11, [sp, #0x50]\n\t" + "stp q12, q13, [sp, #0x70]\n\t" + "stp q14, q15, [sp, #0x90]\n\t" + "bl " __ASM_NAME("__wine_get_syscall_frame_c") "\n\t" /* x0 = frame (via pthread thread data) */ + "mov x10, x0\n\t" + /* x18-x29 are callee-saved, still intact */ "stp x18, x19, [x10, #0x90]\n\t" "stp x20, x21, [x10, #0xa0]\n\t" "stp x22, x23, [x10, #0xb0]\n\t" "stp x24, x25, [x10, #0xc0]\n\t" "stp x26, x27, [x10, #0xd0]\n\t" "stp x28, x29, [x10, #0xe0]\n\t" + "add x9, sp, #0x160\n\t" /* user sp */ + "ldr x17, [sp, #0x18]\n\t" /* lr (PE return address) */ + "stp x17, x9, [x10, #0xf0]\n\t" /* lr, sp */ + "str x17, [x10, #0x100]\n\t" /* pc */ + "ldr x17, [sp, #0x20]\n\t" /* NZCV */ + "str x17, [x10, #0x108]\n\t" /* cpsr */ + "ldp q8, q9, [sp, #0x30]\n\t" "stp q8, q9, [x10, #0x1b0]\n\t" + "ldp q10, q11, [sp, #0x50]\n\t" "stp q10, q11, [x10, #0x1d0]\n\t" + "ldp q12, q13, [sp, #0x70]\n\t" "stp q12, q13, [x10, #0x1f0]\n\t" + "ldp q14, q15, [sp, #0x90]\n\t" "stp q14, q15, [x10, #0x210]\n\t" - "mov x9, sp\n\t" - "stp x30, x9, [x10, #0xf0]\n\t" - "mrs x9, NZCV\n\t" - "stp x30, x9, [x10, #0x100]\n\t" + /* restore the unix call args */ + "ldp x0, x1, [sp]\n\t" + "ldr x2, [sp, #0x10]\n\t" + "add sp, sp, #0x160\n\t" "mov x19, x10\n\t" /* switch to kernel stack */ "mov sp, x10\n\t" @@ -1833,10 +1962,28 @@ __ASM_GLOBAL_FUNC( __wine_unix_call_dispatcher, "ldr w16, [sp, #0x10c]\n\t" /* frame->restore_flags */ "cbnz w16, " __ASM_LOCAL_LABEL("__wine_syscall_dispatcher_return") "\n\t" __ASM_CFI_CFA_IS_AT2(sp, 0x98, 0x02) /* frame->syscall_cfa */ - "ldp x18, x19, [sp, #0x90]\n\t" - "ldp x16, x17, [sp, #0xf8]\n\t" - /* switch to user stack */ - "mov sp, x16\n\t" - "ret x17" ) + "ldp x18, x19, [sp, #0x90]\n\t" + "ldp x16, x17, [sp, #0xf8]\n\t" + /* switch to user stack */ + "mov sp, x16\n\t" + "msr tpidr_el0, x18\n\t" + "br x17" ) + + +/*********************************************************************** + * __wine_get_syscall_frame_c + * + * Helper for the syscall dispatchers: find the current thread's syscall + * frame through the pthread thread data (the TEB register is unreliable + * on Apple Silicon, and a global would race across threads). + */ +struct syscall_frame * __wine_get_syscall_frame_c(void) +{ + struct thread_data *data = get_thread_data(); + current_teb = data->teb; + current_syscall_frame = get_syscall_frame( data ); + if (current_teb_ptr) *current_teb_ptr = data->teb; + return current_syscall_frame; +} #endif /* __aarch64__ */ diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index c8ca97d21a0..793ebda136e 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -400,6 +400,10 @@ extern NTSTATUS unixcall_wine_server_call( void *args ); extern NTSTATUS unixcall_wine_server_fd_to_handle( void *args ); extern NTSTATUS unixcall_wine_server_handle_to_fd( void *args ); extern NTSTATUS unixcall_wine_spawnvp( void *args ); +extern NTSTATUS unixcall_get_shared_user_data( void *args ); +extern NTSTATUS unixcall_register_teb_ptr( void *args ); +extern NTSTATUS unixcall_get_current_teb( void *args ); +extern struct _TEB * volatile *current_teb_ptr; #ifdef _WIN64 extern NTSTATUS wow64_wine_dbg_write( void *args ); extern NTSTATUS wow64_wine_server_call( void *args ); diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index db820a89525..23a797ea895 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -202,7 +202,16 @@ static void *host_addr_space_limit; /* top of the host virtual address space */ static struct file_view *arm64ec_view; ULONG_PTR user_space_wow_limit = 0; +#if defined(__APPLE__) && defined(__aarch64__) +/* On Apple Silicon the kernel reserves the low 4GB (including the Windows + * KUSER_SHARED_DATA address 0x7ffe0000) for the executable page zero, and + * refuses to map anything there. So we allocate the shared user data at an + * address chosen by the OS (following its ASLR policy) and patch the few + * places that reference the fixed Windows address. */ +struct _KUSER_SHARED_DATA *user_shared_data __attribute__((visibility("default"))); +#else struct _KUSER_SHARED_DATA *user_shared_data = (void *)0x7ffe0000; +#endif /* TEB allocation blocks */ static void *teb_block; @@ -749,9 +758,18 @@ static void mmap_init( const struct preload_info *preload_info ) if (preload_info) return; /* if we don't have a preloader, try to reserve the space now */ +#if defined(__APPLE__) && defined(__aarch64__) + /* On Apple Silicon the low 4GB is the kernel page zero, the dyld shared + * cache occupies ~6-12GB, and 64-448GB is kernel-reserved. Reserve the + * usable ranges that Windows images and top-down allocations need; + * unmappable ranges are skipped by reserve_area(). */ + reserve_area( (void *)0x000000010000, (void *)0x1000000000 ); + reserve_area( (void *)0x7000000000, (void *)0x600000000000 ); +#else reserve_area( (void *)0x000000010000, (void *)0x000068000000 ); reserve_area( (void *)0x00007f000000, (void *)0x00007fff0000 ); reserve_area( (void *)0x7ffffe000000, (void *)0x7fffffff0000 ); +#endif #endif } @@ -1979,11 +1997,15 @@ static int mprotect_range( void *base, size_t size, BYTE set, BYTE clear ) /*********************************************************************** - * set_vprot + * set_vprot_track * - * Change the protection of a range of pages. + * Update the per-page protection tracking for a range of pages, without + * changing the actual mappings. Used to set all the image section + * protections before applying them, so that on hosts with a page size + * larger than the Windows one the union of the protections within a host + * page is computed from the final values (avoiding W^X conflicts). */ -static BOOL set_vprot( struct file_view *view, void *base, size_t size, BYTE vprot ) +static void set_vprot_track( struct file_view *view, void *base, size_t size, BYTE vprot ) { if (!use_kernel_writewatch && view->protect & VPROT_WRITEWATCH) { @@ -1996,6 +2018,17 @@ static BOOL set_vprot( struct file_view *view, void *base, size_t size, BYTE vpr else if (use_kernel_writewatch && view->protect & VPROT_WRITEWATCH) vprot &= ~VPROT_WRITEWATCH; set_page_vprot( base, size, vprot ); } +} + + +/*********************************************************************** + * set_vprot + * + * Change the protection of a range of pages. + */ +static BOOL set_vprot( struct file_view *view, void *base, size_t size, BYTE vprot ) +{ + set_vprot_track( view, base, size, vprot ); return !mprotect_range( base, size, 0, 0 ); } @@ -3241,8 +3274,23 @@ static NTSTATUS map_image_into_view( struct file_view *view, const UNICODE_STRIN /* set the image protections */ - set_vprot( view, ptr, ROUND_SIZE( 0, header_size, align_mask ), VPROT_COMMITTED | VPROT_READ ); - + /* First track all the section protections, then apply them. This ensures + * that on hosts with a page size larger than the Windows one, the union + * of protections within a host page is computed from the final values, so + * that an executable page is never also writable (which macOS ARM64 + * rejects with W^X enforcement). + * + * Note that this only works when executable and writable sections do not + * share a host page, i.e. when the image section alignment is at least the + * host page size (16KB on Apple Silicon). ARM64 Windows binaries are + * required to use such an alignment. A PE whose executable and writable + * sections land in the same host page would need an executable and + * writable page at once, which macOS W^X enforcement refuses; the only + * alternative would be to toggle the page between RX and RW on every + * access, which is unusable since a writable data section is written + * continuously while the code on the same page runs. Such binaries simply + * cannot be loaded on this platform. */ + set_vprot_track( view, ptr, ROUND_SIZE( 0, header_size, align_mask ), VPROT_COMMITTED | VPROT_READ ); for (i = 0; i < nt->FileHeader.NumberOfSections; i++) { SIZE_T size; @@ -3257,7 +3305,20 @@ static NTSTATUS map_image_into_view( struct file_view *view, const UNICODE_STRIN if (sec[i].Characteristics & IMAGE_SCN_MEM_WRITE) vprot |= VPROT_WRITECOPY; if (sec[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) vprot |= VPROT_EXEC; - if (!set_vprot( view, ptr + sec[i].VirtualAddress, size, vprot ) && (vprot & VPROT_EXEC)) + set_vprot_track( view, ptr + sec[i].VirtualAddress, size, vprot ); + } + + mprotect_range( ptr, ROUND_SIZE( 0, header_size, align_mask ), 0, 0 ); + for (i = 0; i < nt->FileHeader.NumberOfSections; i++) + { + SIZE_T size; + + if (sec[i].Misc.VirtualSize) + size = ROUND_SIZE( sec[i].VirtualAddress, sec[i].Misc.VirtualSize, align_mask ); + else + size = ROUND_SIZE( sec[i].VirtualAddress, sec[i].SizeOfRawData, align_mask ); + + if (mprotect_range( ptr + sec[i].VirtualAddress, size, 0, 0 ) && (sec[i].Characteristics & IMAGE_SCN_MEM_EXECUTE)) ERR( "failed to set %08x protection on %s section %.8s, noexec filesystem?\n", sec[i].Characteristics, debugstr_us(nt_name), sec[i].Name ); } @@ -4063,6 +4124,10 @@ TEB *virtual_alloc_first_teb(void) struct thread_data *thread_data; /* reserve space for shared user data */ +#if defined(__APPLE__) && defined(__aarch64__) + /* let the OS pick an address for the shared user data */ + user_shared_data = NULL; +#endif status = NtAllocateVirtualMemory( NtCurrentProcess(), (void **)&user_shared_data, 0, &data_size, MEM_RESERVE | MEM_COMMIT, PAGE_READONLY ); if (status) @@ -4071,8 +4136,15 @@ TEB *virtual_alloc_first_teb(void) exit(1); } +#if defined(__APPLE__) && defined(__aarch64__) + /* On Apple Silicon the low 2GB is kernel-reserved (page zero), so do not + * restrict the TEB block to the 2GB range as done on other 64-bit hosts. */ + NtAllocateVirtualMemory( NtCurrentProcess(), &teb_block, 0, &total, + MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); +#else NtAllocateVirtualMemory( NtCurrentProcess(), &teb_block, is_win64 ? limit_2g - 1 : 0, &total, MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE ); +#endif teb_block_pos = 30; ptr = (char *)teb_block + 30 * block_size; data_size = 2 * block_size; @@ -4455,6 +4527,21 @@ done: static const WCHAR shared_data_nameW[] = {'\\','K','e','r','n','e','l','O','b','j','e','c','t','s', '\\','_','_','w','i','n','e','_','u','s','e','r','_','s','h','a','r','e','d','_','d','a','t','a',0}; +/*********************************************************************** + * unixcall_get_shared_user_data + * + * Return the address of the shared user data page. On Apple Silicon the + * fixed Windows address 0x7ffe0000 cannot be mapped (kernel page zero), so + * the data lives at an OS-chosen address and the PE side needs to fetch it. + */ +NTSTATUS unixcall_get_shared_user_data( void *args ) +{ + struct get_shared_user_data_params *params = args; + + params->data = &user_shared_data; + return STATUS_SUCCESS; +} + /*********************************************************************** * virtual_map_user_shared_data */ diff --git a/dlls/ntdll/unixlib.h b/dlls/ntdll/unixlib.h index 9cb444342fe..e8bf1258471 100644 --- a/dlls/ntdll/unixlib.h +++ b/dlls/ntdll/unixlib.h @@ -66,6 +66,21 @@ struct unwind_builtin_dll_params CONTEXT *context; }; +struct get_shared_user_data_params +{ + struct _KUSER_SHARED_DATA **data; +}; + +struct register_teb_ptr_params +{ + struct _TEB **teb_ptr; +}; + +struct get_current_teb_params +{ + struct _TEB **teb; +}; + enum ntdll_unix_funcs { unix_load_so_dll, @@ -76,6 +91,9 @@ enum ntdll_unix_funcs unix_wine_server_handle_to_fd, unix_wine_spawnvp, unix_system_time_precise, + unix_get_shared_user_data, + unix_register_teb_ptr, + unix_get_current_teb, }; extern unixlib_handle_t __wine_unixlib_handle; diff --git a/dlls/ntoskrnl.exe/ntoskrnl.c b/dlls/ntoskrnl.exe/ntoskrnl.c index a701041d876..b16112bb933 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/ntoskrnl.c @@ -4847,7 +4847,7 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, LPVOID reserved ) #endif KeQueryTickCount( &count ); /* initialize the global KeTickCount */ NtBuildNumber = NtCurrentTeb()->Peb->OSBuildNumber; - ntoskrnl_heap = HeapCreate( HEAP_CREATE_ENABLE_EXECUTE, 0, 0 ); + ntoskrnl_heap = HeapCreate( 0, 0, 0 ); dpc_call_tls_index = TlsAlloc(); LdrRegisterDllNotification( 0, ldr_notify_callback, NULL, &ldr_notify_cookie ); break; diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 8c9a2aaef7c..d04e1e5daa7 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -45,7 +45,13 @@ WINE_DECLARE_DEBUG_CHANNEL(relay); #define QS_HARDWARE 0x40000000 #define QS_INTERNAL (QS_DRIVER | QS_HARDWARE) +#if defined(__APPLE__) && defined(__aarch64__) +/* On Apple Silicon the fixed Windows address 0x7ffe0000 cannot be mapped; + * use the address chosen by the unix side (exported by ntdll.so). */ +extern const struct _KUSER_SHARED_DATA *user_shared_data; +#else static const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; +#endif static const struct ratio no_dpi; diff --git a/include/config.h.in b/include/config.h.in index 06720f73666..f2b0db3ff7c 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -54,6 +54,10 @@ /* Define to 1 if you have the 'dlinfo' function. */ #undef HAVE_DLINFO +/* Define to 1 on Apple Silicon, where the fixed KUSER_SHARED_DATA address + cannot be mapped and must be fetched through ntdll. */ +#undef HAVE_DYNAMIC_USER_SHARED_DATA + /* Define to 1 if you have the <elf.h> header file. */ #undef HAVE_ELF_H diff --git a/include/winnt.h b/include/winnt.h index f25ce5170b9..04ae1fa961a 100644 --- a/include/winnt.h +++ b/include/winnt.h @@ -2596,6 +2596,12 @@ static FORCEINLINE struct _TEB * WINAPI NtCurrentTeb(void) { return (struct _TEB *)__readfsdword( 0x18 ); } +#elif defined(__aarch64__) && !defined(__arm64ec__) +/* On Apple Silicon the TEB register (x18) is clobbered when the CPU enters + * freshly executed code pages, and the EL0 thread ID register is only + * reliable until the next such transition, so fetch the TEB through ntdll's + * export, which combines both with a per-thread fallback. */ +NTSYSAPI struct _TEB * WINAPI NtCurrentTeb(void); #elif (defined(__aarch64__) || defined(__arm64ec__)) && defined(__GNUC__) register struct _TEB *__wine_current_teb __asm__("x18"); static FORCEINLINE struct _TEB * WINAPI NtCurrentTeb(void) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11638
From: trcrsired <uwgghhbcad@gmail.com> The unix_get_current_teb call writes through params->teb, so the PE side must point it at a valid TEB slot. Previously the uninitialized stack pointer caused a NULL dereference in the fallback path used when tpidr_el0 is invalid or reset. Also relax -Werror for unused-command-line-argument during configure probes on Apple Silicon clang. --- configure | 9 ++++++--- configure.ac | 7 +++++-- dlls/ntdll/signal_arm64.c | 11 +++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/configure b/configure index cff47fab188..ad73fa81fd7 100755 --- a/configure +++ b/configure @@ -11376,7 +11376,7 @@ fi saved_sysctl_h_CFLAGS=$CFLAGS -test "x${GCC}" != xyes || CFLAGS="$CFLAGS -Werror" +test "x${GCC}" != xyes || CFLAGS="$CFLAGS -Werror -Wno-error=unused-command-line-argument" ac_fn_c_check_header_compile "$LINENO" "sys/sysctl.h" "ac_cv_header_sys_sysctl_h" "#include <sys/types.h> #ifdef HAVE_SYS_PARAM_H # include <sys/param.h> @@ -20772,7 +20772,9 @@ then : EXTRACFLAGS="$EXTRACFLAGS -Wno-misleading-indentation" fi - { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for flags needed for 64-bit compare-and-swap support" >&5 + saved_cas_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for flags needed for 64-bit compare-and-swap support" >&5 printf %s "checking for flags needed for 64-bit compare-and-swap support... " >&6; } if test ${wine_cv_64bit_compare_swap+y} then : @@ -20825,7 +20827,7 @@ else case e in #( esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext - CFLAGS=$saved_CFLAGS + CFLAGS=$saved_cas_CFLAGS fi ;; esac fi @@ -20834,6 +20836,7 @@ esac fi { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $wine_cv_64bit_compare_swap" >&5 printf '%s\n' "$wine_cv_64bit_compare_swap" >&6; } + CFLAGS=$saved_cas_CFLAGS case "$wine_cv_64bit_compare_swap" in unknown) as_fn_error $? "64-bit compare-and-swap is not supported on this platform" "$LINENO" 5 ;; "none needed") ;; diff --git a/configure.ac b/configure.ac index fdaa2ca7a8b..5553216d07a 100644 --- a/configure.ac +++ b/configure.ac @@ -827,7 +827,7 @@ AC_CHECK_HEADERS([sys/conf.h sys/mount.h sys/statfs.h sys/user.h sys/vfs.h],,, dnl check for broken Linux sysctl.h that causes deprecation warnings saved_sysctl_h_CFLAGS=$CFLAGS -test "x${GCC}" != xyes || CFLAGS="$CFLAGS -Werror" +test "x${GCC}" != xyes || CFLAGS="$CFLAGS -Werror -Wno-error=unused-command-line-argument" AC_CHECK_HEADERS([sys/sysctl.h],,, [#include <sys/types.h> #ifdef HAVE_SYS_PARAM_H @@ -2022,6 +2022,8 @@ then WINE_TRY_CFLAGS([-flarge-source-files -Wmisleading-indentation],[EXTRACFLAGS="$EXTRACFLAGS -Wno-misleading-indentation"]) dnl Check for 64-bit compare-and-swap + saved_cas_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -Wno-error=unused-command-line-argument" AC_CACHE_CHECK([for flags needed for 64-bit compare-and-swap support], wine_cv_64bit_compare_swap, [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#ifndef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 #error no @@ -2040,8 +2042,9 @@ then AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#ifndef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 #error no #endif]])],,[wine_cv_64bit_compare_swap="unknown"]) - CFLAGS=$saved_CFLAGS + CFLAGS=$saved_cas_CFLAGS fi])]) + CFLAGS=$saved_cas_CFLAGS case "$wine_cv_64bit_compare_swap" in unknown) AC_MSG_ERROR([64-bit compare-and-swap is not supported on this platform]) ;; "none needed") ;; diff --git a/dlls/ntdll/signal_arm64.c b/dlls/ntdll/signal_arm64.c index d1dea21d421..82a1dbf2c0e 100644 --- a/dlls/ntdll/signal_arm64.c +++ b/dlls/ntdll/signal_arm64.c @@ -851,6 +851,8 @@ struct _TEB * WINAPI NtCurrentTeb(void) __asm__ volatile( "mrs %0, tpidr_el0" : "=r"(t) ); if (t > 0x100000000) /* per-thread TEB in the EL0 thread ID register */ { + if (t > 0x800000000000ULL || t != (ULONG_PTR)__wine_current_teb) + FIXME( "tpidr_el0=%#lx cached=%#lx sp=%#lx\n", t, (ULONG_PTR)__wine_current_teb, (ULONG_PTR)__builtin_frame_address(0) ); __wine_current_teb = t; return (struct _TEB *)t; } @@ -863,12 +865,13 @@ struct _TEB * WINAPI NtCurrentTeb(void) /* tpidr_el0 was reset (fresh code page or signal) and the cached TEB * belongs to another thread; ask the unix side for the current one. */ { - struct get_current_teb_params params; + struct _TEB *current = NULL; + struct get_current_teb_params params = { ¤t }; WINE_UNIX_CALL( unix_get_current_teb, ¶ms ); - if (*params.teb) + if (current) { - __wine_current_teb = (ULONG_PTR)*params.teb; - return *params.teb; + __wine_current_teb = (ULONG_PTR)current; + return current; } } return (struct _TEB *)__wine_current_teb; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11638
From: trcrsired <uwgghhbcad@gmail.com> NtCurrentTeb is defined by the ABI as reading the x18 register, so drop the custom PE-side implementation, the exported arm64 NtCurrentTeb() entry, and the unixcall fallback that pointed at it. LdrInitializeThunk now sets x18 and tpidr_el0 from the thread context at load time. win32u/message.c reads the KUSER_SHARED_DATA page through the user_shared_data symbol exported by ntdll.so, gated by HAVE_DYNAMIC_USER_SHARED_DATA instead of the __APPLE__/__aarch64__ compiler macros. --- dlls/ntdll/ntdll.spec | 1 - dlls/ntdll/signal_arm64.c | 48 +-------------------------------------- dlls/win32u/message.c | 8 ++++--- include/winnt.h | 6 ----- 4 files changed, 6 insertions(+), 57 deletions(-) diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 1b74402a4b1..c245d56e2f4 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -214,7 +214,6 @@ @ stdcall -syscall NtCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) # @ stub NtCreateWaitablePort @ stdcall -arch=i386 NtCurrentTeb() -@ stdcall -arch=arm64 NtCurrentTeb() @ stdcall -syscall NtDebugActiveProcess(long long) @ stdcall -syscall NtDebugContinue(long ptr long) @ stdcall -syscall=0x0034 NtDelayExecution(long ptr) diff --git a/dlls/ntdll/signal_arm64.c b/dlls/ntdll/signal_arm64.c index 82a1dbf2c0e..5a2800ebe3d 100644 --- a/dlls/ntdll/signal_arm64.c +++ b/dlls/ntdll/signal_arm64.c @@ -837,56 +837,10 @@ __ASM_GLOBAL_FUNC( RtlUserThreadStart, /****************************************************************** * LdrInitializeThunk (NTDLL.@) */ -volatile ULONG_PTR __wine_current_teb; - -#ifdef __WINE_PE_BUILD -/*********************************************************************** - * NtCurrentTeb (NTDLL.@) - */ -struct _TEB * WINAPI NtCurrentTeb(void) -{ - ULONG_PTR t, sp; - struct _TEB *teb; - - __asm__ volatile( "mrs %0, tpidr_el0" : "=r"(t) ); - if (t > 0x100000000) /* per-thread TEB in the EL0 thread ID register */ - { - if (t > 0x800000000000ULL || t != (ULONG_PTR)__wine_current_teb) - FIXME( "tpidr_el0=%#lx cached=%#lx sp=%#lx\n", t, (ULONG_PTR)__wine_current_teb, (ULONG_PTR)__builtin_frame_address(0) ); - __wine_current_teb = t; - return (struct _TEB *)t; - } - - __asm__ volatile( "mov %0, sp" : "=r"(sp) ); - teb = (struct _TEB *)__wine_current_teb; - if (teb && sp >= (ULONG_PTR)teb->Tib.StackLimit && sp <= (ULONG_PTR)teb->Tib.StackBase) - return teb; /* cached TEB belongs to the current thread */ - - /* tpidr_el0 was reset (fresh code page or signal) and the cached TEB - * belongs to another thread; ask the unix side for the current one. */ - { - struct _TEB *current = NULL; - struct get_current_teb_params params = { ¤t }; - WINE_UNIX_CALL( unix_get_current_teb, ¶ms ); - if (current) - { - __wine_current_teb = (ULONG_PTR)current; - return current; - } - } - return (struct _TEB *)__wine_current_teb; -} -#endif - void WINAPI LdrInitializeThunk( CONTEXT *context, ULONG_PTR unk2, ULONG_PTR unk3, ULONG_PTR unk4 ) { - extern volatile ULONG_PTR __wine_current_teb; - __wine_current_teb = context->X18; + __asm__ volatile( "mov x18, %0" :: "r"(context->X18) ); __asm__ volatile( "msr tpidr_el0, %0" :: "r"(context->X18) ); - { - struct register_teb_ptr_params params = { (struct _TEB **)&__wine_current_teb }; - WINE_UNIX_CALL( unix_register_teb_ptr, ¶ms ); - } loader_init( context, (void **)&context->X0 ); TRACE_(relay)( "\1Starting thread proc %p (arg=%p)\n", (void *)context->X0, (void *)context->X1 ); NtContinue( context, TRUE ); diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index d04e1e5daa7..63129799098 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -25,6 +25,7 @@ #endif #include <assert.h> +#include "config.h" #include "ntstatus.h" #include "winternl.h" #include "ddk/wdm.h" @@ -45,9 +46,10 @@ WINE_DECLARE_DEBUG_CHANNEL(relay); #define QS_HARDWARE 0x40000000 #define QS_INTERNAL (QS_DRIVER | QS_HARDWARE) -#if defined(__APPLE__) && defined(__aarch64__) -/* On Apple Silicon the fixed Windows address 0x7ffe0000 cannot be mapped; - * use the address chosen by the unix side (exported by ntdll.so). */ +#ifdef HAVE_DYNAMIC_USER_SHARED_DATA +/* On Apple Silicon the fixed Windows address 0x7ffe0000 is inside the kernel + * page zero and cannot be mapped; use the address chosen by the unix side + * (exported by ntdll.so). */ extern const struct _KUSER_SHARED_DATA *user_shared_data; #else static const struct _KUSER_SHARED_DATA *user_shared_data = (struct _KUSER_SHARED_DATA *)0x7ffe0000; diff --git a/include/winnt.h b/include/winnt.h index 04ae1fa961a..f25ce5170b9 100644 --- a/include/winnt.h +++ b/include/winnt.h @@ -2596,12 +2596,6 @@ static FORCEINLINE struct _TEB * WINAPI NtCurrentTeb(void) { return (struct _TEB *)__readfsdword( 0x18 ); } -#elif defined(__aarch64__) && !defined(__arm64ec__) -/* On Apple Silicon the TEB register (x18) is clobbered when the CPU enters - * freshly executed code pages, and the EL0 thread ID register is only - * reliable until the next such transition, so fetch the TEB through ntdll's - * export, which combines both with a per-thread fallback. */ -NTSYSAPI struct _TEB * WINAPI NtCurrentTeb(void); #elif (defined(__aarch64__) || defined(__arm64ec__)) && defined(__GNUC__) register struct _TEB *__wine_current_teb __asm__("x18"); static FORCEINLINE struct _TEB * WINAPI NtCurrentTeb(void) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11638
From: trcrsired <uwgghhbcad@gmail.com> --- dlls/win32u/message.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 63129799098..bc19090b06b 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -24,8 +24,9 @@ #pragma makedep unix #endif -#include <assert.h> #include "config.h" + +#include <assert.h> #include "ntstatus.h" #include "winternl.h" #include "ddk/wdm.h" -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11638
participants (2)
-
cqwrteur (@trcrsired) -
trcrsired