// 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/InvalidationTracker.h" #include "Common/TSOHandlerConfig.h" #include "Common/CPUFeatures.h" #include "DummyHandlers.h" #include "BTInterface.h" #include #include #include #include #include #include #include #include #include #include #include #include class ECSyscallHandler; void* X64ReturnInstr; // See Module.S extern void* ExitFunctionEC; 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]); } }; 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); // Windows always clears TF, DF and AF when handling an exception, restoring after. // TODO: Check windows behaviour for the restoring after, quite awkward to achieve with the BT API. Would need to fixup flags after a // rethrow and keep track of context pointers on the stack so if a SEH handler changes flags they can be restored in BeginContext after // the NtContinue syscall (which will convert to an ARM64 context and back, losing these flags). uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr); EFlags &= ~((1 << FEXCore::X86State::RFLAG_DF_RAW_LOC) | (1 << FEXCore::X86State::RFLAG_TF_LOC) | (1 << FEXCore::X86State::RFLAG_AF_RAW_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: 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; // 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; State.rip = Context.Rip; CTX->SetFlagsFromCompactedEFLAGS(Thread, Context.EFlags); 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; // 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)); } } // 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_CONTROL | CONTEXT_ARM64_INTEGER | CONTEXT_ARM64_FLOATING_POINT; 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 will be converted into EFlags by ntdll, the rest are lost during exception handling. // See HandleGuestException ECContext.Cpsr = Context.Cpsr; 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; 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 namespace Logging { static void MsgHandler(LogMan::DebugLevels Level, const char* Message) { const auto Output = fextl::fmt::format("[{}][{:X}] {}\n", LogMan::DebugLevelStr(Level), GetCurrentThreadId(), Message); __wine_dbg_output(Output.c_str()); } static void AssertHandler(const char* Message) { const auto Output = fextl::fmt::format("[ASSERT] {}\n", Message); __wine_dbg_output(Output.c_str()); } static void Init() { LogMan::Throw::InstallHandler(AssertHandler); LogMan::Msg::InstallHandler(MsgHandler); } } // namespace Logging class ECSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: ECSyscallHandler() { OSABI = FEXCore::HLE::SyscallOSABI::OS_WIN32; } 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(); Exception::LoadStateFromECContext(Thread, *Context); } void ProcessInit() { Logging::Init(); FEX::Config::InitializeConfigs(); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer()); FEXCore::Config::Load(); FEXCore::Config::ReloadMetaLayer(); 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(); 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; Exception::KiUserExceptionDispatcher = GetRedirectedProcAddress(GetModuleHandle("ntdll.dll"), "KiUserExceptionDispatcher"); } void ProcessTerm() {} class ScopedCallbackDisable { private: bool Prev; public: ScopedCallbackDisable() { Prev = GetCPUArea().Area->InSyscallCallback; GetCPUArea().Area->InSyscallCallback = true; } ~ScopedCallbackDisable() { GetCPUArea().Area->InSyscallCallback = Prev; } }; 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; } if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { const auto FaultAddress = static_cast(Exception->ExceptionInformation[1]); bool HandledRWX = false; if (InvalidationTracker && GetCPUArea().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; } } if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context->Pc) && !IsDispatcherAddress(Context->Pc)) { return STATUS_SUCCESS; } 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; } else { LogMan::Msg::EFmt("Unexpected exception in JIT code on guest stack"); return STATUS_SUCCESS; } } void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), Prot); } void NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } std::scoped_lock Lock(ThreadCreationMutex); if (!Size) { InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); } else if (FreeType & MEM_DECOMMIT) { InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), true); } } void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), NewProt); } void NotifyUnmapViewOfSection(void* Address) { if (!InvalidationTracker || !GetCPUArea().ThreadState()) { return; } std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); } 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); } NTSTATUS ThreadInit() { 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_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) { 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); return STATUS_SUCCESS; } BOOLEAN BTCpu64IsProcessorFeaturePresent(UINT Feature) { return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE; } void UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) { CPUFeatures->UpdateInformation(Info); }