// SPDX-License-Identifier: MIT /* $info$ tags: Bin|WOW64 desc: Implements the WOW64 BT module API using FEXCore $end_info$ */ // Thanks to André Zwing, whose ideas from https://github.com/AndreRH/hangover this code is based upon #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Windows/Common/Allocator.h" #include "Windows/Common/EnvironmentVariablesHandling.h" #include "Windows/Common/FEXUnixLib.h" #include "Common/CallRetStack.h" #include "Common/JITGuardPage.h" #include "Common/Config.h" #include "Common/Exception.h" #include "Common/TSOHandlerConfig.h" #include "Common/ImageTracker.h" #include "Common/InvalidationTracker.h" #include "Common/Threads.h" #include "Common/OvercommitTracker.h" #include "Common/CPUFeatures.h" #include "Common/Logging.h" #include "Common/Module.h" #include "Common/CRT/CRT.h" #include "Common/PortabilityInfo.h" #include "Common/Handle.h" #include "DummyHandlers.h" #include "BTInterface.h" #include "Windows/Common/SHMStats.h" #include #include #include #include #include #include #include #include #include #include #include namespace ControlBits { // When this is unset, a thread can be safely interrupted and have its context recovered // IMPORTANT: This can only safely be written by the owning thread static constexpr uint32_t IN_JIT {1U << 0}; // JIT entry polls this bit until it is unset, at which point CONTROL_IN_JIT will be set static constexpr uint32_t PAUSED {1U << 1}; // When this is set, the CPU context stored in the CPU area has not yet been flushed to the FEX TLS static constexpr uint32_t WOW_CPU_AREA_DIRTY {1U << 2}; }; // namespace ControlBits struct TLS { enum class Slot : size_t { ENTRY_CONTEXT = WOW64_TLS_MAX_NUMBER - 1, CONTROL_WORD = WOW64_TLS_MAX_NUMBER - 2, THREAD_STATE = WOW64_TLS_MAX_NUMBER - 3, CACHED_CALLRET_SP = WOW64_TLS_MAX_NUMBER - 4, }; _TEB* TEB; explicit TLS(_TEB* TEB) : TEB(TEB) {} WOW64INFO& Wow64Info() const { return *reinterpret_cast(TEB->TlsSlots[WOW64_TLS_WOW64INFO]); } std::atomic& ControlWord() const { // TODO: Change this when libc++ gains std::atomic_ref support return reinterpret_cast&>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::CONTROL_WORD)]); } uint32_t* ControlWordAddress() const { return reinterpret_cast(&TEB->TlsSlots[FEXCore::ToUnderlying(Slot::CONTROL_WORD)]); } CONTEXT*& EntryContext() const { return reinterpret_cast(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::ENTRY_CONTEXT)]); } FEXCore::Core::InternalThreadState*& ThreadState() const { return reinterpret_cast(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::THREAD_STATE)]); } // This is used to work around user callback handling (see Wow64KiUserCallbackDispatcher in wine) unbalancing the // call-ret stace since user callbacks are returned from using a syscall that we can't really intercept. uint64_t& CachedCallRetSp() const { return reinterpret_cast(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::CACHED_CALLRET_SP)]); } }; struct FrontendThreadData { bool InLockedRWXRead {}; }; class WowSyscallHandler; namespace { namespace BridgeInstrs { // These directly jumped to by the guest to make system calls void* Syscall {}; void* UnixCall {}; } // namespace BridgeInstrs fextl::unique_ptr CTX; fextl::unique_ptr SignalDelegator; fextl::unique_ptr SyscallHandler; fextl::unique_ptr StatAllocHandler; std::optional InvalidationTracker; std::optional CPUFeatures; std::optional OvercommitTracker; std::optional ImageTracker; std::mutex ThreadCreationMutex; // Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing std::unordered_map Threads; decltype(__wine_unix_call_dispatcher) WineUnixCall; std::pair GetThreadTLS(HANDLE Thread) { THREAD_BASIC_INFORMATION Info; const NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); return {Err, TLS {reinterpret_cast<_TEB*>(Info.TebBaseAddress)}}; } TLS GetTLS() { return TLS {NtCurrentTeb()}; } FrontendThreadData* GetFrontendThreadData(FEXCore::Core::InternalThreadState* Thread) { return static_cast(Thread->FrontendPtr); } uint64_t GetWowTEB(void* TEB) { static constexpr size_t WowTEBOffsetMemberOffset {0x180c}; return static_cast( *reinterpret_cast(reinterpret_cast(TEB) + WowTEBOffsetMemberOffset) + reinterpret_cast(TEB)); } bool IsDispatcherAddress(uint64_t Address) { const auto& Config = SignalDelegator->GetConfig(); return Address >= Config.DispatcherBegin && Address < Config.DispatcherEnd; } bool IsAddressInJit(uint64_t Address) { if (IsDispatcherAddress(Address)) { return true; } auto Thread = GetTLS().ThreadState(); return Thread->CTX->IsAddressInCodeBuffer(Thread, Address); } void HandleImageMap(uint64_t Address, bool MainImage = false) { fextl::string ModulePath = FEX::Windows::GetSectionFilePath(Address); fextl::string ModuleName = fextl::string {FEX::Windows::BaseName(ModulePath)}; InvalidationTracker->HandleImageMap(ModuleName, Address); ImageTracker->HandleImageMap(ModulePath, Address, MainImage); } void HandleImageUnmap(uint64_t Address, uint64_t Size) { ImageTracker->HandleImageUnmap(Address, Size); } } // namespace namespace Context { void LoadStateFromWowContext(FEXCore::Core::InternalThreadState* Thread, uint64_t WowTEB, WOW64_CONTEXT* Context) { auto& State = Thread->CurrentFrame->State; // General register state State.gregs[FEXCore::X86State::REG_RAX] = Context->Eax; State.gregs[FEXCore::X86State::REG_RBX] = Context->Ebx; State.gregs[FEXCore::X86State::REG_RCX] = Context->Ecx; State.gregs[FEXCore::X86State::REG_RDX] = Context->Edx; State.gregs[FEXCore::X86State::REG_RSI] = Context->Esi; State.gregs[FEXCore::X86State::REG_RDI] = Context->Edi; State.gregs[FEXCore::X86State::REG_RBP] = Context->Ebp; State.gregs[FEXCore::X86State::REG_RSP] = Context->Esp; State.rip = Context->Eip; 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 auto GDT = State.GetSegmentFromIndex(State, (Context->SegFs & 0xffff)); State.SetGDTBase(GDT, WowTEB); State.SetGDTLimit(GDT, 0xF'FFFFU); State.fs_cached = WowTEB; State.es_cached = 0; State.cs_cached = 0; State.ss_cached = 0; State.ds_cached = 0; // Floating-point register state const auto* XSave = reinterpret_cast(Context->ExtendedRegisters); CTX->SetXMMRegistersFromState(Thread, reinterpret_cast(XSave->XmmRegisters), nullptr); memcpy(State.mm, XSave->FloatRegisters, sizeof(State.mm)); State.FCW = XSave->ControlWord; State.flags[FEXCore::X86State::X87FLAG_IE_LOC] = XSave->StatusWord & 1; State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (XSave->StatusWord >> 8) & 1; State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (XSave->StatusWord >> 9) & 1; State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (XSave->StatusWord >> 10) & 1; State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (XSave->StatusWord >> 14) & 1; State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (XSave->StatusWord >> 11) & 0b111; State.AbridgedFTW = XSave->TagWord; } void StoreWowContextFromState(FEXCore::Core::InternalThreadState* Thread, WOW64_CONTEXT* Context) { auto& State = Thread->CurrentFrame->State; // General register state Context->Eax = State.gregs[FEXCore::X86State::REG_RAX]; Context->Ebx = State.gregs[FEXCore::X86State::REG_RBX]; Context->Ecx = State.gregs[FEXCore::X86State::REG_RCX]; Context->Edx = State.gregs[FEXCore::X86State::REG_RDX]; Context->Esi = State.gregs[FEXCore::X86State::REG_RSI]; Context->Edi = State.gregs[FEXCore::X86State::REG_RDI]; Context->Ebp = State.gregs[FEXCore::X86State::REG_RBP]; Context->Esp = State.gregs[FEXCore::X86State::REG_RSP]; Context->Eip = State.rip; Context->EFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0); Context->SegEs = State.es_idx; Context->SegCs = State.cs_idx; Context->SegSs = State.ss_idx; Context->SegDs = State.ds_idx; Context->SegFs = State.fs_idx; Context->SegGs = State.gs_idx; // Floating-point register state auto* XSave = reinterpret_cast(Context->ExtendedRegisters); CTX->ReconstructXMMRegisters(Thread, reinterpret_cast<__uint128_t*>(XSave->XmmRegisters), nullptr); memcpy(XSave->FloatRegisters, State.mm, sizeof(State.mm)); XSave->ControlWord = State.FCW; XSave->StatusWord = (State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] << 11) | (State.flags[FEXCore::X86State::X87FLAG_C0_LOC] << 8) | (State.flags[FEXCore::X86State::X87FLAG_C1_LOC] << 9) | (State.flags[FEXCore::X86State::X87FLAG_C2_LOC] << 10) | (State.flags[FEXCore::X86State::X87FLAG_C3_LOC] << 14) | State.flags[FEXCore::X86State::X87FLAG_IE_LOC]; XSave->TagWord = State.AbridgedFTW; Context->FloatSave.ControlWord = XSave->ControlWord; Context->FloatSave.StatusWord = XSave->StatusWord; Context->FloatSave.TagWord = FEXCore::FPState::ConvertFromAbridgedFTW(XSave->StatusWord, State.mm, XSave->TagWord); Context->FloatSave.ErrorOffset = XSave->ErrorOffset; Context->FloatSave.ErrorSelector = XSave->ErrorSelector | (XSave->ErrorOpcode << 16); Context->FloatSave.DataOffset = XSave->DataOffset; Context->FloatSave.DataSelector = XSave->DataSelector; Context->FloatSave.Cr0NpxState = XSave->StatusWord | 0xffff0000; } NTSTATUS FlushThreadStateContext(HANDLE Thread) { const auto [Err, TLS] = GetThreadTLS(Thread); if (Err) { return Err; } WOW64_CONTEXT TmpWowContext {.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS}; Context::StoreWowContextFromState(TLS.ThreadState(), &TmpWowContext); return RtlWow64SetThreadContext(Thread, &TmpWowContext); } void ReconstructThreadState(TLS TLS, CONTEXT* Context) { const auto& Config = SignalDelegator->GetConfig(); auto* Thread = TLS.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); } WOW64_CONTEXT ReconstructWowContext(TLS TLS, CONTEXT* Context) { if (!IsDispatcherAddress(Context->Pc)) { ReconstructThreadState(TLS, Context); } WOW64_CONTEXT WowContext { .ContextFlags = WOW64_CONTEXT_ALL, }; auto* XSave = reinterpret_cast(WowContext.ExtendedRegisters); XSave->ControlWord = 0x27f; XSave->MxCsr = 0x1f80; Context::StoreWowContextFromState(TLS.ThreadState(), &WowContext); return WowContext; } static std::optional HandlerConfig; bool HandleUnalignedAccess(TLS TLS, CONTEXT* Context) { auto Thread = TLS.ThreadState(); if (!Thread->CTX->IsAddressInCodeBuffer(Thread, Context->Pc)) { return false; } const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(Thread, HandlerConfig->GetUnalignedHandlerType(), Context->Pc, &Context->X0); Context->Pc += Result.value_or(0); return Result.has_value(); } void LockJITContext(TLS TLS) { uint32_t Expected = TLS.ControlWord().load(), New; // Spin until PAUSED is unset, setting IN_JIT when that occurs do { Expected = Expected & ~ControlBits::PAUSED; New = (Expected | ControlBits::IN_JIT) & ~ControlBits::WOW_CPU_AREA_DIRTY; } while (!TLS.ControlWord().compare_exchange_weak(Expected, New, std::memory_order::relaxed)); std::atomic_signal_fence(std::memory_order::seq_cst); // If the CPU area is dirty, flush it to the JIT context before reentry if (Expected & ControlBits::WOW_CPU_AREA_DIRTY) { WOW64_CONTEXT* WowContext; RtlWow64GetCurrentCpuArea(nullptr, reinterpret_cast(&WowContext), nullptr); Context::LoadStateFromWowContext(TLS.ThreadState(), GetWowTEB(NtCurrentTeb()), WowContext); } } void UnlockJITContext(TLS TLS) { std::atomic_signal_fence(std::memory_order::seq_cst); TLS.ControlWord().fetch_and(~ControlBits::IN_JIT, std::memory_order::relaxed); } class ScopedJITContextLock { private: TLS TLSData; public: ScopedJITContextLock(TLS TLSData) : TLSData {TLSData} { LockJITContext(TLSData); } ~ScopedJITContextLock() { UnlockJITContext(TLSData); } }; bool HandleSuspendInterrupt(TLS TLS, CONTEXT* Context, uint64_t FaultAddress) { if (FaultAddress != reinterpret_cast(&TLS.ThreadState()->InterruptFaultPage)) { return false; } void* TmpAddress = reinterpret_cast(FaultAddress); SIZE_T TmpSize = FEXCore::Utils::FEX_PAGE_SIZE; ULONG TmpProt; NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_READWRITE, &TmpProt); // Since interrupts only happen at the start of blocks, the reconstructed state should be entirely accurate ReconstructThreadState(TLS, Context); // Yield to the suspender UnlockJITContext(TLS); LockJITContext(TLS); // Adjust context to return to the dispatcher, reloading SRA from thread state const auto& Config = SignalDelegator->GetConfig(); Context->Pc = Config.AbsoluteLoopTopAddressFillSRA; Context->X1 = 0; // Set ENTRY_FILL_SRA_SINGLE_INST_REG return true; } } // namespace Context // Calls a 1-argument function `Func` setting the parent unwind frame information to the given SP and PC __attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline1Args(void* Arg0, void* Func, uint64_t Sp, uint64_t Pc) { asm(".seh_proc SEHFrameTrampoline1Args;" "stp x2, x3, [sp, #-0x10]!;" ".seh_pushframe;" "stp x29, x30, [sp, #-0x10]!;" ".seh_save_fplr_x 16;" ".seh_endprologue;" "blr x1;" "ldp x29, x30, [sp], 0x20;" "ret;" ".seh_endproc;"); } // Calls a 2-argument function `Func` setting the parent unwind frame information to the given SP and PC __attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline2Args(void* Arg0, void* Arg1, void* Func, uint64_t Sp, uint64_t Pc) { asm(".seh_proc SEHFrameTrampoline2Args;" "stp x3, x4, [sp, #-0x10]!;" ".seh_pushframe;" "stp x29, x30, [sp, #-0x10]!;" ".seh_save_fplr_x 16;" ".seh_endprologue;" "blr x2;" "ldp x29, x30, [sp], 0x20;" "ret;" ".seh_endproc;"); } class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators { public: WowSyscallHandler() = default; static void HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame) { const uint64_t ReturnRIP = *(uint32_t*)(Frame->State.gregs[FEXCore::X86State::REG_RSP]); // Return address from the stack uint64_t ReturnRSP = Frame->State.gregs[FEXCore::X86State::REG_RSP] + 4; // Stack pointer after popping return address uint64_t ReturnRAX = 0; if (Frame->State.rip == (uint64_t)BridgeInstrs::UnixCall) { struct StackLayout { unixlib_handle_t Handle; UINT32 ID; ULONG32 Args; }* StackArgs = reinterpret_cast(ReturnRSP); Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP + sizeof(StackLayout); Frame->State.rip = ReturnRIP; const auto TLS = GetTLS(); Context::UnlockJITContext(TLS); ReturnRAX = static_cast(WineUnixCall(StackArgs->Handle, StackArgs->ID, ULongToPtr(StackArgs->Args))); Context::LockJITContext(TLS); Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX; } else if (Frame->State.rip == (uint64_t)BridgeInstrs::Syscall) { const uint64_t EntryRAX = Frame->State.gregs[FEXCore::X86State::REG_RAX]; Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP; Frame->State.rip = ReturnRIP; const auto TLS = GetTLS(); Context::UnlockJITContext(TLS); Wow64ProcessPendingCrossProcessItems(); ReturnRAX = static_cast(Wow64SystemServiceEx(static_cast(EntryRAX), reinterpret_cast(ReturnRSP + 4))); Context::LockJITContext(TLS); Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX; } } void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override { const auto TLS = GetTLS(); // Stash the the context pointer on the stack, as Simulate can be called from this syscall handler which would overwrite it CONTEXT* EntryContext = TLS.EntryContext(); // Call the syscall handler with unwind information pointing to Simulate as its caller uint64_t Ret = SEHFrameTrampoline1Args(reinterpret_cast(Frame), reinterpret_cast(&HandleSyscallImpl), EntryContext->Sp, EntryContext->Pc); TLS.EntryContext() = EntryContext; } std::optional LookupExecutableFileSection(FEXCore::Core::InternalThreadState*, uint64_t Address) override { return ImageTracker->LookupExecutableFileSection(Address); } void MarkGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override { InvalidationTracker->ReprotectRWXIntervals(Start, Length); } void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Start, uint64_t Length) override { InvalidationTracker->InvalidateAlignedInterval(Start, Length, false); } void MarkOvercommitRange(uint64_t Start, uint64_t Length) override { OvercommitTracker->MarkRange(Start, Length); } void UnmarkOvercommitRange(uint64_t Start, uint64_t Length) override { OvercommitTracker->UnmarkRange(Start, Length); } FEXCore::HLE::ExecutableRangeInfo QueryGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) override { return InvalidationTracker->QueryExecutableRange(Address); } void PreCompile() override { Wow64ProcessPendingCrossProcessItems(); } }; void BTCpuProcessInit() { FEX::Windows::InitCRTProcess(); FEX::Windows::SetupThreadHandlers(); const auto ExecutablePath = FEX::Windows::GetExecutableFilePath(); const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath); FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation()); FEXCore::Config::ReloadMetaLayer(); FEX::Windows::Logging::Init(); FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, "0"); FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, "0"); FEXCore::Config::Set(FEXCore::Config::CONFIG_APP_FILENAME, ExecutablePath); FEXCore::Config::Set(FEXCore::Config::CONFIG_APP_CONFIG_NAME, ExecutableName); FEXCore::Profiler::Init("", ""); SignalDelegator = fextl::make_unique(); SyscallHandler = fextl::make_unique(); const auto NtDll = GetModuleHandleW(L"ntdll.dll"); const bool IsWine = !!GetProcAddress(NtDll, "wine_get_version"); OvercommitTracker.emplace(IsWine); FEX::Windows::Allocator::SetupHooks(NtDll); FEX::Windows::UnixLib::Init(NtDll); if (FEX::Windows::UnixLib::Available()) { FEXCore::DiskCache::SetFileMapper(FEX::Windows::UnixLib::MapFile); } { auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine, FEXCore::HostFeatures::HostTypeEnum::Wow64); CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures); } CTX->SetSignalDelegator(SignalDelegator.get()); CTX->SetSyscallHandler(SyscallHandler.get()); CTX->InitCore(); Context::HandlerConfig.emplace(*CTX); InvalidationTracker.emplace(*CTX, Threads); ImageTracker.emplace(*CTX, false); auto MainModule = reinterpret_cast<__TEB*>(NtCurrentTeb())->Peb->ImageBaseAddress; HandleImageMap(reinterpret_cast(MainModule), true); auto NtDllX86 = reinterpret_cast(GetProcAddress(NtDll, "LdrSystemDllInitBlock"))->ntdll_handle; HandleImageMap(NtDllX86); CPUFeatures.emplace(*CTX); // Allocate the syscall/unixcall trampolines in the lower 2GB of the address space SIZE_T Size = 4; void* Addr = nullptr; NtAllocateVirtualMemory(NtCurrentProcess(), &Addr, (1U << 31) - 1, &Size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Addr), Size, PAGE_EXECUTE); *reinterpret_cast(Addr) = 0x2ecd2ecd; BridgeInstrs::Syscall = Addr; BridgeInstrs::UnixCall = reinterpret_cast(reinterpret_cast(Addr) + 2); const auto Sym = GetProcAddress(NtDll, "__wine_unix_call_dispatcher"); if (Sym) { WineUnixCall = *reinterpret_cast(Sym); } FEX::Windows::SetupEnvironmentVariableValues(NtDll); // wow64.dll will only initialise the cross-process queue if this is set GetTLS().Wow64Info().CpuFlags = WOW64_CPUFLAGS_SOFTWARE; FEX_CONFIG_OPT(ProfileStats, PROFILESTATS); FEX_CONFIG_OPT(StartupSleep, STARTUPSLEEP); FEX_CONFIG_OPT(StartupSleepProcName, STARTUPSLEEPPROCNAME); if (IsWine && ProfileStats()) { StatAllocHandler = fextl::make_unique(FEXCore::SHMStats::AppType::WIN_WOW64); } if (StartupSleep() && (StartupSleepProcName().empty() || ExecutableName == StartupSleepProcName())) { LogMan::Msg::IFmt("[{}][{}] Sleeping for {} seconds", GetCurrentProcessId(), ExecutableName, StartupSleep()); std::this_thread::sleep_for(std::chrono::seconds(StartupSleep())); } } void BTCpuProcessTerm(HANDLE Handle, BOOL After, ULONG Status) {} void BTCpuThreadInit() { static constexpr size_t DefaultWow64CS {4}; std::scoped_lock Lock(ThreadCreationMutex); FEX::Windows::InitCRTThread(); auto* Thread = CTX->CreateThread(); // Default segment setup. auto Frame = Thread->CurrentFrame; auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32](); // Setup initial code-segment GDT auto& GDT = NewSegments[DefaultWow64CS]; FEXCore::Core::CPUState::SetGDTBase(&GDT, 0); FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU); GDT.L = 0; // L = Long Mode = 32-bit GDT.D = 1; // D = Default Operand Size = 32-bit Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT] = &NewSegments[0]; // TODO: LDTs are currently unsupported, mirror them to GDT. Frame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_LDT] = &NewSegments[0]; Frame->State.cs_idx = DefaultWow64CS << 3; Frame->State.cs_cached = FEXCore::Core::CPUState::CalculateGDTBase(GDT); FEX::Windows::CallRetStack::InitializeThread(Thread); const auto TLS = GetTLS(); TLS.ThreadState() = Thread; TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed); Thread->FrontendPtr = new FrontendThreadData(); auto ThreadTID = GetCurrentThreadId(); Threads.emplace(ThreadTID, Thread); if (StatAllocHandler) { Thread->ThreadStats = StatAllocHandler->AllocateSlot(ThreadTID); } } void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_TERMINATE)) { return; } auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME); THREAD_BASIC_INFORMATION Info; if (auto Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return; } const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); bool Self = ThreadTID == GetCurrentThreadId(); if (!Self) { // If we are suspending a thread that isn't ourselves, try to suspend it first so we know internal JIT locks aren't being held. RtlWow64SuspendThread(*ThreadDup, NULL); } auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return; } { std::scoped_lock Lock(ThreadCreationMutex); auto it = Threads.find(ThreadTID); if (it == Threads.end()) { // Thread already terminated return; } Threads.erase(it); if (StatAllocHandler) { StatAllocHandler->DeallocateSlot(TLS.ThreadState()->ThreadStats); } } auto ThreadState = TLS.ThreadState(); delete GetFrontendThreadData(ThreadState); // GDT and LDT are mirrored, only free one. delete[] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; FEX::Windows::CallRetStack::DestroyThread(ThreadState); CTX->DestroyThread(ThreadState); if (Self) { FEX::Windows::DeinitCRTThread(); } } void* BTCpuGetBopCode() { return BridgeInstrs::Syscall; } void* __wine_get_unix_opcode() { return BridgeInstrs::UnixCall; } NTSTATUS BTCpuGetContext(HANDLE Thread, HANDLE Process, void* Unknown, WOW64_CONTEXT* Context) { if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_GET_CONTEXT)) { return STATUS_ACCESS_DENIED; } auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } Context::ScopedJITContextLock Lk {TLS}; if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } return RtlWow64GetThreadContext(*ThreadDup, Context); } NTSTATUS BTCpuSetContext(HANDLE Thread, HANDLE Process, void* Unknown, WOW64_CONTEXT* Context) { if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_SET_CONTEXT)) { return STATUS_ACCESS_DENIED; } auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } // Back-up the input context incase we've been passed the CPU area (the flush below would wipe it out otherwise) WOW64_CONTEXT TmpContext = *Context; Context::ScopedJITContextLock Lk {TLS}; if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } // Merge the input context into the CPU area then pass the full context into the JIT if (Err = RtlWow64SetThreadContext(*ThreadDup, &TmpContext); Err) { return Err; } TmpContext.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS; if (Err = RtlWow64GetThreadContext(*ThreadDup, &TmpContext); Err) { return Err; } if (Thread == GetCurrentThread() && TLS.CachedCallRetSp()) { TLS.ThreadState()->CurrentFrame->State.callret_sp = TLS.CachedCallRetSp(); } Context::LoadStateFromWowContext(TLS.ThreadState(), GetWowTEB(TLS.TEB), &TmpContext); return STATUS_SUCCESS; } // .seh_pushframe doesn't restore the frame pointer, so if when unwinding from RtlCaptureContext an operation is used // that sets SP from FP, the unwound SP value will be incorrect. Wrap RtlCaptureContext so the correct FP is immediately // restored from the stack to prevent this. __attribute__((naked)) void BTCpuSimulate() { asm(".seh_proc BTCpuSimulate;" "sub sp, sp, #0x390;" ".seh_stackalloc 0x390;" "stp x29, x30, [sp, #-0x10]!;" ".seh_save_fplr_x 16;" ".seh_endprologue;" "add x0, sp, #0x10;" "bl RtlCaptureContext;" "add x0, sp, #0x10;" "bl BTCpuSimulateImpl;" "ldp x29, x30, [sp], 0x10;" "add sp, sp, #0x390;" "ret;" ".seh_endproc;"); } extern "C" void BTCpuSimulateImpl(CONTEXT* entry_context) { const auto TLS = GetTLS(); TLS.EntryContext() = entry_context; TLS.CachedCallRetSp() = TLS.ThreadState()->CurrentFrame->State.callret_sp; Context::ScopedJITContextLock Lk {TLS}; CTX->ExecuteThread(TLS.ThreadState()); } NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_SUSPEND_RESUME)) { return STATUS_ACCESS_DENIED; } auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); THREAD_BASIC_INFORMATION Info; if (NTSTATUS Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); if (ThreadTID == GetCurrentThreadId()) { LogMan::Msg::DFmt("Suspending self"); // Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using // SetThreadContext (which doesn't use the BTCpu API) if (!(GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { if (NTSTATUS Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } return NtSuspendThread(*ThreadDup, Count); } LogMan::Msg::DFmt("Suspending thread: {:X}", ThreadTID); auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } std::scoped_lock Lock(ThreadCreationMutex); // If the thread hasn't yet been initialized, suspend it without special handling as it wont yet have entered the JIT if (!Threads.contains(ThreadTID)) { LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID); return NtSuspendThread(*ThreadDup, Count); } // If CONTROL_IN_JIT is unset at this point, then it can never be set (and thus the JIT cannot be reentered) as // CONTROL_PAUSED has been set, as such, while this may redundantly request interrupts in rare cases it will never // miss them if (TLS.ControlWord().fetch_or(ControlBits::PAUSED, std::memory_order::relaxed) & ControlBits::IN_JIT) { LogMan::Msg::DFmt("Thread {:X} is in JIT, polling for interrupt", ThreadTID); ULONG TmpProt; void* TmpAddress = &TLS.ThreadState()->InterruptFaultPage; SIZE_T TmpSize = FEXCore::Utils::FEX_PAGE_SIZE; NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_READONLY, &TmpProt); } // Spin until the JIT is interrupted FEXCore::Utils::SpinWaitLock::WaitBitMaskPred(TLS.ControlWordAddress(), ControlBits::IN_JIT, 0U, std::equal_to<>()); // The JIT has now been interrupted and the context stored in the thread's CPU area is up-to-date if (Err = NtSuspendThread(*ThreadDup, Count); Err) { TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed); return Err; } CONTEXT TmpContext { .ContextFlags = CONTEXT_INTEGER, }; // NtSuspendThread may return before the thread is actually suspended, so a sync operation like NtGetContextThread // needs to be called to ensure it is before we unset CONTROL_PAUSED std::ignore = NtGetContextThread(*ThreadDup, &TmpContext); // Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using // SetThreadContext (which doesn't use the BTCpu API) if (!(TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID); // Now the thread is suspended on the host, unset CONTROL_PAUSED so that NtResumeThread will // continue execution in the JIT TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed); return Err; } // Returns true if exception dispatch should be halted and the execution context restored to Ptrs->Context bool BTCpuResetToConsistentStateImpl(EXCEPTION_POINTERS* Ptrs) { auto* Context = Ptrs->ContextRecord; auto* Exception = Ptrs->ExceptionRecord; auto TLS = GetTLS(); auto Thread = TLS.ThreadState(); FEXCORE_PROFILE_ACCUMULATION(Thread, AccumulatedSignalTime); if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { const auto FaultAddress = static_cast(Exception->ExceptionInformation[1]); if (FEX::Windows::CallRetStack::HandleAccessViolation(Thread, FaultAddress, Context->X25)) { return true; } if (OvercommitTracker && OvercommitTracker->HandleAccessViolation(FaultAddress)) { return true; } if (Context::HandleSuspendInterrupt(TLS, Context, FaultAddress)) { LogMan::Msg::DFmt("Resumed from suspend"); return true; } if (FEX::Windows::JITGuardPage::HandleJITGuardPage(Thread, reinterpret_cast(FaultAddress), Context->X, reinterpret_cast<__uint128_t*>(Context->V), &Context->Pc)) { return true; } if (Thread) { std::scoped_lock Lock(ThreadCreationMutex); FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedSMCCount, 1); if (InvalidationTracker->HandleRWXAccessViolation(Thread, Context->Pc, FaultAddress)) { if (CTX->IsAddressInCodeBuffer(Thread, Context->Pc) && !CTX->IsCurrentBlockSingleInst(Thread) && CTX->IsAddressInCurrentBlock(Thread, FaultAddress & FEXCore::Utils::FEX_PAGE_MASK, FEXCore::Utils::FEX_PAGE_SIZE)) { Context::ReconstructThreadState(TLS, Context); LogMan::Msg::DFmt("Handled inline self-modifying code: pc: {:X} rip: {:X} fault: {:X}", Context->Pc, Thread->CurrentFrame->State.rip, FaultAddress); // Adjust context to return to the dispatcher, reloading SRA from thread state const auto& Config = SignalDelegator->GetConfig(); Context->Pc = Config.AbsoluteLoopTopAddressFillSRA; Context->X1 = 1; // Set ENTRY_FILL_SRA_SINGLE_INST_REG to force a single step } else { LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", Context->Pc, FaultAddress); } return true; } } } if (!Thread || !IsAddressInJit(Context->Pc)) { return false; } FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedSIGBUSCount, 1); if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Context::HandleUnalignedAccess(TLS, Context)) { LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc); return true; } LogMan::Msg::DFmt("Reconstructing context"); WOW64_CONTEXT WowContext = Context::ReconstructWowContext(TLS, Context); LogMan::Msg::DFmt("pc: {:X} eip: {:X}", Context->Pc, WowContext.Eip); auto& Fault = Thread->CurrentFrame->SynchronousFaultData; *Exception = FEX::Windows::HandleGuestException(Fault, *Exception, WowContext.Eip, WowContext.Eax, WowContext.Ecx); if (Exception->ExceptionCode == EXCEPTION_SINGLE_STEP) { WowContext.EFlags &= ~(1 << FEXCore::X86State::RFLAG_TF_RAW_LOC); } // wow64.dll will handle adjusting PC in the dispatched context after a breakpoint BTCpuSetContext(GetCurrentThread(), GetCurrentProcess(), nullptr, &WowContext); Context::UnlockJITContext(TLS); // Replace the host context with one captured before JIT entry so host code can unwind memcpy(Context, TLS.EntryContext(), sizeof(*Context)); return false; } NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS* Ptrs) { if (BTCpuResetToConsistentStateImpl(Ptrs)) { NtContinue(Ptrs->ContextRecord, FALSE); } return STATUS_SUCCESS; } void BTCpuFlushInstructionCache2(const void* Address, SIZE_T Size) { std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); } void BTCpuFlushInstructionCacheHeavy(const void* Address, SIZE_T Size) { std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); } void BTCpuNotifyMemoryDirty(void* Address, SIZE_T Size) { std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), false); } void BTCpuNotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot, BOOL After, ULONG Status) { if (!After) { ThreadCreationMutex.lock(); } else { // MEM_RESET(_UNDO) ignores the passed permissions if (!Status && !(Type & (MEM_RESET | MEM_RESET_UNDO))) { InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), Prot); } ThreadCreationMutex.unlock(); } } void BTCpuNotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, ULONG Status) { if (!After) { ThreadCreationMutex.lock(); } else { if (!Status) { InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast(Address), static_cast(Size), NewProt); } ThreadCreationMutex.unlock(); } } void BTCpuNotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType, BOOL After, ULONG Status) { if (!After) { ThreadCreationMutex.lock(); } else { if (!Status) { InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast(Address), static_cast(Size), true); } ThreadCreationMutex.unlock(); } } NTSTATUS BTCpuNotifyMapViewOfSection(void* Unk1, void* Address, void* Unk2, SIZE_T Size, ULONG AllocType, ULONG Prot) { std::scoped_lock Lock(ThreadCreationMutex); HandleImageMap(reinterpret_cast(Address)); return STATUS_SUCCESS; } void BTCpuNotifyUnmapViewOfSection(void* Address, BOOL After, ULONG Status) { if (!After) { ThreadCreationMutex.lock(); auto [Start, Size] = InvalidationTracker->InvalidateContainingSection(reinterpret_cast(Address), true); if (Size) { HandleImageUnmap(Start, Size); } } else { ThreadCreationMutex.unlock(); } } void BTCpuNotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) { auto& InLockedRWXRead = GetFrontendThreadData(GetTLS().ThreadState())->InLockedRWXRead; if (!After) { ThreadCreationMutex.lock(); CTX->GetCodeInvalidationMutex().lock(); if (InvalidationTracker->BeginUntrackedWriteLocked(reinterpret_cast(Address), static_cast(Size))) { InLockedRWXRead = true; } else { CTX->GetCodeInvalidationMutex().unlock(); ThreadCreationMutex.unlock(); } } else { if (InLockedRWXRead) { InLockedRWXRead = false; CTX->GetCodeInvalidationMutex().unlock(); ThreadCreationMutex.unlock(); } } } void BTCpuNotifyProcessExecuteFlagsChange(ULONG Flags) { std::scoped_lock Lock(ThreadCreationMutex); InvalidationTracker->HandleProcessExecuteFlagsChange(Flags); } BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) { return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE; } void BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) { CPUFeatures->UpdateInformation(Info); }