Files
FEX-Emu--FEX/Source/Windows/ARM64EC/Module.cpp
T
2024-08-07 15:49:41 +00:00

739 lines
27 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: Bin|ARM64EC
desc: Implements the ARM64EC BT module API using FEXCore
$end_info$
*/
#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 <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/TypeDefines.h>
#include "Common/Config.h"
#include "Common/HostFeatures.h"
#include "Common/InvalidationTracker.h"
#include "Common/TSOHandlerConfig.h"
#include "Common/CPUFeatures.h"
#include "Common/Logging.h"
#include "Common/CRT/CRT.h"
#include "DummyHandlers.h"
#include "BTInterface.h"
#include <cstdint>
#include <cstdio>
#include <type_traits>
#include <mutex>
#include <optional>
#include <unordered_map>
#include <utility>
#include <ntstatus.h>
#include <windef.h>
#include <winternl.h>
#include <winnt.h>
#include <wine/debug.h>
class ECSyscallHandler;
extern "C" {
void* X64ReturnInstr; // See Module.S
extern void* ExitFunctionEC;
// Wine doesn't support issuing direct system calls with SVC, and unlike Windows it doesn't have a 'stable' syscall number for NtContinue
void* WineSyscallDispatcher;
// TODO: this really shouldn't be hardcoded, once wine gains proper syscall thunks this can be dropped.
uint64_t WineNtContinueSyscallId = 0x1a;
}
struct ThreadCPUArea {
static constexpr size_t TEBCPUAreaOffset = 0x1788;
CHPE_V2_CPU_AREA_INFO* Area;
explicit ThreadCPUArea(_TEB* TEB)
: Area(*reinterpret_cast<CHPE_V2_CPU_AREA_INFO**>(reinterpret_cast<uintptr_t>(TEB) + TEBCPUAreaOffset)) {}
uint64_t& EmulatorStackLimit() const {
return Area->EmulatorStackLimit;
}
uint64_t& EmulatorStackBase() const {
return Area->EmulatorStackBase;
}
ARM64EC_NT_CONTEXT& ContextAmd64() const {
return *Area->ContextAmd64;
}
FEXCore::Core::CpuStateFrame*& StateFrame() const {
return reinterpret_cast<FEXCore::Core::CpuStateFrame*&>(Area->EmulatorData[0]);
}
FEXCore::Core::InternalThreadState*& ThreadState() const {
return reinterpret_cast<FEXCore::Core::InternalThreadState*&>(Area->EmulatorData[1]);
}
uint64_t& DispatcherLoopTopEnterEC() const {
return reinterpret_cast<uint64_t&>(Area->EmulatorData[2]);
}
uint64_t& DispatcherLoopTopEnterECFillSRA() const {
return reinterpret_cast<uint64_t&>(Area->EmulatorData[3]);
}
};
extern "C" NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert);
namespace {
fextl::unique_ptr<FEXCore::Context::Context> CTX;
fextl::unique_ptr<FEX::DummyHandlers::DummySignalDelegator> SignalDelegator;
fextl::unique_ptr<ECSyscallHandler> SyscallHandler;
std::optional<FEX::Windows::InvalidationTracker> InvalidationTracker;
std::optional<FEX::Windows::CPUFeatures> CPUFeatures;
std::recursive_mutex ThreadCreationMutex;
// Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing
std::unordered_map<DWORD, FEXCore::Core::InternalThreadState*> Threads;
std::pair<NTSTATUS, ThreadCPUArea> GetThreadCPUArea(HANDLE Thread) {
THREAD_BASIC_INFORMATION Info;
const NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr);
return {Err, ThreadCPUArea(reinterpret_cast<_TEB*>(Info.TebBaseAddress))};
}
ThreadCPUArea GetCPUArea() {
return ThreadCPUArea(NtCurrentTeb());
}
bool IsEmulatorStackAddress(uint64_t Address) {
return Address <= GetCPUArea().EmulatorStackBase() && Address >= GetCPUArea().EmulatorStackLimit();
}
bool IsDispatcherAddress(uint64_t Address) {
const auto& Config = SignalDelegator->GetConfig();
return Address >= Config.DispatcherBegin && Address < Config.DispatcherEnd;
}
// GetProcAddress on ARM64EC returns a pointer to an x64 fast forward sequence to allow for redirecting to the JIT if functions are
// hotpatched. This looks up the procedure address of the native code even if the fast forward sequence has been patched.
uintptr_t GetRedirectedProcAddress(HMODULE Module, const char* ProcName) {
const uintptr_t Proc = reinterpret_cast<uintptr_t>(GetProcAddress(Module, ProcName));
if (!Proc) {
return 0;
}
ULONG Size;
const auto* LoadConfig =
reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(RtlImageDirectoryEntryToData(Module, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &Size));
const auto* CHPEMetadata = reinterpret_cast<IMAGE_ARM64EC_METADATA*>(LoadConfig->CHPEMetadataPointer);
const uintptr_t ModuleBase = reinterpret_cast<uintptr_t>(Module);
const uintptr_t ProcRVA = Proc - ModuleBase;
const auto* RedirectionTableBegin = reinterpret_cast<IMAGE_ARM64EC_REDIRECTION_ENTRY*>(ModuleBase + CHPEMetadata->RedirectionMetadata);
const auto* RedirectionTableEnd = RedirectionTableBegin + CHPEMetadata->RedirectionMetadataCount;
const auto* It =
std::lower_bound(RedirectionTableBegin, RedirectionTableEnd, ProcRVA, [](const auto& Entry, uintptr_t RVA) { return Entry.Source < RVA; });
if (It->Source != ProcRVA) {
return 0;
}
return ModuleBase + It->Destination;
}
} // namespace
namespace Exception {
static std::optional<FEX::Windows::TSOHandlerConfig> HandlerConfig;
static uintptr_t KiUserExceptionDispatcher;
static EXCEPTION_RECORD HandleGuestException(const EXCEPTION_RECORD& Src, ARM64_NT_CONTEXT& Context) {
auto* Thread = GetCPUArea().ThreadState();
auto& Fault = Thread->CurrentFrame->SynchronousFaultData;
EXCEPTION_RECORD Dst = Src;
Dst.ExceptionAddress = reinterpret_cast<void*>(Context.Pc);
// X64 Windows always clears TF, DF and AF when handling an exception, restoring after.
// Current ARM64EC windows can only restore NZCV+SS when returning from an exception and other flags are left untouched from the handler context.
// TODO: Can extend wine to support this by mapping the remaining EFlags into reserved cpsr members.
uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr);
EFlags &= (1 << FEXCore::X86State::RFLAG_TF_LOC);
CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags);
if (!Fault.FaultToTopAndGeneratedException) {
return Dst;
}
Fault.FaultToTopAndGeneratedException = false;
Dst.ExceptionFlags = 0;
Dst.NumberParameters = 0;
switch (Fault.Signal) {
case FEXCore::Core::FAULT_SIGILL: Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return Dst;
case FEXCore::Core::FAULT_SIGTRAP:
switch (Fault.TrapNo) {
case FEXCore::X86State::X86_TRAPNO_DB:
Context.Cpsr &= ~(1 << 21); // PSTATE.SS
Dst.ExceptionCode = EXCEPTION_SINGLE_STEP;
return Dst;
case FEXCore::X86State::X86_TRAPNO_BP:
Context.Pc -= 1;
Dst.ExceptionAddress = reinterpret_cast<void*>(Context.Pc);
Dst.ExceptionCode = EXCEPTION_BREAKPOINT;
Dst.NumberParameters = 1;
Dst.ExceptionInformation[0] = 0;
return Dst;
default: LogMan::Msg::EFmt("Unknown SIGTRAP trap: {}", Fault.TrapNo); break;
}
break;
case FEXCore::Core::FAULT_SIGSEGV:
switch (Fault.TrapNo) {
case FEXCore::X86State::X86_TRAPNO_GP:
if ((Fault.err_code & 0b111) == 0b010) {
switch (Fault.err_code >> 3) {
case 0x2d:
Context.Pc += 2;
Dst.ExceptionCode = EXCEPTION_BREAKPOINT;
Dst.ExceptionAddress = reinterpret_cast<void*>(Context.Pc + 1);
Dst.NumberParameters = 1;
Dst.ExceptionInformation[0] = Context.X8; // RAX
// Note that ExceptionAddress doesn't equal the reported context RIP here, this discrepancy expected and not having it can trigger anti-debug logic.
return Dst;
default: LogMan::Msg::EFmt("Unknown interrupt: 0x{:X}", Fault.err_code >> 3); break;
}
} else {
Dst.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION;
return Dst;
}
break;
case FEXCore::X86State::X86_TRAPNO_OF: Dst.ExceptionCode = EXCEPTION_INT_OVERFLOW; return Dst;
default: LogMan::Msg::EFmt("Unknown SIGSEGV trap: {}", Fault.TrapNo); break;
}
break;
default: LogMan::Msg::EFmt("Unknown signal type: {}", Fault.Signal); break;
}
// Default to SIGILL
Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION;
return Dst;
}
static bool HandleUnalignedAccess(ARM64_NT_CONTEXT& Context) {
if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context.Pc)) {
return false;
}
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(GetCPUArea().ThreadState(),
HandlerConfig->GetUnalignedHandlerType(), Context.Pc, &Context.X0);
if (!Result.first) {
return false;
}
Context.Pc += Result.second;
return true;
}
static void LoadStateFromECContext(FEXCore::Core::InternalThreadState* Thread, CONTEXT& Context) {
auto& State = Thread->CurrentFrame->State;
if (Context.ContextFlags & CONTEXT_INTEGER) {
// General register state
State.gregs[FEXCore::X86State::REG_RAX] = Context.Rax;
State.gregs[FEXCore::X86State::REG_RCX] = Context.Rcx;
State.gregs[FEXCore::X86State::REG_RDX] = Context.Rdx;
State.gregs[FEXCore::X86State::REG_RBX] = Context.Rbx;
State.gregs[FEXCore::X86State::REG_RSI] = Context.Rsi;
State.gregs[FEXCore::X86State::REG_RDI] = Context.Rdi;
State.gregs[FEXCore::X86State::REG_R8] = Context.R8;
State.gregs[FEXCore::X86State::REG_R9] = Context.R9;
State.gregs[FEXCore::X86State::REG_R10] = Context.R10;
State.gregs[FEXCore::X86State::REG_R11] = Context.R11;
State.gregs[FEXCore::X86State::REG_R12] = Context.R12;
State.gregs[FEXCore::X86State::REG_R13] = Context.R13;
State.gregs[FEXCore::X86State::REG_R14] = Context.R14;
State.gregs[FEXCore::X86State::REG_R15] = Context.R15;
}
if (Context.ContextFlags & CONTEXT_CONTROL) {
State.rip = Context.Rip;
State.gregs[FEXCore::X86State::REG_RSP] = Context.Rsp;
State.gregs[FEXCore::X86State::REG_RBP] = Context.Rbp;
CTX->SetFlagsFromCompactedEFLAGS(Thread, Context.EFlags);
}
if (Context.ContextFlags & CONTEXT_SEGMENTS) {
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
const auto TEB = reinterpret_cast<uint64_t>(NtCurrentTeb());
State.gdt[(Context.SegGs & 0xffff) >> 3].base = TEB;
State.gs_cached = TEB;
State.fs_cached = 0;
State.es_cached = 0;
State.cs_cached = 0;
State.ss_cached = 0;
State.ds_cached = 0;
}
if (Context.ContextFlags & CONTEXT_FLOATING_POINT) {
// Floating-point register state
CTX->SetXMMRegistersFromState(Thread, reinterpret_cast<const __uint128_t*>(Context.FltSave.XmmRegisters), nullptr);
memcpy(State.mm, Context.FltSave.FloatRegisters, sizeof(State.mm));
State.FCW = Context.FltSave.ControlWord;
State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (Context.FltSave.StatusWord >> 8) & 1;
State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (Context.FltSave.StatusWord >> 9) & 1;
State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (Context.FltSave.StatusWord >> 10) & 1;
State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (Context.FltSave.StatusWord >> 14) & 1;
State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (Context.FltSave.StatusWord >> 11) & 0b111;
State.AbridgedFTW = Context.FltSave.TagWord;
}
}
static void ReconstructThreadState(ARM64_NT_CONTEXT& Context) {
const auto& Config = SignalDelegator->GetConfig();
auto* Thread = GetCPUArea().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);
}
// Reconstructs an x64 context from the input context within the JIT, packed into a regular ARM64 context following the ARM64EC register mapping
static ARM64_NT_CONTEXT ReconstructPackedECContext(ARM64_NT_CONTEXT& Context) {
ReconstructThreadState(Context);
ARM64_NT_CONTEXT ECContext {};
ECContext.ContextFlags = CONTEXT_ARM64_FULL;
auto* Thread = GetCPUArea().ThreadState();
auto& State = Thread->CurrentFrame->State;
ECContext.X8 = State.gregs[FEXCore::X86State::REG_RAX];
ECContext.X0 = State.gregs[FEXCore::X86State::REG_RCX];
ECContext.X1 = State.gregs[FEXCore::X86State::REG_RDX];
ECContext.X27 = State.gregs[FEXCore::X86State::REG_RBX];
ECContext.Sp = State.gregs[FEXCore::X86State::REG_RSP];
ECContext.Fp = State.gregs[FEXCore::X86State::REG_RBP];
ECContext.X25 = State.gregs[FEXCore::X86State::REG_RSI];
ECContext.X26 = State.gregs[FEXCore::X86State::REG_RDI];
ECContext.X2 = State.gregs[FEXCore::X86State::REG_R8];
ECContext.X3 = State.gregs[FEXCore::X86State::REG_R9];
ECContext.X4 = State.gregs[FEXCore::X86State::REG_R10];
ECContext.X5 = State.gregs[FEXCore::X86State::REG_R11];
ECContext.X19 = State.gregs[FEXCore::X86State::REG_R12];
ECContext.X20 = State.gregs[FEXCore::X86State::REG_R13];
ECContext.X21 = State.gregs[FEXCore::X86State::REG_R14];
ECContext.X22 = State.gregs[FEXCore::X86State::REG_R15];
ECContext.Pc = State.rip;
CTX->ReconstructXMMRegisters(Thread, reinterpret_cast<__uint128_t*>(&ECContext.V[0]), nullptr);
ECContext.Lr = State.mm[0][0];
ECContext.X6 = State.mm[1][0];
ECContext.X7 = State.mm[2][0];
ECContext.X9 = State.mm[3][0];
ECContext.X16 = (State.mm[3][1] & 0xffff) << 48 | (State.mm[2][1] & 0xffff) << 32 | (State.mm[1][1] & 0xffff) << 16 | (State.mm[0][1] & 0xffff);
ECContext.X10 = State.mm[4][0];
ECContext.X11 = State.mm[5][0];
ECContext.X12 = State.mm[6][0];
ECContext.X15 = State.mm[7][0];
ECContext.X17 = (State.mm[7][1] & 0xffff) << 48 | (State.mm[6][1] & 0xffff) << 32 | (State.mm[5][1] & 0xffff) << 16 | (State.mm[4][1] & 0xffff);
// Zero all disallowed registers
ECContext.X13 = 0;
ECContext.X14 = 0;
ECContext.X18 = 0;
ECContext.X23 = 0;
ECContext.X24 = 0;
ECContext.X28 = 0;
// NZCV+SS will be converted into EFlags by ntdll, the rest are lost during exception handling.
// See HandleGuestException
ECContext.Cpsr = Context.Cpsr;
uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0);
if (EFlags & (1U << FEXCore::X86State::RFLAG_TF_LOC)) {
ECContext.Cpsr |= 1 << 21; // PSTATE.SS
}
ECContext.Fpcr = Context.Fpcr;
ECContext.Fpsr = Context.Fpsr;
return ECContext;
}
static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& Context) {
const auto& Config = SignalDelegator->GetConfig();
uint64_t GuestSp = Context.X[Config.SRAGPRMapping[static_cast<size_t>(FEXCore::X86State::REG_RSP)]];
struct DispatchArgs {
ARM64_NT_CONTEXT Context;
uint64_t Pad[4]; // Only present on newer Windows versions, likely for SVE.
EXCEPTION_RECORD Rec;
uint64_t Align;
uint64_t Redzone[2];
}* Args = reinterpret_cast<DispatchArgs*>(FEXCore::AlignDown(GuestSp, 64)) - 1;
LogMan::Msg::DFmt("Reconstructing context");
Args->Context = ReconstructPackedECContext(Context);
LogMan::Msg::DFmt("pc: {:X} rip: {:X}", Context.Pc, Args->Context.Pc);
Args->Rec = HandleGuestException(Rec, Args->Context);
Context.Sp = reinterpret_cast<uint64_t>(Args);
Context.Pc = KiUserExceptionDispatcher;
}
} // namespace Exception
class ECSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators {
public:
ECSyscallHandler() {
OSABI = FEXCore::HLE::SyscallOSABI::OS_GENERIC;
}
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame* Frame, FEXCore::HLE::SyscallArguments* Args) override {
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 {
InvalidationTracker->ReprotectRWXIntervals(Start, Length);
}
};
extern "C" void SyncThreadContext(CONTEXT* Context) {
auto* Thread = GetCPUArea().ThreadState();
// All other EFlags bits are lost when converting to/from an ARM64EC context, so merge them in from the current JIT state.
// This is advisable over dropping their values as thread suspend/resume uses this function, and that can happen at any point in guest code.
static constexpr uint32_t ECValidEFlagsMask {(1U << FEXCore::X86State::RFLAG_OF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_CF_RAW_LOC) |
(1U << FEXCore::X86State::RFLAG_ZF_RAW_LOC) | (1U << FEXCore::X86State::RFLAG_SF_RAW_LOC) |
(1U << FEXCore::X86State::RFLAG_TF_LOC)};
uint32_t StateEFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0);
Context->EFlags = (Context->EFlags & ECValidEFlagsMask) | (StateEFlags & ~ECValidEFlagsMask);
Exception::LoadStateFromECContext(Thread, *Context);
}
NTSTATUS ProcessInit() {
FEX::Windows::InitCRTProcess();
FEX::Config::InitializeConfigs();
FEXCore::Config::Initialize();
FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer());
FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer());
FEXCore::Config::Load();
FEXCore::Config::ReloadMetaLayer();
FEX::Windows::Logging::Init();
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "1");
// Not applicable to Windows
FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0");
FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT);
SignalDelegator = fextl::make_unique<FEX::DummyHandlers::DummySignalDelegator>();
SyscallHandler = fextl::make_unique<ECSyscallHandler>();
Exception::HandlerConfig.emplace();
CTX = FEXCore::Context::Context::CreateNewContext();
{
auto HostFeatures = FEX::FetchHostFeatures();
CTX->SetHostFeatures(HostFeatures);
}
CTX->SetSignalDelegator(SignalDelegator.get());
CTX->SetSyscallHandler(SyscallHandler.get());
CTX->InitCore();
InvalidationTracker.emplace(*CTX, Threads);
CPUFeatures.emplace(*CTX);
X64ReturnInstr = ::VirtualAlloc(nullptr, FEXCore::Utils::FEX_PAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
*reinterpret_cast<uint8_t*>(X64ReturnInstr) = 0xc3;
const auto NtDll = GetModuleHandle("ntdll.dll");
Exception::KiUserExceptionDispatcher = GetRedirectedProcAddress(NtDll, "KiUserExceptionDispatcher");
const auto WineSyscallDispatcherPtr = reinterpret_cast<void**>(GetProcAddress(NtDll, "__wine_syscall_dispatcher"));
if (WineSyscallDispatcherPtr) {
WineSyscallDispatcher = *WineSyscallDispatcherPtr;
}
return STATUS_SUCCESS;
}
void ProcessTerm(HANDLE Handle, BOOL After, NTSTATUS Status) {}
class ScopedCallbackDisable {
private:
bool Prev;
public:
ScopedCallbackDisable() {
Prev = GetCPUArea().Area->InSyscallCallback;
GetCPUArea().Area->InSyscallCallback = true;
}
~ScopedCallbackDisable() {
GetCPUArea().Area->InSyscallCallback = Prev;
}
};
bool ResetToConsistentStateImpl(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext) {
LogMan::Msg::DFmt("Exception: Code: {:X} Address: {:X}", Exception->ExceptionCode, reinterpret_cast<uintptr_t>(Exception->ExceptionAddress));
const auto CPUArea = GetCPUArea();
if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
const auto FaultAddress = static_cast<uint64_t>(Exception->ExceptionInformation[1]);
bool HandledRWX = false;
if (InvalidationTracker && CPUArea.ThreadState()) {
std::scoped_lock Lock(ThreadCreationMutex);
HandledRWX = InvalidationTracker->HandleRWXAccessViolation(FaultAddress);
}
if (HandledRWX) {
LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", NativeContext->Pc, FaultAddress);
return true;
}
}
if (!CTX->IsAddressInCodeBuffer(CPUArea.ThreadState(), NativeContext->Pc) && !IsDispatcherAddress(NativeContext->Pc)) {
LogMan::Msg::DFmt("Passing through exception");
return false;
}
if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Exception::HandleUnalignedAccess(*NativeContext)) {
LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", NativeContext->Pc);
return true;
}
if (IsEmulatorStackAddress(reinterpret_cast<uint64_t>(__builtin_frame_address(0)))) {
Exception::RethrowGuestException(*Exception, *NativeContext);
LogMan::Msg::DFmt("Rethrowing onto guest stack: {:X}", NativeContext->Sp);
return true;
} else {
LogMan::Msg::EFmt("Unexpected exception in JIT code on guest stack");
return false;
}
}
NTSTATUS ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext) {
if (!GetCPUArea().ThreadState()) {
return STATUS_SUCCESS;
}
bool Cont {};
{
ScopedCallbackDisable guard;
Cont = ResetToConsistentStateImpl(Exception, GuestContext, NativeContext);
}
if (Cont) {
NtContinueNative(NativeContext, false);
}
GetCPUArea().Area->InSimulation = false;
GetCPUArea().Area->InSyscallCallback = false;
return STATUS_SUCCESS;
}
void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot, BOOL After, NTSTATUS Status) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
if (!After || Status) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), Prot);
}
void NotifyMemoryFree(void* Address, SIZE_T Size, ULONG FreeType, BOOL After, NTSTATUS Status) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
if (After) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
if (FreeType & MEM_DECOMMIT) {
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), true);
} else if (FreeType & MEM_RELEASE) {
InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
}
}
void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt, BOOL After, NTSTATUS Status) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
if (!After || Status) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), NewProt);
}
NTSTATUS NotifyMapViewOfSection(void* Unk1, void* Address, void* Unk2, SIZE_T Size, ULONG AllocType, ULONG Prot) {
return STATUS_SUCCESS;
}
void NotifyUnmapViewOfSection(void* Address, BOOL After, NTSTATUS Status) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
if (After) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
}
void FlushInstructionCacheHeavy(const void* Address, SIZE_T Size) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpu64FlushInstructionCache(const void* Address, SIZE_T Size) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpu64NotifyMemoryDirty(void* Address, SIZE_T Size) {
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
return;
}
std::scoped_lock Lock(ThreadCreationMutex);
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), false);
}
void BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {}
NTSTATUS ThreadInit() {
FEX::Windows::InitCRTThread();
static constexpr size_t EmulatorStackSize = 0x40000;
const uint64_t EmulatorStack = reinterpret_cast<uint64_t>(::VirtualAlloc(nullptr, EmulatorStackSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
GetCPUArea().EmulatorStackLimit() = EmulatorStack;
GetCPUArea().EmulatorStackBase() = EmulatorStack + EmulatorStackSize;
const auto CPUArea = GetCPUArea();
auto* Thread = CTX->CreateThread(0, 0);
Thread->CurrentFrame->Pointers.Common.ExitFunctionEC = reinterpret_cast<uintptr_t>(&ExitFunctionEC);
CPUArea.StateFrame() = Thread->CurrentFrame;
uint64_t EnterEC = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterEC;
CPUArea.DispatcherLoopTopEnterEC() = EnterEC;
uint64_t EnterECFillSRA = Thread->CurrentFrame->Pointers.Common.DispatcherLoopTopEnterECFillSRA;
CPUArea.DispatcherLoopTopEnterECFillSRA() = EnterECFillSRA;
CPUArea.ContextAmd64() = {.ContextFlags = CONTEXT_CONTROL | CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT,
.AMD64_SegCs = 0x33,
.AMD64_SegDs = 0x2b,
.AMD64_SegEs = 0x2b,
.AMD64_SegFs = 0x53,
.AMD64_SegGs = 0x2b,
.AMD64_SegSs = 0x2b,
.AMD64_EFlags = 0x202,
.AMD64_MxCsr = 0x1f80,
.AMD64_MxCsr_copy = 0x1f80,
.AMD64_ControlWord = 0x27f};
Exception::LoadStateFromECContext(Thread, CPUArea.ContextAmd64().AMD64_Context);
{
std::scoped_lock Lock(ThreadCreationMutex);
Threads.emplace(GetCurrentThreadId(), Thread);
}
CPUArea.ThreadState() = Thread;
return STATUS_SUCCESS;
}
NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) {
const auto [Err, CPUArea] = GetThreadCPUArea(Thread);
if (Err) {
return Err;
}
auto* OldThreadState = CPUArea.ThreadState();
CPUArea.ThreadState() = nullptr;
{
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);
std::scoped_lock Lock(ThreadCreationMutex);
Threads.erase(ThreadTID);
}
CTX->DestroyThread(OldThreadState);
::VirtualFree(reinterpret_cast<void*>(GetCPUArea().EmulatorStackLimit()), 0, MEM_RELEASE);
FEX::Windows::DeinitCRTThread();
return STATUS_SUCCESS;
}
BOOLEAN BTCpu64IsProcessorFeaturePresent(UINT Feature) {
return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE;
}
void UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) {
CPUFeatures->UpdateInformation(Info);
}