mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 10:00:16 +02:00
Windows: Don't hardcode NT syscall numbers for ARM64EC
This commit is contained in:
1 parent
08f451d3bc
commit
aad2c969b7
2 files changed
+98
-10
No files matched your search
@@ -101,9 +101,26 @@ ExitFunctionSuspendPoint:
|
||||
brk #0xCAFE
|
||||
// Resume will jump back to `ExitFunctionSuspendResumePoint`
|
||||
|
||||
#ifdef ARCHITECTURE_arm64ec
|
||||
// This table will contain all of the possible SVC #x instructions that we might need.
|
||||
// We index into this table in DIRECT_SYSCALL_WRAPPER based on the syscall number we find
|
||||
// by parsing the table __arm64ec_syscall_ffs from NTDLL.
|
||||
#define SYSCALL_TABLE_ENTRIES 512
|
||||
.global SyscallTable
|
||||
SyscallTable:
|
||||
.set SyscallId, 0
|
||||
.rept SYSCALL_TABLE_ENTRIES
|
||||
svc #SyscallId
|
||||
ret
|
||||
.set SyscallId, SyscallId + 1
|
||||
.endr
|
||||
.global SyscallTableEnd
|
||||
SyscallTableEnd:
|
||||
#endif
|
||||
|
||||
// Makes a wrapper for calling a system call directly, skipping the usual ntdll thunks
|
||||
#define HASH #
|
||||
#define DIRECT_SYSCALL_WRAPPER(Name, WineIdName, WindowsId) \
|
||||
#define DIRECT_SYSCALL_WRAPPER(Name, WineIdName) \
|
||||
.global Name; \
|
||||
Name:; \
|
||||
adrp x16, WineSyscallDispatcher; \
|
||||
@@ -115,19 +132,23 @@ ExitFunctionSuspendPoint:
|
||||
blr x16; \
|
||||
ret; \
|
||||
1:; \
|
||||
svc HASH WindowsId; \
|
||||
ret
|
||||
adrp x16, SyscallTable; \
|
||||
add x16, x16, HASH:lo12:SyscallTable; \
|
||||
adrp x17, WineIdName; \
|
||||
ldr x17, [x17, HASH:lo12:WineIdName]; \
|
||||
add x16, x16, x17, lsl HASH 3; \
|
||||
br x16
|
||||
|
||||
// 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)
|
||||
DIRECT_SYSCALL_WRAPPER("#NtRaiseExceptionNative", WineNtRaiseExceptionSyscallId, 0x174)
|
||||
DIRECT_SYSCALL_WRAPPER("#NtContinueNative", WineNtContinueSyscallId)
|
||||
DIRECT_SYSCALL_WRAPPER("#NtRaiseExceptionNative", WineNtRaiseExceptionSyscallId)
|
||||
|
||||
// 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)
|
||||
DIRECT_SYSCALL_WRAPPER("#NtAllocateVirtualMemoryNative", WineNtAllocateVirtualMemorySyscallId)
|
||||
DIRECT_SYSCALL_WRAPPER("#NtProtectVirtualMemoryNative", WineNtProtectVirtualMemorySyscallId)
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -74,6 +74,10 @@ extern void* ExitFunctionEC;
|
||||
extern void* CheckCall;
|
||||
extern void* ExitFunctionSuspendPoint;
|
||||
extern void* ExitFunctionSuspendResumePoint;
|
||||
#ifdef ARCHITECTURE_arm64ec
|
||||
extern uint64_t SyscallTable[];
|
||||
extern uint64_t SyscallTableEnd[];
|
||||
#endif
|
||||
|
||||
void* X64ReturnInstr; // See Module.S
|
||||
uintptr_t NtDllBase;
|
||||
@@ -82,7 +86,7 @@ uintptr_t NtDllBase;
|
||||
uint32_t* NtDllRedirectionLUT;
|
||||
uint32_t NtDllRedirectionLUTSize;
|
||||
|
||||
// Wine doesn't support issuing direct system calls with SVC, and unlike Windows it doesn't have a 'stable' syscall number for NtContinue
|
||||
// Wine doesn't support issuing direct system calls with SVC, and like Windows it also doesn't have 'stable' syscall numbers
|
||||
void* WineSyscallDispatcher;
|
||||
uint64_t WineNtContinueSyscallId;
|
||||
uint64_t WineNtAllocateVirtualMemorySyscallId;
|
||||
@@ -268,11 +272,53 @@ void ParseWineSyscallNumbers(HMODULE NtDll) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_arm64ec
|
||||
uint64_t GetNTSyscallNo(HMODULE NtDll, const char* ExportName) {
|
||||
ULONG LoadConfigSize;
|
||||
const _IMAGE_LOAD_CONFIG_DIRECTORY64* NtDllLoadConfig = reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(
|
||||
RtlImageDirectoryEntryToData(NtDll, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &LoadConfigSize));
|
||||
|
||||
const IMAGE_ARM64EC_METADATA* NtDllARM64ECMetadata = reinterpret_cast<IMAGE_ARM64EC_METADATA*>(NtDllLoadConfig->CHPEMetadataPointer);
|
||||
|
||||
// This redirection table stores a mapping from x64 entry points for fast forward sequences,
|
||||
// to the real ARM64EC implementation of the function.
|
||||
const IMAGE_ARM64EC_REDIRECTION_ENTRY* RedirectionTableBegin =
|
||||
reinterpret_cast<IMAGE_ARM64EC_REDIRECTION_ENTRY*>(NtDllBase + NtDllARM64ECMetadata->RedirectionMetadata);
|
||||
const IMAGE_ARM64EC_REDIRECTION_ENTRY* RedirectionTableEnd = RedirectionTableBegin + NtDllARM64ECMetadata->RedirectionMetadataCount;
|
||||
|
||||
// Walk through the table until we find an entry matching the x64 FFS.
|
||||
const uintptr_t x64FFS = reinterpret_cast<uintptr_t>(GetProcAddress(NtDll, ExportName)) - NtDllBase;
|
||||
uintptr_t ARM64ECImplementation = 0;
|
||||
for (const IMAGE_ARM64EC_REDIRECTION_ENTRY* RedirectionEntry = RedirectionTableBegin; RedirectionEntry != RedirectionTableEnd;
|
||||
RedirectionEntry++) {
|
||||
if (RedirectionEntry->Source == x64FFS) {
|
||||
ARM64ECImplementation = NtDllBase + RedirectionEntry->Destination;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now walk through the syscall FFS table and find which entry points to the ARM64EC implementation
|
||||
// we found in the previous table. The index of this entry in that table will be the syscall number.
|
||||
const uintptr_t* SyscallImplementationTable = reinterpret_cast<uintptr_t*>(GetProcAddress(NtDll, "__arm64ec_syscall_ffs"));
|
||||
const uint32_t* SyscallImplementationCount = reinterpret_cast<uint32_t*>(GetProcAddress(NtDll, "__arm64ec_syscall_ffs_size"));
|
||||
if (!ARM64ECImplementation || !SyscallImplementationTable || !SyscallImplementationCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint64_t SyscallNo = 0; SyscallNo < *SyscallImplementationCount; SyscallNo++) {
|
||||
if (SyscallImplementationTable[SyscallNo] == ARM64ECImplementation) {
|
||||
return SyscallNo;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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() {
|
||||
bool 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 = GetModuleHandleW(L"ntdll.dll");
|
||||
@@ -282,10 +328,29 @@ void InitSyscalls() {
|
||||
if (WineSyscallDispatcherPtr) {
|
||||
WineSyscallDispatcher = *WineSyscallDispatcherPtr;
|
||||
ParseWineSyscallNumbers(NtDll);
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_arm64ec
|
||||
// NT syscall numbers may change between versions, so we need to find the numbers
|
||||
// for the syscalls we need ahead of time.
|
||||
WineNtContinueSyscallId = GetNTSyscallNo(NtDll, "NtContinue");
|
||||
WineNtAllocateVirtualMemorySyscallId = GetNTSyscallNo(NtDll, "NtAllocateVirtualMemory");
|
||||
WineNtProtectVirtualMemorySyscallId = GetNTSyscallNo(NtDll, "NtProtectVirtualMemory");
|
||||
WineNtRaiseExceptionSyscallId = GetNTSyscallNo(NtDll, "NtRaiseException");
|
||||
|
||||
// Fail if the syscall number we found is beyond the bounds
|
||||
// of our static table. This is almost certainly not going
|
||||
// to happen, but failing here will make debugging easier later on.
|
||||
const uint64_t SyscallTableSize = SyscallTableEnd - SyscallTable;
|
||||
if (std::max({WineNtContinueSyscallId, WineNtAllocateVirtualMemorySyscallId, WineNtProtectVirtualMemorySyscallId,
|
||||
WineNtRaiseExceptionSyscallId}) >= SyscallTableSize) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FillNtDllLUTs(NtDll);
|
||||
PatchCallChecker();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleImageMap(uint64_t Address, bool MainImage = false) {
|
||||
@@ -580,7 +645,9 @@ extern "C" void SyncThreadContext(CONTEXT* Context) {
|
||||
}
|
||||
|
||||
NTSTATUS ProcessInit() {
|
||||
InitSyscalls();
|
||||
if (!InitSyscalls()) {
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
FEX::Windows::InitCRTProcess();
|
||||
FEX::Windows::SetupThreadHandlers();
|
||||
|
||||
Reference in new issue
Block a user