mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-07 06:00:16 +02:00
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.
841 lines
31 KiB
C++
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;
|
|
}
|