diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S index eb739b88f..46c18d884 100644 --- a/Source/Windows/ARM64EC/Module.S +++ b/Source/Windows/ARM64EC/Module.S @@ -91,22 +91,32 @@ ret_sp_misaligned: ldr lr, [lr, #:lo12:X64ReturnInstr] br x17 - // Calls NtContinue directly to allow continuing from a full native context, as the NTDLL NtContinue export takes in - // an x64 context with EC and the conversion to that loses the ARM64EC ABI-disallowed registers that FEX uses. -.global "#NtContinueNative" -"#NtContinueNative": - adrp x16, WineSyscallDispatcher - ldr x16, [x16, #:lo12:WineSyscallDispatcher] - cbz x16, direct_syscall -wine_syscall: - mov x9, x30 - adrp x8, WineNtContinueSyscallId - ldr x8, [x8, #:lo12:WineNtContinueSyscallId] - blr x16 - ret -direct_syscall: - svc #0x43 - ret + // Makes a wrapper for calling a system call directly, skipping the usual ntdll thunks +#define HASH # +#define DIRECT_SYSCALL_WRAPPER(Name, WineIdName, WindowsId) \ + .global Name; \ + Name:; \ + adrp x16, WineSyscallDispatcher; \ + ldr x16, [x16, HASH:lo12:WineSyscallDispatcher]; \ + cbz x16, 1f; \ + mov x9, x30; \ + adrp x8, WineIdName; \ + ldr x8, [x8, HASH:lo12:WineIdName]; \ + blr x16; \ + ret; \ + 1:; \ + svc HASH WindowsId; \ + ret + + // Allows for continuing from a full native context, as the NTDLL NtContinue export takes in an x64 context with EC and + // the conversion to that loses the ARM64EC ABI-disallowed registers that FEX uses. +DIRECT_SYSCALL_WRAPPER("#NtContinueNative", WineNtContinueSyscallId, 0x43) + + // Both of these are wrapped as FEX needs them to setup its call checker at startup time and their NTDLL thunks could + // already be patched by then (and because the call checker isn't installed, their patched x86 versions would be invoked + // when called by FEX). +DIRECT_SYSCALL_WRAPPER("#NtAllocateVirtualMemoryNative", WineNtAllocateVirtualMemorySyscallId, 0x18) +DIRECT_SYSCALL_WRAPPER("#NtProtectVirtualMemoryNative", WineNtProtectVirtualMemorySyscallId, 0x50) // A replacement for the standard ARM64EC call checker that ignores any FFS patches and always redirects to a function's // native implementation. As the only library FEX calls into is NTDLL, this is done using a LUT generated at init time. diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 8136a43fe..f2d8ed10a 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -71,8 +71,12 @@ uint32_t NtDllRedirectionLUTSize; void* WineSyscallDispatcher; // TODO: this really shouldn't be hardcoded, once wine gains proper syscall thunks this can be dropped. uint64_t WineNtContinueSyscallId = 0x1a; +uint64_t WineNtAllocateVirtualMemorySyscallId = 0xb; +uint64_t WineNtProtectVirtualMemorySyscallId = 0x76; NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert); +NTSTATUS NtAllocateVirtualMemoryNative(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG); +NTSTATUS NtProtectVirtualMemoryNative(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG*); [[noreturn]] void JumpSetStack(uintptr_t PC, uintptr_t SP); @@ -146,8 +150,7 @@ bool IsDispatcherAddress(uint64_t Address) { } -void FillNtDllLUTs() { - const HMODULE NtDll = GetModuleHandle("ntdll.dll"); +void FillNtDllLUTs(HMODULE NtDll) { NtDllBase = reinterpret_cast(NtDll); ULONG Size; const auto* LoadConfig = @@ -157,7 +160,10 @@ void FillNtDllLUTs() { const auto* RedirectionTableEnd = RedirectionTableBegin + CHPEMetadata->RedirectionMetadataCount; NtDllRedirectionLUTSize = std::prev(RedirectionTableEnd)->Source + 1; - NtDllRedirectionLUT = new uint32_t[NtDllRedirectionLUTSize]; + + SIZE_T AllocSize = NtDllRedirectionLUTSize * sizeof(uint32_t); + NtAllocateVirtualMemoryNative(NtCurrentProcess(), reinterpret_cast(&NtDllRedirectionLUT), 0, &AllocSize, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); for (auto It = RedirectionTableBegin; It != RedirectionTableEnd; It++) { NtDllRedirectionLUT[It->Source] = It->Destination; } @@ -173,9 +179,9 @@ void WriteModuleRVA(HMODULE Module, LONG RVA, T Data) { void* ProtAddress = Address; SIZE_T ProtSize = sizeof(T); ULONG Prot; - NtProtectVirtualMemory(NtCurrentProcess(), &ProtAddress, &ProtSize, PAGE_READWRITE, &Prot); + NtProtectVirtualMemoryNative(NtCurrentProcess(), &ProtAddress, &ProtSize, PAGE_READWRITE, &Prot); *reinterpret_cast(Address) = Data; - NtProtectVirtualMemory(NtCurrentProcess(), &ProtAddress, &ProtSize, Prot, nullptr); + NtProtectVirtualMemoryNative(NtCurrentProcess(), &ProtAddress, &ProtSize, Prot, nullptr); } void PatchCallChecker() { @@ -189,6 +195,23 @@ void PatchCallChecker() { WriteModuleRVA(Module, CHPEMetadata->__os_arm64x_dispatch_icall, &CheckCall); WriteModuleRVA(Module, CHPEMetadata->__os_arm64x_dispatch_icall_cfg, &CheckCall); } + +// Syscall thunks may have been patched before FEX has loaded, the default call checker installed by ntdll into FEX will +// try to invoke the JIT when calling such patched syscalls but this obviously doesn't work before FEX is initalised. +// This function parses ntdll and sets up a custom call checker to prevent this, as such it must avoid using any syscall +// thunks itself. +void InitSyscalls() { + // The ntdll exports called by GetModuleHandle/GetProcAddress aren't known to be patched before JIT init by any current + // software so are safe to call, but if that changes the loader structures in the PEB could be parsed manually. + const auto NtDll = GetModuleHandle("ntdll.dll"); + const auto WineSyscallDispatcherPtr = reinterpret_cast(GetProcAddress(NtDll, "__wine_syscall_dispatcher")); + if (WineSyscallDispatcherPtr) { + WineSyscallDispatcher = *WineSyscallDispatcherPtr; + } + + FillNtDllLUTs(NtDll); + PatchCallChecker(); +} } // namespace namespace Exception { @@ -452,6 +475,8 @@ extern "C" void SyncThreadContext(CONTEXT* Context) { } NTSTATUS ProcessInit() { + InitSyscalls(); + FEX::Windows::InitCRTProcess(); FEX::Config::LoadConfig(nullptr, FEX::Windows::GetExecutableFilePath()); FEXCore::Config::ReloadMetaLayer(); @@ -486,14 +511,8 @@ NTSTATUS ProcessInit() { X64ReturnInstr = ::VirtualAlloc(nullptr, FEXCore::Utils::FEX_PAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); *reinterpret_cast(X64ReturnInstr) = 0xc3; - FillNtDllLUTs(); - PatchCallChecker(); const uintptr_t KiUserExceptionDispatcherFFS = reinterpret_cast(GetProcAddress(NtDll, "KiUserExceptionDispatcher")); Exception::KiUserExceptionDispatcher = NtDllRedirectionLUT[KiUserExceptionDispatcherFFS - NtDllBase] + NtDllBase; - const auto WineSyscallDispatcherPtr = reinterpret_cast(GetProcAddress(NtDll, "__wine_syscall_dispatcher")); - if (WineSyscallDispatcherPtr) { - WineSyscallDispatcher = *WineSyscallDispatcherPtr; - } return STATUS_SUCCESS; }