Files
FEX-Emu--FEX/Source/Windows/WOW64/Module.cpp
T
Ryan Houdek 11993daec4 FEXCore: Hides eflags reconstruction information in the core
The frontend shouldn't need to know any information about how to
reconstruct eflags. Just give us the information we need and it'll work
out.
There are still some inherit limitations of this and some edge cases
that might give invalid data, but it is roughly as close as it was
before.

Just provide if the PC was in the JIT, the host GPRs, and the PState object from the signal
information and FEXCore does the rest.

We don't need to change the signature for `SetFlagsFromCompactedEFLAGS`
because during reloading of register state automatically does this for
us.
2023-11-17 20:38:42 -04:00

841 lines
31 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/EnumOperators.h>
#include <FEXCore/Utils/EnumUtils.h>
#include <FEXCore/Utils/FPState.h>
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
#include <FEXHeaderUtils/TypeDefines.h>
#include "Common/Config.h"
#include "DummyHandlers.h"
#include "BTInterface.h"
#include "IntervalList.h"
#include <cstdint>
#include <type_traits>
#include <atomic>
#include <mutex>
#include <utility>
#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};
};
struct TLS {
enum class Slot : size_t {
ENTRY_CONTEXT = WOW64_TLS_MAX_NUMBER,
CONTROL_WORD = WOW64_TLS_MAX_NUMBER - 1,
THREAD_STATE = WOW64_TLS_MAX_NUMBER - 2,
};
_TEB *TEB;
explicit TLS(_TEB *TEB) : TEB(TEB) {}
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)]);
}
};
class WowSyscallHandler;
namespace {
namespace BridgeInstrs {
// These directly jumped to by the guest to make system calls
uint16_t Syscall{0x2ecd};
uint16_t UnixCall{0x2ecd};
}
fextl::unique_ptr<FEXCore::Context::Context> CTX;
fextl::unique_ptr<FEX::DummyHandlers::DummySignalDelegator> SignalDelegator;
fextl::unique_ptr<WowSyscallHandler> SyscallHandler;
SYSTEM_CPU_INFORMATION CpuInfo{};
std::mutex ThreadSuspendLock;
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()};
}
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 IsAddressInJit(uint64_t Address) {
return GetTLS().ThreadState()->CPUBackend->IsAddressInCodeBuffer(Address);
}
}
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
State.gdt[(Context->SegFs & 0xffff) >> 3].base = WowTEB;
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);
memcpy(State.xmm.sse.data, XSave->XmmRegisters, sizeof(State.xmm.sse.data));
memcpy(State.mm, XSave->FloatRegisters, sizeof(State.mm));
State.FCW = XSave->ControlWord;
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);
memcpy(XSave->XmmRegisters, State.xmm.sse.data, sizeof(State.xmm.sse.data));
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);
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(CONTEXT *Context) {
const auto &Config = SignalDelegator->GetConfig();
auto *Thread = GetTLS().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));
}
}
WOW64_CONTEXT ReconstructWowContext(CONTEXT *Context) {
ReconstructThreadState(Context);
WOW64_CONTEXT WowContext{
.ContextFlags = WOW64_CONTEXT_ALL,
};
auto *XSave = reinterpret_cast<XSAVE_FORMAT *>(WowContext.ExtendedRegisters);
XSave->ControlWord = 0x27f;
XSave->MxCsr = 0x1f80;
Context::StoreWowContextFromState(GetTLS().ThreadState(), &WowContext);
return WowContext;
}
bool HandleUnalignedAccess(CONTEXT *Context) {
if (!GetTLS().ThreadState()->CPUBackend->IsAddressInCodeBuffer(Context->Pc)) {
return false;
}
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(ParanoidTSO(), Context->Pc, &Context->X0);
if (!Result.first) {
return false;
}
Context->Pc += Result.second;
return true;
}
void LockJITContext() {
uint32_t Expected = GetTLS().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 (!GetTLS().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(GetTLS().ThreadState(), GetWowTEB(NtCurrentTeb()), WowContext);
}
}
void UnlockJITContext() {
std::atomic_signal_fence(std::memory_order::seq_cst);
GetTLS().ControlWord().fetch_and(~ControlBits::IN_JIT, std::memory_order::relaxed);
}
bool HandleSuspendInterrupt(CONTEXT *Context, uint64_t FaultAddress) {
if (FaultAddress != reinterpret_cast<uint64_t>(&GetTLS().ThreadState()->InterruptFaultPage)) {
return false;
}
void *TmpAddress = reinterpret_cast<void *>(FaultAddress);
SIZE_T TmpSize = FHU::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(Context);
// Yield to the suspender
UnlockJITContext();
LockJITContext();
// Adjust context to return to the dispatcher, reloading SRA from thread state
const auto &Config = SignalDelegator->GetConfig();
Context->Pc = Config.AbsoluteLoopTopAddressFillSRA;
return true;
}
}
namespace Invalidation {
static IntervalList<uint64_t> RWXIntervals;
static std::mutex RWXIntervalsLock;
void HandleMemoryProtectionNotification(uint64_t Address, uint64_t Size, ULONG Prot) {
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;
if (Prot & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) {
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), AlignedBase, AlignedSize);
}
if (Prot & PAGE_EXECUTE_READWRITE) {
LogMan::Msg::DFmt("Add SMC interval: {:X} - {:X}", AlignedBase, AlignedBase + AlignedSize);
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Insert({AlignedBase, AlignedBase + AlignedSize});
} else {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
}
}
void InvalidateContainingSection(uint64_t Address, bool Free) {
MEMORY_BASIC_INFORMATION Info;
if (NtQueryVirtualMemory(NtCurrentProcess(), reinterpret_cast<void *>(Address), MemoryBasicInformation, &Info, sizeof(Info), nullptr))
return;
const auto SectionBase = reinterpret_cast<uint64_t>(Info.AllocationBase);
const auto SectionSize = reinterpret_cast<uint64_t>(Info.BaseAddress) + Info.RegionSize
- reinterpret_cast<uint64_t>(Info.AllocationBase);
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), SectionBase, SectionSize);
if (Free) {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({SectionBase, SectionBase + SectionSize});
}
}
void InvalidateAlignedInterval(uint64_t Address, uint64_t Size, bool Free) {
const auto AlignedBase = Address & FHU::FEX_PAGE_MASK;
const auto AlignedSize = (Address - AlignedBase + Size + FHU::FEX_PAGE_SIZE - 1) & FHU::FEX_PAGE_MASK;
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), AlignedBase, AlignedSize);
if (Free) {
std::scoped_lock Lock(RWXIntervalsLock);
RWXIntervals.Remove({AlignedBase, AlignedBase + AlignedSize});
}
}
void ReprotectRWXIntervals(uint64_t Address, uint64_t Size) {
const auto End = Address + Size;
std::scoped_lock Lock(RWXIntervalsLock);
do {
const auto Query = RWXIntervals.Query(Address);
if (Query.Enclosed) {
void *TmpAddress = reinterpret_cast<void *>(Address);
SIZE_T TmpSize = static_cast<SIZE_T>(std::min(End, Address + Query.Size) - Address);
ULONG TmpProt;
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READ, &TmpProt);
} else if (!Query.Size) {
// No more regions past `Address` in the interval list
break;
}
Address += Query.Size;
} while (Address < End);
}
bool HandleRWXAccessViolation(uint64_t FaultAddress) {
const bool NeedsInvalidate = [](uint64_t Address) {
std::unique_lock Lock(RWXIntervalsLock);
const bool Enclosed = RWXIntervals.Query(Address).Enclosed;
// Invalidate just the single faulting page
if (!Enclosed)
return false;
ULONG TmpProt;
void *TmpAddress = reinterpret_cast<void *>(Address);
SIZE_T TmpSize = 1;
NtProtectVirtualMemory(NtCurrentProcess(), &TmpAddress, &TmpSize, PAGE_EXECUTE_READWRITE, &TmpProt);
return true;
}(FaultAddress);
if (NeedsInvalidate) {
// RWXIntervalsLock cannot be held during invalidation
CTX->InvalidateGuestCodeRange(GetTLS().ThreadState(), FaultAddress & FHU::FEX_PAGE_MASK, FHU::FEX_PAGE_SIZE);
return true;
}
return false;
}
}
namespace Logging {
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
const auto Output = fextl::fmt::format("[{}][{:X}] {}\n", LogMan::DebugLevelStr(Level), GetCurrentThreadId(), Message);
__wine_dbg_output(Output.c_str());
}
void AssertHandler(char const *Message) {
const auto Output = fextl::fmt::format("[ASSERT] {}\n", Message);
__wine_dbg_output(Output.c_str());
}
void Init() {
LogMan::Throw::InstallHandler(AssertHandler);
LogMan::Msg::InstallHandler(MsgHandler);
}
}
class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators {
public:
WowSyscallHandler() {
OSABI = FEXCore::HLE::SyscallOSABI::OS_WIN32;
}
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame *Frame, FEXCore::HLE::SyscallArguments *Args) override {
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);
Context::UnlockJITContext();
ReturnRAX = static_cast<uint64_t>(__wine_unix_call(StackArgs->Handle, StackArgs->ID, ULongToPtr(StackArgs->Args)));
Context::LockJITContext();
} else if (Frame->State.rip == (uint64_t)&BridgeInstrs::Syscall) {
const uint64_t EntryRAX = Frame->State.gregs[FEXCore::X86State::REG_RAX];
Context::UnlockJITContext();
ReturnRAX = static_cast<uint64_t>(Wow64SystemServiceEx(static_cast<UINT>(EntryRAX),
reinterpret_cast<UINT *>(ReturnRSP + 4)));
Context::LockJITContext();
}
// 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;
}
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 {
Invalidation::ReprotectRWXIntervals(Start, Length);
}
};
void BTCpuProcessInit() {
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_IS_INTERPRETER, "0");
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, "0");
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "0");
// Not applicable to Windows
FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0");
FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_32BIT);
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
SyscallHandler = fextl::make_unique<WowSyscallHandler>();
CTX = FEXCore::Context::Context::CreateNewContext();
CTX->InitializeContext();
CTX->SetSignalDelegator(SignalDelegator.get());
CTX->SetSyscallHandler(SyscallHandler.get());
CTX->InitCore(0, 0);
CpuInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
// Baseline FEX feature-set
CpuInfo.ProcessorFeatureBits = CPU_FEATURE_VME | CPU_FEATURE_TSC | CPU_FEATURE_CMOV | CPU_FEATURE_PGE |
CPU_FEATURE_PSE | CPU_FEATURE_MTRR | CPU_FEATURE_CX8 | CPU_FEATURE_MMX |
CPU_FEATURE_X86 | CPU_FEATURE_PAT | CPU_FEATURE_FXSR | CPU_FEATURE_SEP |
CPU_FEATURE_SSE | CPU_FEATURE_3DNOW | CPU_FEATURE_SSE2 | CPU_FEATURE_SSE3 |
CPU_FEATURE_CX128 | CPU_FEATURE_NX | CPU_FEATURE_SSSE3 | CPU_FEATURE_SSE41 |
CPU_FEATURE_PAE | CPU_FEATURE_DAZ;
// Features that require specific host CPU support
const auto CPUIDResult01 = CTX->RunCPUIDFunction(0x01, 0);
if (CPUIDResult01.ecx & (1 << 20)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_SSE42;
}
if (CPUIDResult01.ecx & (1 << 27)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_XSAVE;
}
if (CPUIDResult01.ecx & (1 << 28)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX;
}
const auto CPUIDResult07 = CTX->RunCPUIDFunction(0x07, 0);
if (CPUIDResult07.ebx & (1 << 5)) {
CpuInfo.ProcessorFeatureBits |= CPU_FEATURE_AVX2;
}
const auto FamilyIdentifier = CPUIDResult01.eax;
CpuInfo.ProcessorLevel = ((FamilyIdentifier >> 8) & 0xf) + ((FamilyIdentifier >> 20) & 0xff); // Family
CpuInfo.ProcessorRevision = (FamilyIdentifier & 0xf0000) >> 4; // Extended Model
CpuInfo.ProcessorRevision |= (FamilyIdentifier & 0xf0) << 4; // Model
CpuInfo.ProcessorRevision |= FamilyIdentifier & 0xf; // Stepping
}
NTSTATUS BTCpuThreadInit() {
GetTLS().ThreadState() = CTX->CreateThread(nullptr, 0);
return STATUS_SUCCESS;
}
NTSTATUS BTCpuThreadTerm(HANDLE Thread) {
const auto [Err, TLS] = GetThreadTLS(Thread);
if (Err) {
return Err;
}
CTX->DestroyThread(TLS.ThreadState());
return STATUS_SUCCESS;
}
void *BTCpuGetBopCode() {
return &BridgeInstrs::Syscall;
}
void *__wine_get_unix_opcode() {
return &BridgeInstrs::UnixCall;
}
NTSTATUS BTCpuGetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context) {
auto [Err, TLS] = GetThreadTLS(Thread);
if (Err) {
return Err;
}
if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
if (Err = Context::FlushThreadStateContext(Thread); Err) {
return Err;
}
}
return RtlWow64GetThreadContext(Thread, Context);
}
NTSTATUS BTCpuSetContext(HANDLE Thread, HANDLE Process, void *Unknown, WOW64_CONTEXT *Context) {
auto [Err, TLS] = GetThreadTLS(Thread);
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;
if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
if (Err = Context::FlushThreadStateContext(Thread); Err) {
return Err;
}
}
// Merge the input context into the CPU area then pass the full context into the JIT
if (Err = RtlWow64SetThreadContext(Thread, &TmpContext); Err) {
return Err;
}
TmpContext.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS;
if (Err = RtlWow64GetThreadContext(Thread, &TmpContext); Err) {
return Err;
}
Context::LoadStateFromWowContext(TLS.ThreadState(), GetWowTEB(TLS.TEB), &TmpContext);
return STATUS_SUCCESS;
}
void BTCpuSimulate() {
CONTEXT entry_context;
RtlCaptureContext(&entry_context);
// APC handling calls BTCpuSimulate from syscalls and then use NtContinue to return to the previous context,
// to avoid the saved context being clobbered in this case only save the entry context highest in the stack
if (!GetTLS().EntryContext() || GetTLS().EntryContext()->Sp <= entry_context.Sp) {
GetTLS().EntryContext() = &entry_context;
}
Context::LockJITContext();
CTX->ExecuteThread(GetTLS().ThreadState());
Context::UnlockJITContext();
}
NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG *Count) {
THREAD_BASIC_INFORMATION Info;
if (NTSTATUS Err = NtQueryInformationThread(Thread, 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(Thread); Err) {
return Err;
}
}
return NtSuspendThread(Thread, Count);
}
LogMan::Msg::DFmt("Suspending thread: {:X}", ThreadTID);
auto [Err, TLS] = GetThreadTLS(Thread);
if (Err) {
return Err;
}
std::scoped_lock Lock(ThreadSuspendLock);
// 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 = FHU::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(Thread, 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(Thread, &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(Thread); 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;
}
NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS *Ptrs) {
auto *Context = Ptrs->ContextRecord;
const auto *Exception = Ptrs->ExceptionRecord;
if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Context::HandleUnalignedAccess(Context)) {
LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc);
NtContinue(Context, FALSE);
}
if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
const auto FaultAddress = static_cast<uint64_t>(Exception->ExceptionInformation[1]);
if (Invalidation::HandleRWXAccessViolation(FaultAddress)) {
LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", Context->Pc, FaultAddress);
NtContinue(Context, FALSE);
}
if (Context::HandleSuspendInterrupt(Context, FaultAddress)) {
LogMan::Msg::DFmt("Resumed from suspend");
NtContinue(Context, FALSE);
}
}
if (!IsAddressInJit(Context->Pc)) {
return STATUS_SUCCESS;
}
LogMan::Msg::DFmt("Reconstructing context");
WOW64_CONTEXT WowContext = Context::ReconstructWowContext(Context);
LogMan::Msg::DFmt("pc: {:X} eip: {:X}", Context->Pc, WowContext.Eip);
BTCpuSetContext(GetCurrentThread(), GetCurrentProcess(), nullptr, &WowContext);
Context::UnlockJITContext();
// Replace the host context with one captured before JIT entry so host code can unwind
memcpy(Context, GetTLS().EntryContext(), sizeof(*Context));
return STATUS_SUCCESS;
}
void BTCpuFlushInstructionCache2(const void *Address, SIZE_T Size) {
Invalidation::InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpuNotifyMemoryAlloc(void *Address, SIZE_T Size, ULONG Type, ULONG Prot) {
Invalidation::HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size),
Prot);
}
void BTCpuNotifyMemoryProtect(void *Address, SIZE_T Size, ULONG NewProt) {
Invalidation::HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size),
NewProt);
}
void BTCpuNotifyMemoryFree(void *Address, SIZE_T Size, ULONG FreeType) {
if (!Size) {
Invalidation::InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
} else if (FreeType & MEM_DECOMMIT) {
Invalidation::InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), true);
}
}
void BTCpuNotifyUnmapViewOfSection(void *Address, ULONG Flags) {
Invalidation::InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
}
BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) {
switch (Feature) {
case PF_FLOATING_POINT_PRECISION_ERRATA:
return FALSE;
case PF_FLOATING_POINT_EMULATED:
return FALSE;
case PF_COMPARE_EXCHANGE_DOUBLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX8);
case PF_MMX_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_MMX);
case PF_XMMI_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE);
case PF_3DNOW_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_3DNOW);
case PF_RDTSC_INSTRUCTION_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_TSC);
case PF_PAE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_PAE);
case PF_XMMI64_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE2);
case PF_SSE3_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE3);
case PF_SSSE3_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSSE3);
case PF_XSAVE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_XSAVE);
case PF_COMPARE_EXCHANGE128:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_CX128);
case PF_SSE_DAZ_MODE_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_DAZ);
case PF_NX_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_NX);
case PF_SECOND_LEVEL_ADDRESS_TRANSLATION:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_2NDLEV);
case PF_VIRT_FIRMWARE_ENABLED:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_VIRT);
case PF_RDWRFSGSBASE_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_RDFS);
case PF_FASTFAIL_AVAILABLE:
return TRUE;
case PF_SSE4_1_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE41);
case PF_SSE4_2_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_SSE42);
case PF_AVX_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX);
case PF_AVX2_INSTRUCTIONS_AVAILABLE:
return !!(CpuInfo.ProcessorFeatureBits & CPU_FEATURE_AVX2);
default:
LogMan::Msg::DFmt("Unknown CPU feature: {:X}", Feature);
return FALSE;
}
}
BOOLEAN BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION *Info) {
Info->ProcessorArchitecture = CpuInfo.ProcessorArchitecture;
Info->ProcessorLevel = CpuInfo.ProcessorLevel;
Info->ProcessorRevision = CpuInfo.ProcessorRevision;
Info->ProcessorFeatureBits = CpuInfo.ProcessorFeatureBits;
return TRUE;
}