mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-07 18:00:17 +02:00
Windows can sometimes pass in incomplete contexts to BeginSimulation. So only sync the valid parts specified in ContextFlags.
646 lines
24 KiB
C++
646 lines
24 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/InvalidationTracker.h"
|
|
#include "Common/TSOHandlerConfig.h"
|
|
#include "Common/CPUFeatures.h"
|
|
#include "Common/Logging.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);
|
|
|
|
// Windows always clears TF, DF and AF when handling an exception, restoring after.
|
|
// TODO: Check windows behaviour for the restoring after, quite awkward to achieve with the BT API. Would need to fixup flags after a
|
|
// rethrow and keep track of context pointers on the stack so if a SEH handler changes flags they can be restored in BeginContext after
|
|
// the NtContinue syscall (which will convert to an ARM64 context and back, losing these flags).
|
|
uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr);
|
|
EFlags &= ~((1 << FEXCore::X86State::RFLAG_DF_RAW_LOC) | (1 << FEXCore::X86State::RFLAG_TF_LOC) | (1 << FEXCore::X86State::RFLAG_AF_RAW_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: 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));
|
|
}
|
|
}
|
|
|
|
// 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 will be converted into EFlags by ntdll, the rest are lost during exception handling.
|
|
// See HandleGuestException
|
|
ECContext.Cpsr = Context.Cpsr;
|
|
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;
|
|
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();
|
|
Exception::LoadStateFromECContext(Thread, *Context);
|
|
}
|
|
|
|
void ProcessInit() {
|
|
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();
|
|
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;
|
|
}
|
|
}
|
|
|
|
void ProcessTerm() {}
|
|
|
|
class ScopedCallbackDisable {
|
|
private:
|
|
bool Prev;
|
|
|
|
public:
|
|
ScopedCallbackDisable() {
|
|
Prev = GetCPUArea().Area->InSyscallCallback;
|
|
GetCPUArea().Area->InSyscallCallback = true;
|
|
}
|
|
|
|
~ScopedCallbackDisable() {
|
|
GetCPUArea().Area->InSyscallCallback = Prev;
|
|
}
|
|
};
|
|
|
|
NTSTATUS ResetToConsistentState(EXCEPTION_POINTERS* Ptrs, ARM64_NT_CONTEXT* Context, BOOLEAN* Continue) {
|
|
ScopedCallbackDisable Guard;
|
|
const auto* Exception = Ptrs->ExceptionRecord;
|
|
if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Exception::HandleUnalignedAccess(*Context)) {
|
|
LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc);
|
|
*Continue = true;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
|
const auto FaultAddress = static_cast<uint64_t>(Exception->ExceptionInformation[1]);
|
|
|
|
bool HandledRWX = false;
|
|
if (InvalidationTracker && GetCPUArea().ThreadState()) {
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
HandledRWX = InvalidationTracker->HandleRWXAccessViolation(FaultAddress);
|
|
}
|
|
|
|
if (HandledRWX) {
|
|
LogMan::Msg::DFmt("Handled self-modifying code: pc: {:X} fault: {:X}", Context->Pc, FaultAddress);
|
|
*Continue = true;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context->Pc) && !IsDispatcherAddress(Context->Pc)) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
if (IsEmulatorStackAddress(reinterpret_cast<uint64_t>(__builtin_frame_address(0)))) {
|
|
Exception::RethrowGuestException(*Exception, *Context);
|
|
LogMan::Msg::DFmt("Rethrowing onto guest stack: {:X}", Context->Sp);
|
|
*Continue = true;
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
LogMan::Msg::EFmt("Unexpected exception in JIT code on guest stack");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
void NotifyMemoryAlloc(void* Address, SIZE_T Size, ULONG Type, ULONG Prot) {
|
|
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
|
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) {
|
|
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
|
return;
|
|
}
|
|
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
if (!Size) {
|
|
InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
|
|
} else if (FreeType & MEM_DECOMMIT) {
|
|
InvalidationTracker->InvalidateAlignedInterval(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), true);
|
|
}
|
|
}
|
|
|
|
void NotifyMemoryProtect(void* Address, SIZE_T Size, ULONG NewProt) {
|
|
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
|
return;
|
|
}
|
|
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
InvalidationTracker->HandleMemoryProtectionNotification(reinterpret_cast<uint64_t>(Address), static_cast<uint64_t>(Size), NewProt);
|
|
}
|
|
|
|
void NotifyUnmapViewOfSection(void* Address) {
|
|
if (!InvalidationTracker || !GetCPUArea().ThreadState()) {
|
|
return;
|
|
}
|
|
|
|
std::scoped_lock Lock(ThreadCreationMutex);
|
|
InvalidationTracker->InvalidateContainingSection(reinterpret_cast<uint64_t>(Address), true);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
NTSTATUS ThreadInit() {
|
|
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) {
|
|
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);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
BOOLEAN BTCpu64IsProcessorFeaturePresent(UINT Feature) {
|
|
return CPUFeatures->IsFeaturePresent(Feature) ? TRUE : FALSE;
|
|
}
|
|
|
|
void UpdateProcessorInformation(SYSTEM_CPU_INFORMATION* Info) {
|
|
CPUFeatures->UpdateInformation(Info);
|
|
}
|