From fa72be2ec53461c1b05760564cbaf4a40f89646f Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Wed, 31 Jul 2024 17:07:09 +0000 Subject: [PATCH 1/8] Windows: Don't warn for unknown CPU features This happens regularly as wine/games will scan all features from 0 to 64. --- Source/Windows/Common/CPUFeatures.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Windows/Common/CPUFeatures.cpp b/Source/Windows/Common/CPUFeatures.cpp index cad802e23..65f99b24e 100644 --- a/Source/Windows/Common/CPUFeatures.cpp +++ b/Source/Windows/Common/CPUFeatures.cpp @@ -68,7 +68,7 @@ bool CPUFeatures::IsFeaturePresent(uint32_t Feature) { case PF_SSE4_2_INSTRUCTIONS_AVAILABLE: return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE42); case PF_AVX_INSTRUCTIONS_AVAILABLE: return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX); case PF_AVX2_INSTRUCTIONS_AVAILABLE: return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX2); - default: LogMan::Msg::DFmt("Unknown CPU feature: {:X}", Feature); return false; + default: return false; } } From f48071dcbef0cacb09772266f7043bb0dfd551e6 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Wed, 31 Jul 2024 17:08:06 +0000 Subject: [PATCH 2/8] ARM64EC: Switch to the emulator stack in BeginSimulation Windows calls this function on the guest stack for some reason. --- Source/Windows/ARM64EC/Module.S | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S index 1ed2bd599..9d8e169ee 100644 --- a/Source/Windows/ARM64EC/Module.S +++ b/Source/Windows/ARM64EC/Module.S @@ -41,6 +41,9 @@ enter_jit: // Expects a CONTEXT pointer in x0 .global BeginSimulation BeginSimulation: + ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo + ldr x16, [x17, #0x8] // ChpeV2CpuAreaInfo->EmulatorStackBase + mov sp, x16 bl "#SyncThreadContext" ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo ldr x16, [x17, #0x48] // ChpeV2CpuAreaInfo->EmulatorData[3] - DispatcherLoopTopEnterECFillSRA From e2583249c658c5fe41143fc349f3801b14e715e3 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 17:29:36 +0000 Subject: [PATCH 3/8] ARM64EC: Allocate the emulator stack ourselves Actual Windows does not allocate it for us. --- Source/Windows/ARM64EC/Module.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 5e9364b84..8b6a0195e 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -56,11 +56,11 @@ struct ThreadCPUArea { explicit ThreadCPUArea(_TEB* TEB) : Area(*reinterpret_cast(reinterpret_cast(TEB) + TEBCPUAreaOffset)) {} - uint64_t EmulatorStackLimit() const { + uint64_t& EmulatorStackLimit() const { return Area->EmulatorStackLimit; } - uint64_t EmulatorStackBase() const { + uint64_t& EmulatorStackBase() const { return Area->EmulatorStackBase; } @@ -548,6 +548,11 @@ void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) { } NTSTATUS ThreadInit() { + static constexpr size_t EmulatorStackSize = 0x40000; + const uint64_t EmulatorStack = reinterpret_cast(::VirtualAlloc(nullptr, EmulatorStackSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + GetCPUArea().EmulatorStackLimit() = EmulatorStack; + GetCPUArea().EmulatorStackBase() = EmulatorStack + EmulatorStackSize; + const auto CPUArea = GetCPUArea(); auto* Thread = CTX->CreateThread(0, 0); @@ -602,6 +607,7 @@ NTSTATUS ThreadTerm(HANDLE Thread) { } CTX->DestroyThread(OldThreadState); + ::VirtualFree(reinterpret_cast(GetCPUArea().EmulatorStackLimit()), 0, MEM_RELEASE); return STATUS_SUCCESS; } From 4877bb3f19fdb606c7231975d3c343ea20132981 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 18:08:59 +0000 Subject: [PATCH 4/8] ARM64EC: Support directly issuing the NtContinue syscall This is required for handling SMC with the ResetToConsistentState arguments as used in Windows, as using the NTDLL exported NtContinue would wipe out any reserved registers in the ARM64EC ABI. For Windows the syscall numbers are somewhat stable, and the SVC instruction can be called directly. Since wine doesn't handle that on ARM64, hardcode the system call number and manually call into wine dispatcher. Once wine gains proper syscall thunks, those can be parsed to get the number and the hardcoding dropped. --- Source/Windows/ARM64EC/Module.S | 17 +++++++++++++++++ Source/Windows/ARM64EC/Module.cpp | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S index 9d8e169ee..6510dd447 100644 --- a/Source/Windows/ARM64EC/Module.S +++ b/Source/Windows/ARM64EC/Module.S @@ -81,3 +81,20 @@ ret_sp_misaligned: adrp lr, X64ReturnInstr 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 diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 8b6a0195e..8584afcf9 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -46,9 +46,17 @@ $end_info$ #include class ECSyscallHandler; + +extern "C" { void* X64ReturnInstr; // See Module.S extern void* ExitFunctionEC; +// 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; +} + struct ThreadCPUArea { static constexpr size_t TEBCPUAreaOffset = 0x1788; CHPE_V2_CPU_AREA_INFO* Area; @@ -85,6 +93,9 @@ struct ThreadCPUArea { } }; + +extern "C" NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert); + namespace { fextl::unique_ptr CTX; fextl::unique_ptr SignalDelegator; @@ -437,7 +448,12 @@ void ProcessInit() { X64ReturnInstr = ::VirtualAlloc(nullptr, FEXCore::Utils::FEX_PAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); *reinterpret_cast(X64ReturnInstr) = 0xc3; - Exception::KiUserExceptionDispatcher = GetRedirectedProcAddress(GetModuleHandle("ntdll.dll"), "KiUserExceptionDispatcher"); + const auto NtDll = GetModuleHandle("ntdll.dll"); + Exception::KiUserExceptionDispatcher = GetRedirectedProcAddress(NtDll, "KiUserExceptionDispatcher"); + const auto WineSyscallDispatcherPtr = reinterpret_cast(GetProcAddress(NtDll, "__wine_syscall_dispatcher")); + if (WineSyscallDispatcherPtr) { + WineSyscallDispatcher = *WineSyscallDispatcherPtr; + } } void ProcessTerm() {} From af9bcb3efd6874a0689a7b67aa199f6aede3e07f Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 17:43:36 +0000 Subject: [PATCH 5/8] ARM64EC: Avoid syncing uninitialized context members to the JIT state Windows can sometimes pass in incomplete contexts to BeginSimulation. So only sync the valid parts specified in ContextFlags. --- Source/Windows/ARM64EC/Module.cpp | 101 ++++++++++++++++-------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 8584afcf9..7391ff25b 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -243,55 +243,64 @@ static bool HandleUnalignedAccess(ARM64_NT_CONTEXT& Context) { static void LoadStateFromECContext(FEXCore::Core::InternalThreadState* Thread, CONTEXT& Context) { auto& State = Thread->CurrentFrame->State; - // General register state - State.gregs[FEXCore::X86State::REG_RAX] = Context.Rax; - State.gregs[FEXCore::X86State::REG_RCX] = Context.Rcx; - State.gregs[FEXCore::X86State::REG_RDX] = Context.Rdx; - State.gregs[FEXCore::X86State::REG_RBX] = Context.Rbx; - State.gregs[FEXCore::X86State::REG_RSP] = Context.Rsp; - State.gregs[FEXCore::X86State::REG_RBP] = Context.Rbp; - State.gregs[FEXCore::X86State::REG_RSI] = Context.Rsi; - State.gregs[FEXCore::X86State::REG_RDI] = Context.Rdi; - State.gregs[FEXCore::X86State::REG_R8] = Context.R8; - State.gregs[FEXCore::X86State::REG_R9] = Context.R9; - State.gregs[FEXCore::X86State::REG_R10] = Context.R10; - State.gregs[FEXCore::X86State::REG_R11] = Context.R11; - State.gregs[FEXCore::X86State::REG_R12] = Context.R12; - State.gregs[FEXCore::X86State::REG_R13] = Context.R13; - State.gregs[FEXCore::X86State::REG_R14] = Context.R14; - State.gregs[FEXCore::X86State::REG_R15] = Context.R15; + if (Context.ContextFlags & CONTEXT_INTEGER) { + // General register state + State.gregs[FEXCore::X86State::REG_RAX] = Context.Rax; + State.gregs[FEXCore::X86State::REG_RCX] = Context.Rcx; + State.gregs[FEXCore::X86State::REG_RDX] = Context.Rdx; + State.gregs[FEXCore::X86State::REG_RBX] = Context.Rbx; - State.rip = Context.Rip; - CTX->SetFlagsFromCompactedEFLAGS(Thread, Context.EFlags); + State.gregs[FEXCore::X86State::REG_RSI] = Context.Rsi; + State.gregs[FEXCore::X86State::REG_RDI] = Context.Rdi; + State.gregs[FEXCore::X86State::REG_R8] = Context.R8; + State.gregs[FEXCore::X86State::REG_R9] = Context.R9; + State.gregs[FEXCore::X86State::REG_R10] = Context.R10; + State.gregs[FEXCore::X86State::REG_R11] = Context.R11; + State.gregs[FEXCore::X86State::REG_R12] = Context.R12; + State.gregs[FEXCore::X86State::REG_R13] = Context.R13; + State.gregs[FEXCore::X86State::REG_R14] = Context.R14; + State.gregs[FEXCore::X86State::REG_R15] = Context.R15; + } - State.es_idx = Context.SegEs & 0xffff; - State.cs_idx = Context.SegCs & 0xffff; - State.ss_idx = Context.SegSs & 0xffff; - State.ds_idx = Context.SegDs & 0xffff; - State.fs_idx = Context.SegFs & 0xffff; - State.gs_idx = Context.SegGs & 0xffff; + if (Context.ContextFlags & CONTEXT_CONTROL) { + State.rip = Context.Rip; + State.gregs[FEXCore::X86State::REG_RSP] = Context.Rsp; + State.gregs[FEXCore::X86State::REG_RBP] = Context.Rbp; + CTX->SetFlagsFromCompactedEFLAGS(Thread, Context.EFlags); + } - // The TEB is the only populated GDT entry by default - const auto TEB = reinterpret_cast(NtCurrentTeb()); - State.gdt[(Context.SegGs & 0xffff) >> 3].base = TEB; - State.gs_cached = TEB; - State.fs_cached = 0; - State.es_cached = 0; - State.cs_cached = 0; - State.ss_cached = 0; - State.ds_cached = 0; + if (Context.ContextFlags & CONTEXT_SEGMENTS) { + State.es_idx = Context.SegEs & 0xffff; + State.cs_idx = Context.SegCs & 0xffff; + State.ss_idx = Context.SegSs & 0xffff; + State.ds_idx = Context.SegDs & 0xffff; + State.fs_idx = Context.SegFs & 0xffff; + State.gs_idx = Context.SegGs & 0xffff; - // Floating-point register state - CTX->SetXMMRegistersFromState(Thread, reinterpret_cast(Context.FltSave.XmmRegisters), nullptr); - memcpy(State.mm, Context.FltSave.FloatRegisters, sizeof(State.mm)); + // The TEB is the only populated GDT entry by default + const auto TEB = reinterpret_cast(NtCurrentTeb()); + State.gdt[(Context.SegGs & 0xffff) >> 3].base = TEB; + State.gs_cached = TEB; + State.fs_cached = 0; + State.es_cached = 0; + State.cs_cached = 0; + State.ss_cached = 0; + State.ds_cached = 0; + } - State.FCW = Context.FltSave.ControlWord; - State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (Context.FltSave.StatusWord >> 8) & 1; - State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (Context.FltSave.StatusWord >> 9) & 1; - State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (Context.FltSave.StatusWord >> 10) & 1; - State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (Context.FltSave.StatusWord >> 14) & 1; - State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (Context.FltSave.StatusWord >> 11) & 0b111; - State.AbridgedFTW = Context.FltSave.TagWord; + if (Context.ContextFlags & CONTEXT_FLOATING_POINT) { + // Floating-point register state + CTX->SetXMMRegistersFromState(Thread, reinterpret_cast(Context.FltSave.XmmRegisters), nullptr); + memcpy(State.mm, Context.FltSave.FloatRegisters, sizeof(State.mm)); + + State.FCW = Context.FltSave.ControlWord; + State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (Context.FltSave.StatusWord >> 8) & 1; + State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (Context.FltSave.StatusWord >> 9) & 1; + State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (Context.FltSave.StatusWord >> 10) & 1; + State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (Context.FltSave.StatusWord >> 14) & 1; + State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (Context.FltSave.StatusWord >> 11) & 0b111; + State.AbridgedFTW = Context.FltSave.TagWord; + } } static void ReconstructThreadState(ARM64_NT_CONTEXT& Context) { @@ -317,7 +326,7 @@ static ARM64_NT_CONTEXT ReconstructPackedECContext(ARM64_NT_CONTEXT& Context) { ReconstructThreadState(Context); ARM64_NT_CONTEXT ECContext {}; - ECContext.ContextFlags = CONTEXT_ARM64_CONTROL | CONTEXT_ARM64_INTEGER | CONTEXT_ARM64_FLOATING_POINT; + ECContext.ContextFlags = CONTEXT_ARM64_FULL; auto* Thread = GetCPUArea().ThreadState(); auto& State = Thread->CurrentFrame->State; @@ -581,7 +590,7 @@ NTSTATUS ThreadInit() { uint64_t EnterECFillSRA = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterECFillSRA; CPUArea.DispatcherLoopTopEnterECFillSRA() = EnterECFillSRA; - CPUArea.ContextAmd64() = {.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CPUArea.ContextAmd64() = {.ContextFlags = CONTEXT_CONTROL | CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, .AMD64_SegCs = 0x33, .AMD64_SegDs = 0x2b, .AMD64_SegEs = 0x2b, From f121be649dcae37bfe4261e45fae3771343464dd Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 18:17:45 +0000 Subject: [PATCH 6/8] ARM64EC: Match the Windows BT API function prototypes --- Source/Windows/ARM64EC/BTInterface.h | 16 ++--- Source/Windows/ARM64EC/Module.cpp | 95 +++++++++++++++++++--------- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Source/Windows/ARM64EC/BTInterface.h b/Source/Windows/ARM64EC/BTInterface.h index 7f14a5f2e..50336c6d1 100644 --- a/Source/Windows/ARM64EC/BTInterface.h +++ b/Source/Windows/ARM64EC/BTInterface.h @@ -6,16 +6,16 @@ #include extern "C" { -void STDMETHODCALLTYPE ProcessInit(); -void STDMETHODCALLTYPE ProcessTerm(); +NTSTATUS STDMETHODCALLTYPE ProcessInit(); +void STDMETHODCALLTYPE ProcessTerm(HANDLE Handle, BOOL After, NTSTATUS Status); NTSTATUS STDMETHODCALLTYPE ThreadInit(); -NTSTATUS STDMETHODCALLTYPE ThreadTerm(HANDLE Thread); -NTSTATUS STDMETHODCALLTYPE ResetToConsistentState(EXCEPTION_POINTERS* Ptrs, ARM64_NT_CONTEXT* Context, BOOLEAN* Continue); +NTSTATUS STDMETHODCALLTYPE ThreadTerm(HANDLE Thread, LONG ExitCode); +NTSTATUS STDMETHODCALLTYPE ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext); void STDMETHODCALLTYPE BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size); -void STDMETHODCALLTYPE NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot); -void STDMETHODCALLTYPE NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType); -void STDMETHODCALLTYPE NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt); -void STDMETHODCALLTYPE NotifyUnmapViewOfSection(void* Address); +void STDMETHODCALLTYPE NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot, BOOL After, NTSTATUS Status); +void STDMETHODCALLTYPE NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType, BOOL After, NTSTATUS Status); +void STDMETHODCALLTYPE NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, NTSTATUS Status); +void STDMETHODCALLTYPE NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status); BOOLEAN STDMETHODCALLTYPE BTCpu64IsProcessorFeaturePresent(UINT Feature); void STDMETHODCALLTYPE UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info); } diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 7391ff25b..55d96bc94 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -427,7 +427,7 @@ extern "C" void SyncThreadContext(CONTEXT* Context) { Exception::LoadStateFromECContext(Thread, *Context); } -void ProcessInit() { +NTSTATUS ProcessInit() { FEX::Config::InitializeConfigs(); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); @@ -463,9 +463,11 @@ void ProcessInit() { if (WineSyscallDispatcherPtr) { WineSyscallDispatcher = *WineSyscallDispatcherPtr; } + + return STATUS_SUCCESS; } -void ProcessTerm() {} +void ProcessTerm(HANDLE Handle, BOOL After, NTSTATUS Status) {} class ScopedCallbackDisable { private: @@ -482,83 +484,118 @@ public: } }; -NTSTATUS ResetToConsistentState(EXCEPTION_POINTERS* Ptrs, ARM64_NT_CONTEXT* Context, BOOLEAN* Continue) { - ScopedCallbackDisable Guard; - const auto* Exception = Ptrs->ExceptionRecord; - if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Exception::HandleUnalignedAccess(*Context)) { - LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc); - *Continue = true; - return STATUS_SUCCESS; - } +bool ResetToConsistentStateImpl(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext) { + LogMan::Msg::DFmt("Exception: Code: {:X} Address: {:X}", Exception->ExceptionCode, reinterpret_cast(Exception->ExceptionAddress)); + + const auto CPUArea = GetCPUArea(); if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { const auto FaultAddress = static_cast(Exception->ExceptionInformation[1]); bool HandledRWX = false; - if (InvalidationTracker && GetCPUArea().ThreadState()) { + if (InvalidationTracker && CPUArea.ThreadState()) { std::scoped_lock Lock(ThreadCreationMutex); HandledRWX = InvalidationTracker->HandleRWXAccessViolation(FaultAddress); } if (HandledRWX) { - LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", Context->Pc, FaultAddress); - *Continue = true; - return STATUS_SUCCESS; + LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", NativeContext->Pc, FaultAddress); + return true; } } - if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context->Pc) && !IsDispatcherAddress(Context->Pc)) { - return STATUS_SUCCESS; + if (!CTX->IsAddressInCodeBuffer(CPUArea.ThreadState(), NativeContext->Pc) && !IsDispatcherAddress(NativeContext->Pc)) { + LogMan::Msg::DFmt("Passing through exception"); + return false; + } + + if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Exception::HandleUnalignedAccess(*NativeContext)) { + LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", NativeContext->Pc); + return true; } if (IsEmulatorStackAddress(reinterpret_cast(__builtin_frame_address(0)))) { - Exception::RethrowGuestException(*Exception, *Context); - LogMan::Msg::DFmt("Rethrowing onto guest stack: {:X}", Context->Sp); - *Continue = true; - return STATUS_SUCCESS; + Exception::RethrowGuestException(*Exception, *NativeContext); + LogMan::Msg::DFmt("Rethrowing onto guest stack: {:X}", NativeContext->Sp); + return true; } else { LogMan::Msg::EFmt("Unexpected exception in JIT code on guest stack"); - return STATUS_SUCCESS; + return false; } } -void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot) { +NTSTATUS ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext) { + if (!GetCPUArea().ThreadState()) { + return STATUS_SUCCESS; + } + + bool Cont {}; + { + + ScopedCallbackDisable guard; + Cont = ResetToConsistentStateImpl(Exception, GuestContext, NativeContext); + } + + if (Cont) { + NtContinueNative(NativeContext, false); + } + + return STATUS_SUCCESS; +} + +void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } + if (!After || Status) { + return; + } + std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), Prot); } -void NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType) { +void NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } + if (After) { + return; + } + std::scoped_lock Lock(ThreadCreationMutex); - if (!Size) { - InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); - } else if (FreeType & MEM_DECOMMIT) { + if (FreeType & MEM_DECOMMIT) { InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), true); + } else if (FreeType & MEM_RELEASE) { + InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); } } -void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt) { +void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } + if (!After || Status) { + return; + } + std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), NewProt); } -void NotifyUnmapViewOfSection(void* Address) { +void NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } + if (After) { + return; + } + std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); } @@ -612,7 +649,7 @@ NTSTATUS ThreadInit() { return STATUS_SUCCESS; } -NTSTATUS ThreadTerm(HANDLE Thread) { +NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { const auto [Err, CPUArea] = GetThreadCPUArea(Thread); if (Err) { return Err; From 27973d5637dd5c6346a461ac561a11202b2239a3 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 20:21:36 +0000 Subject: [PATCH 7/8] ARM64EC: Fix the exception dispatcher stack layout --- Source/Windows/ARM64EC/Module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 55d96bc94..349271c88 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -385,6 +385,7 @@ static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& uint64_t GuestSp = Context.X[Config.SRAGPRMapping[static_cast(FEXCore::X86State::REG_RSP)]]; struct DispatchArgs { ARM64_NT_CONTEXT Context; + uint64_t Pad[4]; // Only present on newer Windows versions, likely for SVE. EXCEPTION_RECORD Rec; uint64_t Align; uint64_t Redzone[2]; From 115fbb5039d5ace0158f5ce619e535c4645c1ecb Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 20:18:10 +0000 Subject: [PATCH 8/8] ARM64EC: Implement remaining notification callbacks --- Source/Windows/ARM64EC/BTInterface.h | 6 +++++- Source/Windows/ARM64EC/Module.cpp | 24 ++++++++++++++++++++++++ Source/Windows/ARM64EC/libarm64ecfex.def | 6 ++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Source/Windows/ARM64EC/BTInterface.h b/Source/Windows/ARM64EC/BTInterface.h index 50336c6d1..d57603079 100644 --- a/Source/Windows/ARM64EC/BTInterface.h +++ b/Source/Windows/ARM64EC/BTInterface.h @@ -11,11 +11,15 @@ void STDMETHODCALLTYPE ProcessTerm(HANDLE Handle, BOOL After, NTSTATUS Status); NTSTATUS STDMETHODCALLTYPE ThreadInit(); NTSTATUS STDMETHODCALLTYPE ThreadTerm(HANDLE Thread, LONG ExitCode); NTSTATUS STDMETHODCALLTYPE ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext); -void STDMETHODCALLTYPE BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size); void STDMETHODCALLTYPE NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot, BOOL After, NTSTATUS Status); void STDMETHODCALLTYPE NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType, BOOL After, NTSTATUS Status); void STDMETHODCALLTYPE NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, NTSTATUS Status); +NTSTATUS STDMETHODCALLTYPE NotifyMapViewOfSection(void* Unk1, void* Address, void* Unk2, SIZE_T Size, ULONG AllocType, ULONG Prot); void STDMETHODCALLTYPE NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status); +void STDMETHODCALLTYPE FlushInstructionCacheHeavy(const void* Address, SIZE_T Size); +void STDMETHODCALLTYPE BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size); +void STDMETHODCALLTYPE BTCpu64NotifyMemoryDirty(void* Address, SIZE_T Size); +void STDMETHODCALLTYPE BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status); BOOLEAN STDMETHODCALLTYPE BTCpu64IsProcessorFeaturePresent(UINT Feature); void STDMETHODCALLTYPE UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info); } diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 349271c88..0edc3d7cd 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -588,6 +588,10 @@ void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), NewProt); } +NTSTATUS NotifyMapViewOfSection(void* Unk1, void* Address, void* Unk2, SIZE_T Size, ULONG AllocType, ULONG Prot) { + return STATUS_SUCCESS; +} + void NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; @@ -601,6 +605,15 @@ void NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status) { InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); } +void FlushInstructionCacheHeavy(const void* Address, SIZE_T Size) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); +} + void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; @@ -610,6 +623,17 @@ void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) { InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); } +void BTCpu64NotifyMemoryDirty(void* Address, SIZE_T Size) { + if (!InvalidationTracker || !GetCPUArea().ThreadState()) { + return; + } + + std::scoped_lock Lock(ThreadCreationMutex); + InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); +} + +void BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {} + NTSTATUS ThreadInit() { static constexpr size_t EmulatorStackSize = 0x40000; const uint64_t EmulatorStack = reinterpret_cast(::VirtualAlloc(nullptr, EmulatorStackSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); diff --git a/Source/Windows/ARM64EC/libarm64ecfex.def b/Source/Windows/ARM64EC/libarm64ecfex.def index 035e03be0..bb51ca889 100644 --- a/Source/Windows/ARM64EC/libarm64ecfex.def +++ b/Source/Windows/ARM64EC/libarm64ecfex.def @@ -3,12 +3,14 @@ LIBRARY libarm64ecfex.dll EXPORTS BTCpu64FlushInstructionCache BTCpu64IsProcessorFeaturePresent + BTCpu64NotifyMemoryDirty + BTCpu64NotifyReadFile DispatchJump DATA RetToEntryThunk DATA ExitToX64 DATA BeginSimulation DATA -; FlushInstructionCacheHeavy -; NotifyMapViewOfSection + FlushInstructionCacheHeavy + NotifyMapViewOfSection NotifyMemoryAlloc NotifyMemoryFree NotifyMemoryProtect