Files
FEX-Emu--FEX/Source/Windows/ARM64EC/Module.S
T
2026-09-29 12:02:38 -07:00

176 lines
6.3 KiB
ArmAsm

.text
.balign 16
// __os_arm64x_x64_jump in ARM64EC docs
// Expects target code address in x9
.globl DispatchJump
DispatchJump:
str lr, [sp, #-8]! // Push return address to stack, this will be popped by the x86 RET instr.
b check_target_ec
// __os_arm64x_dispatch_ret in ARM64EC docs
// Expects target code address in lr
.globl RetToEntryThunk
RetToEntryThunk:
mov x9, lr
check_target_ec:
// Check if target is in fact x86 code
ldr x16, [x18, #0x60] // TEB->PEB
ldr x16, [x16, #0x368] // PEB->EcCodeBitMap
lsr x17, x9, #18
ldr x16, [x16, x17, lsl #3]
lsr x17, x9, #12
lsr x16, x16, x17
tbnz x16, #0, ExitFunctionEC
b enter_jit
// __os_arm64x_dispatch_call_no_redirect in ARM64EC docs
// Expects target code address in x9, and to be called using a 'blr x16' instruction.
.globl ExitToX64
ExitToX64:
str lr, [sp, #-8]! // Push return address to stack, this will be popped by the x86 RET instr.
enter_jit:
ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo
mov w16, #1
strb w16, [x17, #0x0] // ChpeV2CpuAreaInfo->InSimulation
ldr x16, [x17, #0x40] // ChpeV2CpuAreaInfo->EmulatorData[2] - DispatcherLoopTopEnterEC
br x16 // DispatcherLoopTopEnterEC(RIP:x9, CPUArea:x17)
// Invoked by KiUserEmulationDispatcher after e.g. an NtContinue to x86 code
.global BeginSimulation
BeginSimulation:
ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo
ldr x16, [x17, #0x8] // ChpeV2CpuAreaInfo->EmulatorStackBase
mov sp, x16
ldr x0, [x17, #0x18] // ChpeV2CpuAreaInfo->ContextAmd64
bl "#SyncThreadContext"
ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo
ldr x16, [x17, #0x48] // ChpeV2CpuAreaInfo->EmulatorData[3] - DispatcherLoopTopEnterECFillSRA
mov x11, #0 // Zero ENTRY_FILL_SRA_SINGLE_INST_REG to avoid single step
br x16 // DispatcherLoopTopEnterECFillSRA(SingleInst:x10, CPUArea:x17)
// Called into by FEXCore
// Expects the target code address in x9
.global ExitFunctionEC
ExitFunctionEC:
// Clear any the AFP NEP and AH bits in FPCR as native code won't expect their behaviour.
mrs x17, fpcr
and x17, x17, #~6 // NEP + AH
msr fpcr, x17
ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo
strb wzr, [x17, #0x0] // ChpeV2CpuAreaInfo->InSimulation
ldr x17, [x17, #0x20] // ChpeV2CpuAreaInfo->SuspendDoorbell
ldr w17, [x17]
cbnz w17, ExitFunctionSuspendPoint
.global ExitFunctionSuspendResumePoint
ExitFunctionSuspendResumePoint:
// Either return to an exit thunk (return to ARM64EC function) or call an entry thunk (call to ARM64EC function).
// It is assumed that a 'blr x16' instruction is only ever used to call into x86 code from an exit thunk, and that all
// exported ARM64EC functions have a 4-byte offset to their entry thunk immediately before their first instruction.
mov x17, x9
mov w16, #0x200
movk w16, #0xd63f, lsl 16 // blr x16
ldursw x23, [x17, #-0x4] // Load either the entry thunk offset or the calling instruction.
cmp w23, w16
beq ret_sp_aligned
and x23, x23, #-0x4
add x17, x17, x23 // Resolve entry thunk address.
mov x4, sp
tbz x4, #3, ret_sp_misaligned
ldr lr, [x4], #0x8 // Pop the return address into lr.
mov sp, x4
ret_sp_aligned:
br x17
ret_sp_misaligned:
// In the case of the x64 caller leaving sp only 8-byte aligned, leave the return address on the stack to keep 16-byte
// alignment and have the callee return to an x86 ret instruction. FEX can then return to the actual caller keeping
// the misaligned RSP.
adrp lr, X64ReturnInstr
ldr lr, [lr, #:lo12:X64ReturnInstr]
br x17
.global ExitFunctionSuspendPoint
ExitFunctionSuspendPoint:
brk #0xCAFE
// Resume will jump back to `ExitFunctionSuspendResumePoint`
// 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:
// Makes a wrapper for calling a system call directly, skipping the usual ntdll thunks
#define HASH #
#define DIRECT_SYSCALL_WRAPPER(Name, WineIdName) \
.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:; \
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)
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)
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.
// Expects the FFS address in x11, exit thunk address in x10 (unused) and it's own address in x9. Return address is in x11.
.global "CheckCall"
"CheckCall":
adrp x9, NtDllBase
ldr x9, [x9, #:lo12:NtDllBase]
subs x16, x11, x9
b.lo end
adrp x17, NtDllRedirectionLUTSize
ldr x17, [x17, #:lo12:NtDllRedirectionLUTSize]
cmp x16, x17
b.hi end
adrp x17, NtDllRedirectionLUT
ldr x17, [x17, #:lo12:NtDllRedirectionLUT]
ldr w11, [x17, x16, lsl #2]
add x11, x11, x9
end:
ret
// Expects target address in x0, and the SP to set in x1
.global "#JumpSetStack"
"#JumpSetStack":
mov sp, x1
br x0