// SPDX-License-Identifier: MIT /* $info$ tags: Bin|ARM64EC desc: Implements the ARM64EC BT module API using FEXCore $end_info$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Common/Config.h" #include "Common/HostFeatures.h" #include "Common/InvalidationTracker.h" #include "Common/TSOHandlerConfig.h" #include "Common/CPUFeatures.h" #include "Common/Logging.h" #include "Common/CRT/CRT.h" #include "DummyHandlers.h" #include "BTInterface.h" #include #include #include #include #include #include #include #include #include #include #include #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; explicit ThreadCPUArea(_TEB* TEB) : Area(*reinterpret_cast(reinterpret_cast(TEB) + TEBCPUAreaOffset)) {} uint64_t& EmulatorStackLimit() const { return Area->EmulatorStackLimit; } uint64_t& EmulatorStackBase() const { return Area->EmulatorStackBase; } ARM64EC_NT_CONTEXT& ContextAmd64() const { return *Area->ContextAmd64; } FEXCore::Core::CpuStateFrame*& StateFrame() const { return reinterpret_cast(Area->EmulatorData[0]); } FEXCore::Core::InternalThreadState*& ThreadState() const { return reinterpret_cast(Area->EmulatorData[1]); } uint64_t& DispatcherLoopTopEnterEC() const { return reinterpret_cast(Area->EmulatorData[2]); } uint64_t& DispatcherLoopTopEnterECFillSRA() const { return reinterpret_cast(Area->EmulatorData[3]); } }; extern "C" NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert); namespace { fextl::unique_ptr CTX; fextl::unique_ptr SignalDelegator; fextl::unique_ptr SyscallHandler; std::optional InvalidationTracker; std::optional CPUFeatures; std::recursive_mutex ThreadCreationMutex; // Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing std::unordered_map Threads; std::pair GetThreadCPUArea(HANDLE Thread) { THREAD_BASIC_INFORMATION Info; const NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); return {Err, ThreadCPUArea(reinterpret_cast<_TEB*>(Info.TebBaseAddress))}; } ThreadCPUArea GetCPUArea() { return ThreadCPUArea(NtCurrentTeb()); } bool IsEmulatorStackAddress(uint64_t Address) { return Address <= GetCPUArea().EmulatorStackBase() && Address >= GetCPUArea().EmulatorStackLimit(); } bool IsDispatcherAddress(uint64_t Address) { const auto& Config = SignalDelegator->GetConfig(); return Address >= Config.DispatcherBegin && Address < Config.DispatcherEnd; } // GetProcAddress on ARM64EC returns a pointer to an x64 fast forward sequence to allow for redirecting to the JIT if functions are // hotpatched. This looks up the procedure address of the native code even if the fast forward sequence has been patched. uintptr_t GetRedirectedProcAddress(HMODULE Module, const char* ProcName) { const uintptr_t Proc = reinterpret_cast(GetProcAddress(Module, ProcName)); if (!Proc) { return 0; } ULONG Size; const auto* LoadConfig = reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(RtlImageDirectoryEntryToData(Module, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &Size)); const auto* CHPEMetadata = reinterpret_cast(LoadConfig->CHPEMetadataPointer); const uintptr_t ModuleBase = reinterpret_cast(Module); const uintptr_t ProcRVA = Proc - ModuleBase; const auto* RedirectionTableBegin = reinterpret_cast(ModuleBase + CHPEMetadata->RedirectionMetadata); const auto* RedirectionTableEnd = RedirectionTableBegin + CHPEMetadata->RedirectionMetadataCount; const auto* It = std::lower_bound(RedirectionTableBegin, RedirectionTableEnd, ProcRVA, [](const auto& Entry, uintptr_t RVA) { return Entry.Source < RVA; }); if (It->Source != ProcRVA) { return 0; } return ModuleBase + It->Destination; } } // namespace namespace Exception { static std::optional HandlerConfig; static uintptr_t KiUserExceptionDispatcher; static EXCEPTION_RECORD HandleGuestException(const EXCEPTION_RECORD& Src, ARM64_NT_CONTEXT& Context) { auto* Thread = GetCPUArea().ThreadState(); auto& Fault = Thread->CurrentFrame->SynchronousFaultData; EXCEPTION_RECORD Dst = Src; Dst.ExceptionAddress = reinterpret_cast(Context.Pc); // X64 Windows always clears TF, DF and AF when handling an exception, restoring after. // Current ARM64EC windows can only restore NZCV+SS when returning from an exception and other flags are left untouched from the handler context. // TODO: Can extend wine to support this by mapping the remaining EFlags into reserved cpsr members. uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr); EFlags &= (1 << FEXCore::X86State::RFLAG_TF_LOC); CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags); if (!Fault.FaultToTopAndGeneratedException) { return Dst; } Fault.FaultToTopAndGeneratedException = false; Dst.ExceptionFlags = 0; Dst.NumberParameters = 0; switch (Fault.Signal) { case FEXCore::Core::FAULT_SIGILL: Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return Dst; case FEXCore::Core::FAULT_SIGTRAP: switch (Fault.TrapNo) { case FEXCore::X86State::X86_TRAPNO_DB: Context.Cpsr &= ~(1 << 21); // PSTATE.SS Dst.ExceptionCode = EXCEPTION_SINGLE_STEP; return Dst; case FEXCore::X86State::X86_TRAPNO_BP: Context.Pc -= 1; Dst.ExceptionAddress = reinterpret_cast(Context.Pc); Dst.ExceptionCode = EXCEPTION_BREAKPOINT; Dst.NumberParameters = 1; Dst.ExceptionInformation[0] = 0; return Dst; default: LogMan::Msg::EFmt("Unknown SIGTRAP trap: {}", Fault.TrapNo); break; } break; case FEXCore::Core::FAULT_SIGSEGV: switch (Fault.TrapNo) { case FEXCore::X86State::X86_TRAPNO_GP: if ((Fault.err_code & 0b111) == 0b010) { switch (Fault.err_code >> 3) { case 0x2d: Context.Pc += 2; Dst.ExceptionCode = EXCEPTION_BREAKPOINT; Dst.ExceptionAddress = reinterpret_cast(Context.Pc + 1); Dst.NumberParameters = 1; Dst.ExceptionInformation[0] = Context.X8; // RAX // Note that ExceptionAddress doesn't equal the reported context RIP here, this discrepancy expected and not having it can trigger anti-debug logic. return Dst; default: LogMan::Msg::EFmt("Unknown interrupt: 0x{:X}", Fault.err_code >> 3); break; } } else { Dst.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; return Dst; } break; case FEXCore::X86State::X86_TRAPNO_OF: Dst.ExceptionCode = EXCEPTION_INT_OVERFLOW; return Dst; default: LogMan::Msg::EFmt("Unknown SIGSEGV trap: {}", Fault.TrapNo); break; } break; default: LogMan::Msg::EFmt("Unknown signal type: {}", Fault.Signal); break; } // Default to SIGILL Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return Dst; } static bool HandleUnalignedAccess(ARM64_NT_CONTEXT& Context) { if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context.Pc)) { return false; } const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(GetCPUArea().ThreadState(), HandlerConfig->GetUnalignedHandlerType(), Context.Pc, &Context.X0); if (!Result.first) { return false; } Context.Pc += Result.second; return true; } static void LoadStateFromECContext(FEXCore::Core::InternalThreadState* Thread, CONTEXT& Context) { auto& State = Thread->CurrentFrame->State; 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.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_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); } 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; // 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_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) { const auto& Config = SignalDelegator->GetConfig(); auto* Thread = GetCPUArea().ThreadState(); auto& State = Thread->CurrentFrame->State; State.rip = CTX->RestoreRIPFromHostPC(Thread, Context.Pc); // Spill all SRA GPRs for (size_t i = 0; i < Config.SRAGPRCount; i++) { State.gregs[i] = Context.X[Config.SRAGPRMapping[i]]; } // Spill all SRA FPRs for (size_t i = 0; i < Config.SRAFPRCount; i++) { memcpy(State.xmm.sse.data[i], &Context.V[Config.SRAFPRMapping[i]], sizeof(__uint128_t)); } // Spill EFlags uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr); CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags); } // Reconstructs an x64 context from the input context within the JIT, packed into a regular ARM64 context following the ARM64EC register mapping static ARM64_NT_CONTEXT ReconstructPackedECContext(ARM64_NT_CONTEXT& Context) { ReconstructThreadState(Context); ARM64_NT_CONTEXT ECContext {}; ECContext.ContextFlags = CONTEXT_ARM64_FULL; auto* Thread = GetCPUArea().ThreadState(); auto& State = Thread->CurrentFrame->State; ECContext.X8 = State.gregs[FEXCore::X86State::REG_RAX]; ECContext.X0 = State.gregs[FEXCore::X86State::REG_RCX]; ECContext.X1 = State.gregs[FEXCore::X86State::REG_RDX]; ECContext.X27 = State.gregs[FEXCore::X86State::REG_RBX]; ECContext.Sp = State.gregs[FEXCore::X86State::REG_RSP]; ECContext.Fp = State.gregs[FEXCore::X86State::REG_RBP]; ECContext.X25 = State.gregs[FEXCore::X86State::REG_RSI]; ECContext.X26 = State.gregs[FEXCore::X86State::REG_RDI]; ECContext.X2 = State.gregs[FEXCore::X86State::REG_R8]; ECContext.X3 = State.gregs[FEXCore::X86State::REG_R9]; ECContext.X4 = State.gregs[FEXCore::X86State::REG_R10]; ECContext.X5 = State.gregs[FEXCore::X86State::REG_R11]; ECContext.X19 = State.gregs[FEXCore::X86State::REG_R12]; ECContext.X20 = State.gregs[FEXCore::X86State::REG_R13]; ECContext.X21 = State.gregs[FEXCore::X86State::REG_R14]; ECContext.X22 = State.gregs[FEXCore::X86State::REG_R15]; ECContext.Pc = State.rip; CTX->ReconstructXMMRegisters(Thread, reinterpret_cast<__uint128_t*>(&ECContext.V[0]), nullptr); ECContext.Lr = State.mm[0][0]; ECContext.X6 = State.mm[1][0]; ECContext.X7 = State.mm[2][0]; ECContext.X9 = State.mm[3][0]; ECContext.X16 = (State.mm[3][1] & 0xffff) << 48 | (State.mm[2][1] & 0xffff) << 32 | (State.mm[1][1] & 0xffff) << 16 | (State.mm[0][1] & 0xffff); ECContext.X10 = State.mm[4][0]; ECContext.X11 = State.mm[5][0]; ECContext.X12 = State.mm[6][0]; ECContext.X15 = State.mm[7][0]; ECContext.X17 = (State.mm[7][1] & 0xffff) << 48 | (State.mm[6][1] & 0xffff) << 32 | (State.mm[5][1] & 0xffff) << 16 | (State.mm[4][1] & 0xffff); // Zero all disallowed registers ECContext.X13 = 0; ECContext.X14 = 0; ECContext.X18 = 0; ECContext.X23 = 0; ECContext.X24 = 0; ECContext.X28 = 0; // NZCV+SS will be converted into EFlags by ntdll, the rest are lost during exception handling. // See HandleGuestException ECContext.Cpsr = Context.Cpsr; uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0); if (EFlags & (1U << FEXCore::X86State::RFLAG_TF_LOC)) { ECContext.Cpsr |= 1 << 21; // PSTATE.SS } ECContext.Fpcr = Context.Fpcr; ECContext.Fpsr = Context.Fpsr; return ECContext; } static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& Context) { const auto& Config = SignalDelegator->GetConfig(); 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]; }* Args = reinterpret_cast(FEXCore::AlignDown(GuestSp, 64)) - 1; LogMan::Msg::DFmt("Reconstructing context"); Args->Context = ReconstructPackedECContext(Context); LogMan::Msg::DFmt("pc: {:X} rip: {:X}", Context.Pc, Args->Context.Pc); Args->Rec = HandleGuestException(Rec, Args->Context); Context.Sp = reinterpret_cast(Args); Context.Pc = KiUserExceptionDispatcher; } } // namespace Exception class ECSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: ECSyscallHandler() { OSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC; } uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override { return 0; } FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override { return {.NumArgs = 0, .HasReturn = false, .HostSyscallNumber = -1}; } FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState* Thread, uint64_t GuestAddr) override { return {0, 0}; } void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override { InvalidationTracker->ReprotectRWXIntervals(Start, Length); } }; extern "C" void SyncThreadContext(CONTEXT* Context) { auto* Thread = GetCPUArea().ThreadState(); // All other EFlags bits are lost when converting to/from an ARM64EC context, so merge them in from the current JIT state. // This is advisable over dropping their values as thread suspend/resume uses this function, and that can happen at any point in guest code. static constexpr uint32_t ECValidEFlagsMask {(1U << FEXCore::X86State::RFLAG_OF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_CF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_ZF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_SF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_TF_LOC)}; uint32_t StateEFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0); Context->EFlags = (Context->EFlags & ECValidEFlagsMask) | (StateEFlags & ~ECValidEFlagsMask); Exception::LoadStateFromECContext(Thread, *Context); } NTSTATUS ProcessInit() { FEX::Windows::InitCRTProcess(); FEX::Config::InitializeConfigs(); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer()); FEXCore::Config::Load(); FEXCore::Config::ReloadMetaLayer(); FEX::Windows::Logging::Init(); FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "1"); // Not applicable to Windows FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0"); FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT); SignalDelegator = fextl::make_unique(); SyscallHandler = fextl::make_unique(); Exception::HandlerConfig.emplace(); CTX = FEXCore::Context::Context::CreateNewContext(); { auto HostFeatures = FEX::FetchHostFeatures(); CTX->SetHostFeatures(HostFeatures); } CTX->SetSignalDelegator(SignalDelegator.get()); CTX->SetSyscallHandler(SyscallHandler.get()); CTX->InitCore(); InvalidationTracker.emplace(*CTX, Threads); CPUFeatures.emplace(*CTX); X64ReturnInstr = ::VirtualAlloc(nullptr, FEXCore::Utils::FEX_PAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); *reinterpret_cast(X64ReturnInstr) = 0xc3; 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; } return STATUS_SUCCESS; } void ProcessTerm(HANDLE Handle, BOOL After, NTSTATUS Status) {} class ScopedCallbackDisable { private: bool Prev; public: ScopedCallbackDisable() { Prev = GetCPUArea().Area->InSyscallCallback; GetCPUArea().Area->InSyscallCallback = true; } ~ScopedCallbackDisable() { GetCPUArea().Area->InSyscallCallback = Prev; } }; 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 && CPUArea.ThreadState()) { std::scoped_lock Lock(ThreadCreationMutex); HandledRWX = InvalidationTracker->HandleRWXAccessViolation(FaultAddress); } if (HandledRWX) { LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", NativeContext->Pc, FaultAddress); return true; } } 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, *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 false; } } 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); } GetCPUArea().Area->InSimulation = false; GetCPUArea().Area->InSyscallCallback = 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, BOOL After, NTSTATUS Status) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } if (After) { return; } std::scoped_lock Lock(ThreadCreationMutex); 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, 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); } 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; } if (After) { return; } std::scoped_lock Lock(ThreadCreationMutex); 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; } std::scoped_lock Lock(ThreadCreationMutex); 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() { FEX::Windows::InitCRTThread(); 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); Thread->CurrentFrame->Pointers.Common.ExitFunctionEC = reinterpret_cast(&ExitFunctionEC); CPUArea.StateFrame() = Thread->CurrentFrame; uint64_t EnterEC = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterEC; CPUArea.DispatcherLoopTopEnterEC() = EnterEC; uint64_t EnterECFillSRA = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterECFillSRA; CPUArea.DispatcherLoopTopEnterECFillSRA() = EnterECFillSRA; CPUArea.ContextAmd64() = {.ContextFlags = CONTEXT_CONTROL | CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, .AMD64_SegCs = 0x33, .AMD64_SegDs = 0x2b, .AMD64_SegEs = 0x2b, .AMD64_SegFs = 0x53, .AMD64_SegGs = 0x2b, .AMD64_SegSs = 0x2b, .AMD64_EFlags = 0x202, .AMD64_MxCsr = 0x1f80, .AMD64_MxCsr_copy = 0x1f80, .AMD64_ControlWord = 0x27f}; Exception::LoadStateFromECContext(Thread, CPUArea.ContextAmd64().AMD64_Context); { std::scoped_lock Lock(ThreadCreationMutex); Threads.emplace(GetCurrentThreadId(), Thread); } CPUArea.ThreadState() = Thread; return STATUS_SUCCESS; } NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { const auto [Err, CPUArea] = GetThreadCPUArea(Thread); if (Err) { return Err; } auto* OldThreadState = CPUArea.ThreadState(); CPUArea.ThreadState() = nullptr; { THREAD_BASIC_INFORMATION Info; if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); std::scoped_lock Lock(ThreadCreationMutex); Threads.erase(ThreadTID); } CTX->DestroyThread(OldThreadState); ::VirtualFree(reinterpret_cast(GetCPUArea().EmulatorStackLimit()), 0, MEM_RELEASE); FEX::Windows::DeinitCRTThread(); return STATUS_SUCCESS; } BOOLEAN BTCpu64IsProcessorFeaturePresent(UINT Feature) { return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE; } void UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) { CPUFeatures->UpdateInformation(Info); }