mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 15:00:17 +02:00
The JIT was doing a bunch of additional work where it was saving and restoring registers and then juggling the arguments back in to a stack frame. All of this is nonsensical without the optimization where we could call syscalls inline without a stack frame. Instead remove this optimization entirely and behave like a "generic" syscall path always. The Linux syscall handler now pulls the arguments out of the CPU context directly and stores the result back in to RAX directly as well. This has knock-on effects where technically syscalls are going to be slightly faster because no stack frame setup for the arguments, but additionally we are going to be able to have syscalls be proper serialization points where we can interrupt the syscall and long-jump out without problems. Bumps the DiskCache version again because it causes codegen to change.
1068 lines
39 KiB
C++
1068 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/Core/DiskCacheFileMapper.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 "Windows/Common/EnvironmentVariablesHandling.h"
|
|
#include "Windows/Common/FEXUnixLib.h"
|
|
#include "Common/CallRetStack.h"
|
|
#include "Common/JITGuardPage.h"
|
|
#include "Common/Config.h"
|
|
#include "Common/Exception.h"
|
|
#include "Common/TSOHandlerConfig.h"
|
|
#include "Common/ImageTracker.h"
|
|
#include "Common/InvalidationTracker.h"
|
|
#include "Common/Threads.h"
|
|
#include "Common/OvercommitTracker.h"
|
|
#include "Common/CPUFeatures.h"
|
|
#include "Common/Logging.h"
|
|
#include "Common/Module.h"
|
|
#include "Common/CRT/CRT.h"
|
|
#include "Common/PortabilityInfo.h"
|
|
#include "Common/Handle.h"
|
|
#include "DummyHandlers.h"
|
|
#include "BTInterface.h"
|
|
#include "Windows/Common/SHMStats.h"
|
|
|
|
#include <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)]);
|
|
}
|
|
|
|
uint32_t* ControlWordAddress() const {
|
|
return reinterpret_cast<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 1-argument function `Func` setting the parent unwind frame information to the given SP and PC
|
|
__attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline1Args(void* Arg0, void* Func, uint64_t Sp, uint64_t Pc) {
|
|
asm(".seh_proc SEHFrameTrampoline1Args;"
|
|
"stp x2, x3, [sp, #-0x10]!;"
|
|
".seh_pushframe;"
|
|
"stp x29, x30, [sp, #-0x10]!;"
|
|
".seh_save_fplr_x 16;"
|
|
".seh_endprologue;"
|
|
"blr x1;"
|
|
"ldp x29, x30, [sp], 0x20;"
|
|
"ret;"
|
|
".seh_endproc;");
|
|
}
|
|
|
|
// Calls a 2-argument function `Func` setting the parent unwind frame information to the given SP and PC
|
|
__attribute__((naked)) extern "C" uint64_t SEHFrameTrampoline2Args(void* Arg0, void* Arg1, void* Func, uint64_t Sp, uint64_t Pc) {
|
|
asm(".seh_proc SEHFrameTrampoline2Args;"
|
|
"stp x3, x4, [sp, #-0x10]!;"
|
|
".seh_pushframe;"
|
|
"stp x29, x30, [sp, #-0x10]!;"
|
|
".seh_save_fplr_x 16;"
|
|
".seh_endprologue;"
|
|
"blr x2;"
|
|
"ldp x29, x30, [sp], 0x20;"
|
|
"ret;"
|
|
".seh_endproc;");
|
|
}
|
|
|
|
class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators {
|
|
public:
|
|
WowSyscallHandler() = default;
|
|
|
|
static void HandleSyscallImpl(FEXCore::Core::CpuStateFrame* Frame) {
|
|
const uint64_t ReturnRIP = *(uint32_t*)(Frame->State.gregs[FEXCore::X86State::REG_RSP]); // Return address from the stack
|
|
uint64_t ReturnRSP = Frame->State.gregs[FEXCore::X86State::REG_RSP] + 4; // Stack pointer after popping return address
|
|
uint64_t ReturnRAX = 0;
|
|
|
|
if (Frame->State.rip == (uint64_t)BridgeInstrs::UnixCall) {
|
|
struct StackLayout {
|
|
unixlib_handle_t Handle;
|
|
UINT32 ID;
|
|
ULONG32 Args;
|
|
}* StackArgs = reinterpret_cast<StackLayout*>(ReturnRSP);
|
|
|
|
Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP + sizeof(StackLayout);
|
|
Frame->State.rip = ReturnRIP;
|
|
|
|
const auto TLS = GetTLS();
|
|
Context::UnlockJITContext(TLS);
|
|
ReturnRAX = static_cast<uint64_t>(WineUnixCall(StackArgs->Handle, StackArgs->ID, ULongToPtr(StackArgs->Args)));
|
|
Context::LockJITContext(TLS);
|
|
Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX;
|
|
} else if (Frame->State.rip == (uint64_t)BridgeInstrs::Syscall) {
|
|
const uint64_t EntryRAX = Frame->State.gregs[FEXCore::X86State::REG_RAX];
|
|
|
|
Frame->State.gregs[FEXCore::X86State::REG_RSP] = ReturnRSP;
|
|
Frame->State.rip = ReturnRIP;
|
|
|
|
const auto TLS = GetTLS();
|
|
Context::UnlockJITContext(TLS);
|
|
Wow64ProcessPendingCrossProcessItems();
|
|
ReturnRAX = static_cast<uint64_t>(Wow64SystemServiceEx(static_cast<UINT>(EntryRAX), reinterpret_cast<UINT*>(ReturnRSP + 4)));
|
|
Context::LockJITContext(TLS);
|
|
Frame->State.gregs[FEXCore::X86State::REG_RAX] = ReturnRAX;
|
|
}
|
|
}
|
|
|
|
void HandleSyscall(FEXCore::Core::CpuStateFrame* Frame) override {
|
|
const auto TLS = GetTLS();
|
|
// Stash the the context pointer on the stack, as Simulate can be called from this syscall handler which would overwrite it
|
|
CONTEXT* EntryContext = TLS.EntryContext();
|
|
// Call the syscall handler with unwind information pointing to Simulate as its caller
|
|
uint64_t Ret =
|
|
SEHFrameTrampoline1Args(reinterpret_cast<void*>(Frame), reinterpret_cast<void*>(&HandleSyscallImpl), EntryContext->Sp, EntryContext->Pc);
|
|
TLS.EntryContext() = EntryContext;
|
|
}
|
|
|
|
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();
|
|
FEX::Windows::SetupThreadHandlers();
|
|
const auto ExecutablePath = FEX::Windows::GetExecutableFilePath();
|
|
const auto ExecutableName = FEX::Windows::BaseName(ExecutablePath);
|
|
FEX::Config::LoadConfig(fextl::string {ExecutableName}, _environ, FEX::ReadPortabilityInformation());
|
|
FEXCore::Config::ReloadMetaLayer();
|
|
FEX::Windows::Logging::Init();
|
|
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, "0");
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, "0");
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_APP_FILENAME, ExecutablePath);
|
|
FEXCore::Config::Set(FEXCore::Config::CONFIG_APP_CONFIG_NAME, ExecutableName);
|
|
|
|
FEXCore::Profiler::Init("", "");
|
|
|
|
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
|
|
SyscallHandler = fextl::make_unique<WowSyscallHandler>();
|
|
const auto NtDll = GetModuleHandleW(L"ntdll.dll");
|
|
const bool IsWine = !!GetProcAddress(NtDll, "wine_get_version");
|
|
OvercommitTracker.emplace(IsWine);
|
|
|
|
FEX::Windows::Allocator::SetupHooks(NtDll);
|
|
FEX::Windows::UnixLib::Init(NtDll);
|
|
if (FEX::Windows::UnixLib::Available()) {
|
|
FEXCore::DiskCache::SetFileMapper(FEX::Windows::UnixLib::MapFile);
|
|
}
|
|
|
|
{
|
|
auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine, FEXCore::HostFeatures::HostTypeEnum::Wow64);
|
|
CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures);
|
|
}
|
|
|
|
CTX->SetSignalDelegator(SignalDelegator.get());
|
|
CTX->SetSyscallHandler(SyscallHandler.get());
|
|
CTX->InitCore();
|
|
Context::HandlerConfig.emplace(*CTX);
|
|
InvalidationTracker.emplace(*CTX, Threads);
|
|
ImageTracker.emplace(*CTX, false);
|
|
|
|
auto MainModule = reinterpret_cast<__TEB*>(NtCurrentTeb())->Peb->ImageBaseAddress;
|
|
HandleImageMap(reinterpret_cast<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);
|
|
}
|
|
|
|
FEX::Windows::SetupEnvironmentVariableValues(NtDll);
|
|
|
|
// wow64.dll will only initialise the cross-process queue if this is set
|
|
GetTLS().Wow64Info().CpuFlags = WOW64_CPUFLAGS_SOFTWARE;
|
|
|
|
FEX_CONFIG_OPT(ProfileStats, PROFILESTATS);
|
|
FEX_CONFIG_OPT(StartupSleep, STARTUPSLEEP);
|
|
FEX_CONFIG_OPT(StartupSleepProcName, STARTUPSLEEPPROCNAME);
|
|
|
|
if (IsWine && ProfileStats()) {
|
|
StatAllocHandler = fextl::make_unique<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();
|
|
|
|
// 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
|
|
FEXCore::Utils::SpinWaitLock::WaitBitMaskPred(TLS.ControlWordAddress(), ControlBits::IN_JIT, 0U, std::equal_to<>());
|
|
|
|
// The JIT has now been interrupted and the context stored in the thread's CPU area is up-to-date
|
|
if (Err = NtSuspendThread(*ThreadDup, Count); Err) {
|
|
TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed);
|
|
return Err;
|
|
}
|
|
|
|
CONTEXT TmpContext {
|
|
.ContextFlags = CONTEXT_INTEGER,
|
|
};
|
|
|
|
// NtSuspendThread may return before the thread is actually suspended, so a sync operation like NtGetContextThread
|
|
// needs to be called to ensure it is before we unset CONTROL_PAUSED
|
|
std::ignore = NtGetContextThread(*ThreadDup, &TmpContext);
|
|
|
|
// Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using
|
|
// SetThreadContext (which doesn't use the BTCpu API)
|
|
if (!(TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) {
|
|
if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) {
|
|
return Err;
|
|
}
|
|
}
|
|
|
|
LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID);
|
|
|
|
// Now the thread is suspended on the host, unset CONTROL_PAUSED so that NtResumeThread will
|
|
// continue execution in the JIT
|
|
TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed);
|
|
|
|
return Err;
|
|
}
|
|
|
|
// Returns true if exception dispatch should be halted and the execution context restored to Ptrs->Context
|
|
bool BTCpuResetToConsistentStateImpl(EXCEPTION_POINTERS* Ptrs) {
|
|
auto* Context = Ptrs->ContextRecord;
|
|
auto* Exception = Ptrs->ExceptionRecord;
|
|
auto TLS = GetTLS();
|
|
auto Thread = TLS.ThreadState();
|
|
FEXCORE_PROFILE_ACCUMULATION(Thread, AccumulatedSignalTime);
|
|
|
|
if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
|
const auto FaultAddress = static_cast<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, WowContext.Ecx);
|
|
if (Exception->ExceptionCode == EXCEPTION_SINGLE_STEP) {
|
|
WowContext.EFlags &= ~(1 << FEXCore::X86State::RFLAG_TF_RAW_LOC);
|
|
}
|
|
// wow64.dll will handle adjusting PC in the dispatched context after a breakpoint
|
|
|
|
BTCpuSetContext(GetCurrentThread(), GetCurrentProcess(), nullptr, &WowContext);
|
|
Context::UnlockJITContext(TLS);
|
|
|
|
// Replace the host context with one captured before JIT entry so host code can unwind
|
|
memcpy(Context, TLS.EntryContext(), sizeof(*Context));
|
|
|
|
return false;
|
|
}
|
|
|
|
NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS* Ptrs) {
|
|
if (BTCpuResetToConsistentStateImpl(Ptrs)) {
|
|
NtContinue(Ptrs->ContextRecord, FALSE);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void BTCpuFlushInstructionCache2(const void* Address, SIZE_T Size) {
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BTCpuNotifyProcessExecuteFlagsChange(ULONG Flags) {
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
InvalidationTracker->HandleProcessExecuteFlagsChange(Flags);
|
|
}
|
|
|
|
BOOLEAN WINAPI BTCpuIsProcessorFeaturePresent(UINT Feature) {
|
|
return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE;
|
|
}
|
|
|
|
void BTCpuUpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) {
|
|
CPUFeatures->UpdateInformation(Info);
|
|
}
|