Files
FEX-Emu--FEX/Source/Windows/WOW64/Module.cpp
T
Ryan Houdek d3cfdcb431 Win32: Enable support for virtual naming and THP control
Allows WTF to work (mostly) with Wine by letting us VirtualName things,
and also allows madvise control of THP, which significantly cuts back
memory usage.

This works around the problem of Wine not giving us control of this by
using raw syscalls when wine is detected.

Based on top of #5362 so the THP disable controls are in.
2026-04-01 10:50:40 -07:00

1049 lines
39 KiB
C++

// 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 <FEXCore/fextl/fmt.h>
#include <FEXCore/Core/X86Enums.h>
#include <FEXCore/Core/SignalDelegator.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/HLE/SyscallHandler.h>
#include <FEXCore/Config/Config.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/Threads.h>
#include <FEXCore/Utils/Profiler.h>
#include <FEXCore/Utils/SHMStats.h>
#include <FEXCore/Utils/EnumOperators.h>
#include <FEXCore/Utils/EnumUtils.h>
#include <FEXCore/Utils/FPState.h>
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
#include <FEXCore/Utils/TypeDefines.h>
#include <FEXCore/Utils/SignalScopeGuards.h>
#include "Windows/Common/Allocator.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/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 <cstdint>
#include <type_traits>
#include <atomic>
#include <mutex>
#include <utility>
#include <unordered_map>
#include <ntstatus.h>
#include <windef.h>
#include <winternl.h>
#include <wine/debug.h>
#include <wine/unixlib.h>
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<WOW64INFO*>(TEB->TlsSlots[WOW64_TLS_WOW64INFO]);
}
std::atomic<uint32_t>& ControlWord() const {
// TODO: Change this when libc++ gains std::atomic_ref support
return reinterpret_cast<std::atomic<uint32_t>&>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::CONTROL_WORD)]);
}
CONTEXT*& EntryContext() const {
return reinterpret_cast<CONTEXT*&>(TEB->TlsSlots[FEXCore::ToUnderlying(Slot::ENTRY_CONTEXT)]);
}
FEXCore::Core::InternalThreadState*& ThreadState() const {
return reinterpret_cast<FEXCore::Core::InternalThreadState*&>(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<uint64_t&>(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<FEXCore::Context::Context> CTX;
fextl::unique_ptr<FEX::DummyHandlers::DummySignalDelegator> SignalDelegator;
fextl::unique_ptr<WowSyscallHandler> SyscallHandler;
fextl::unique_ptr<FEX::Windows::StatAlloc> StatAllocHandler;
std::optional<FEX::Windows::InvalidationTracker> InvalidationTracker;
std::optional<FEX::Windows::CPUFeatures> CPUFeatures;
std::optional<FEX::Windows::OvercommitTracker> OvercommitTracker;
std::optional<FEX::Windows::ImageTracker> ImageTracker;
std::mutex ThreadCreationMutex;
// Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing
std::unordered_map<DWORD, FEXCore::Core::InternalThreadState*> Threads;
decltype(__wine_unix_call_dispatcher) WineUnixCall;
std::pair<NTSTATUS, TLS> 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<FrontendThreadData*>(Thread->FrontendPtr);
}
uint64_t GetWowTEB(void* TEB) {
static constexpr size_t WowTEBOffsetMemberOffset {0x180c};
return static_cast<uint64_t>(
*reinterpret_cast<LONG*>(reinterpret_cast<uintptr_t>(TEB) + WowTEBOffsetMemberOffset) + reinterpret_cast<uint64_t>(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<XSAVE_FORMAT*>(Context->ExtendedRegisters);
CTX->SetXMMRegistersFromState(Thread, reinterpret_cast<const __uint128_t*>(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<XSAVE_FORMAT*>(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<XSAVE_FORMAT*>(WowContext.ExtendedRegisters);
XSave->ControlWord = 0x27f;
XSave->MxCsr = 0x1f80;
Context::StoreWowContextFromState(TLS.ThreadState(), &WowContext);
return WowContext;
}
static std::optional<FEX::Windows::TSOHandlerConfig> 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<void**>(&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<uint64_t>(&TLS.ThreadState()->InterruptFaultPage)) {
return false;
}
void* TmpAddress = reinterpret_cast<void*>(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 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() {
OSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC;
}
static uint64_t HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) {
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<StackLayout*>(ReturnRSP);
ReturnRSP += sizeof(StackLayout);
const auto TLS = GetTLS();
Context::UnlockJITContext(TLS);
ReturnRAX = static_cast<uint64_t>(WineUnixCall(StackArgs->Handle, StackArgs->ID, ULongToPtr(StackArgs->Args)));
Context::LockJITContext(TLS);
} else if (Frame->State.rip == (uint64_t)BridgeInstrs::Syscall) {
const uint64_t EntryRAX = Frame->State.gregs[FEXCore::X86State::REG_RAX];
const auto TLS = GetTLS();
Context::UnlockJITContext(TLS);
Wow64ProcessPendingCrossProcessItems();
ReturnRAX = static_cast<uint64_t>(Wow64SystemServiceEx(static_cast<UINT>(EntryRAX), reinterpret_cast<UINT*>(ReturnRSP + 4)));
Context::LockJITContext(TLS);
}
// If a new context has been set, use it directly and don't return to the syscall caller
if (Frame->State.rip == (uint64_t)BridgeInstrs::Syscall || Frame->State.rip == (uint64_t)BridgeInstrs::UnixCall) {
Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX;
Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP;
Frame->State.rip = ReturnRIP;
}
// NORETURNEDRESULT causes this result to be ignored since we restore all registers back from memory after a syscall anyway
return 0;
}
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) 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 = SEHFrameTrampoline2Args(reinterpret_cast<void*>(Frame), reinterpret_cast<void*>(Args),
reinterpret_cast<void*>(&HandleSyscallImpl), EntryContext->Sp, EntryContext->Pc);
TLS.EntryContext() = EntryContext;
return Ret;
}
std::optional<FEXCore::ExecutableFileSectionInfo> 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();
const auto ExecutableName = FEX::Windows::BaseName(FEX::Windows::GetExecutableFilePath());
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::Profiler::Init("", "");
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
SyscallHandler = fextl::make_unique<WowSyscallHandler>();
const auto NtDll = GetModuleHandle("ntdll.dll");
const bool IsWine = !!GetProcAddress(NtDll, "wine_get_version");
OvercommitTracker.emplace(IsWine);
FEX::Windows::Allocator::SetupHooks(NtDll);
{
auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine);
// AVX is unsupported for WOW64
HostFeatures.SupportsAVX = false;
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<uint64_t>(MainModule), true);
auto NtDllX86 = reinterpret_cast<SYSTEM_DLL_INIT_BLOCK*>(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<uint64_t>(Addr), Size, PAGE_EXECUTE);
*reinterpret_cast<uint32_t*>(Addr) = 0x2ecd2ecd;
BridgeInstrs::Syscall = Addr;
BridgeInstrs::UnixCall = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(Addr) + 2);
const auto Sym = GetProcAddress(NtDll, "__wine_unix_call_dispatcher");
if (Sym) {
WineUnixCall = *reinterpret_cast<decltype(WineUnixCall)*>(Sym);
}
// wow64.dll will only initialise the cross-process queue if this is set
GetTLS().Wow64Info().CpuFlags = WOW64_CPUFLAGS_SOFTWARE;
FEX_CONFIG_OPT(TSOEnabled, TSOENABLED);
if (TSOEnabled()) {
BOOL Enable = TRUE;
NTSTATUS Status = NtSetInformationProcess(NtCurrentProcess(), ProcessFexHardwareTso, &Enable, sizeof(Enable));
if (Status == STATUS_SUCCESS) {
CTX->SetHardwareTSOSupport(true);
}
}
FEX_CONFIG_OPT(ProfileStats, PROFILESTATS);
FEX_CONFIG_OPT(StartupSleep, STARTUPSLEEP);
FEX_CONFIG_OPT(StartupSleepProcName, STARTUPSLEEPPROCNAME);
if (IsWine && ProfileStats()) {
StatAllocHandler = fextl::make_unique<FEX::Windows::StatAlloc>(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(0, 0);
// 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<uint64_t>(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<uint64_t>(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
while (TLS.ControlWord().load() & ControlBits::IN_JIT)
;
// 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<uint64_t>(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<void*>(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);
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<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpuFlushInstructionCacheHeavy(const void* Address, SIZE_T Size) {
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpuNotifyMemoryDirty(void* Address, SIZE_T Size) {
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address), static_cast<uint64_t>(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<uint64_t>(Address));
return STATUS_SUCCESS;
}
void BTCpuNotifyUnmapViewOfSection(void* Address, BOOL After, ULONG Status) {
if (!After) {
ThreadCreationMutex.lock();
auto [Start, Size] = InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(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<uint64_t>(Address), static_cast<uint64_t>(Size))) {
InLockedRWXRead = true;
} else {
CTX->GetCodeInvalidationMutex().unlock();
ThreadCreationMutex.unlock();
}
} else {
if (InLockedRWXRead) {
InLockedRWXRead = false;
CTX->GetCodeInvalidationMutex().unlock();
ThreadCreationMutex.unlock();
}
}
}
BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) {
return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE;
}
void BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) {
CPUFeatures->UpdateInformation(Info);
}