.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` // 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. // 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