Skip to content

Commit

Permalink
ARM64EC: Dynamically determine syscall numbers under wine.
Browse files Browse the repository at this point in the history
Takes advantage of the consistent alphabetical syscall ordering that
wine follows.
  • Loading branch information
bylaws committed Sep 27, 2024
1 parent b6d3df0 commit 0b8ee49
Showing 1 changed file with 31 additions and 5 deletions.
36 changes: 31 additions & 5 deletions Source/Windows/ARM64EC/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ 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
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;
uint64_t WineNtContinueSyscallId;
uint64_t WineNtAllocateVirtualMemorySyscallId;
uint64_t WineNtProtectVirtualMemorySyscallId;

NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert);
NTSTATUS NtAllocateVirtualMemoryNative(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG);
Expand Down Expand Up @@ -151,7 +150,6 @@ bool IsDispatcherAddress(uint64_t Address) {


void FillNtDllLUTs(HMODULE NtDll) {
NtDllBase = reinterpret_cast<uintptr_t>(NtDll);
ULONG Size;
const auto* LoadConfig =
reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(RtlImageDirectoryEntryToData(NtDll, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &Size));
Expand Down Expand Up @@ -196,6 +194,31 @@ void PatchCallChecker() {
WriteModuleRVA(Module, CHPEMetadata->__os_arm64x_dispatch_icall_cfg, &CheckCall);
}

// Fills in the syscall numbers necessary to call *Native variants of syscalls from FEX under wine.
void ParseWineSyscallNumbers(HMODULE NtDll) {
ULONG Size;
const auto* Exports = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(RtlImageDirectoryEntryToData(NtDll, true, IMAGE_DIRECTORY_ENTRY_EXPORT, &Size));
const auto* NameTable = reinterpret_cast<uint32_t*>(NtDllBase + Exports->AddressOfNames);

// Wine orders syscalls alphabetically, take advantage of that to find the syscall indices for those which we need to
// manually issue. Note that all functions starting with Nt besides NtGetTickCount are syscalls.
uint32_t CurSyscallId = 0;
for (uint32_t Idx = 0; Idx < Exports->NumberOfNames; Idx++) {
const char* Name = reinterpret_cast<const char*>(NtDllBase + NameTable[Idx]);
if (Name[0] == 'N' && Name[1] == 't' && strcmp(Name, "NtGetTickCount")) {
if (!strcmp(Name, "NtContinue")) {
WineNtContinueSyscallId = CurSyscallId;
} else if (!strcmp(Name, "NtAllocateVirtualMemory")) {
WineNtAllocateVirtualMemorySyscallId = CurSyscallId;
} else if (!strcmp(Name, "NtProtectVirtualMemory")) {
WineNtProtectVirtualMemorySyscallId = CurSyscallId;
}

CurSyscallId++;
}
}
}

// 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
Expand All @@ -204,9 +227,12 @@ 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");
NtDllBase = reinterpret_cast<uintptr_t>(NtDll);

const auto WineSyscallDispatcherPtr = reinterpret_cast<void**>(GetProcAddress(NtDll, "__wine_syscall_dispatcher"));
if (WineSyscallDispatcherPtr) {
WineSyscallDispatcher = *WineSyscallDispatcherPtr;
ParseWineSyscallNumbers(NtDll);
}

FillNtDllLUTs(NtDll);
Expand Down

0 comments on commit 0b8ee49

Please sign in to comment.