Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/ArchHelpers/MContext.h
T

383 lines
11 KiB
C++

// SPDX-License-Identifier: MIT
#pragma once
#include "UContext.h"
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/X86Enums.h>
#include <signal.h>
#include <string.h>
#ifndef _WIN32
#include <ucontext.h>
#endif
#include <stdint.h>
#include <type_traits>
namespace FEX::ArchHelpers::Context {
#ifndef _WIN32
enum ContextFlags : uint32_t {
CONTEXT_FLAG_INJIT = (1U << 0),
};
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
constexpr uint64_t STACK_COOKIE_MAGIC = 0x4142434445464748ULL;
#endif
struct X86ContextBackup {
// Host State
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
// During debug builds, insert a cookie on the stack.
// This is useful for validation that the stack is trying to be restored from the correct location.
// During stack restore, we ensure this is set to the value we expect.
// If given an incorrect stack location, or corrupted stack then this cookie will be wrong.
uint64_t StackCookie;
#endif
// RIP and RSP is stored in GPRs here
uint64_t GPRs[23];
FEXCore::x86_64::_libc_fpstate FPRState;
uint64_t sa_mask;
uint16_t InSyscallInfo;
bool FaultToTopAndGeneratedException;
// Guest state
int Signal;
uint32_t Flags;
uint64_t OriginalRIP;
uint64_t FPStateLocation;
uint64_t UContextLocation;
uint64_t SigInfoLocation;
FEXCore::Core::CPUState GuestState;
static constexpr int RedZoneSize = 128;
};
struct ArmContextBackup {
// Host State
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
uint64_t StackCookie;
#endif
uint64_t GPRs[31];
uint64_t PrevSP;
uint64_t PrevPC;
uint64_t PState;
uint32_t FPSR;
uint32_t FPCR;
__uint128_t FPRs[32];
uint64_t sa_mask;
uint16_t InSyscallInfo;
bool FaultToTopAndGeneratedException;
// Guest state
int Signal;
uint32_t Flags;
uint64_t OriginalRIP;
uint64_t FPStateLocation;
uint64_t UContextLocation;
uint64_t SigInfoLocation;
FEXCore::Core::CPUState GuestState;
// Arm64 doesn't have a red zone
static constexpr int RedZoneSize = 0;
};
static inline ucontext_t* GetUContext(void* ucontext) {
ucontext_t* _context = (ucontext_t*)ucontext;
return _context;
}
static inline mcontext_t* GetMContext(void* ucontext) {
ucontext_t* _context = (ucontext_t*)ucontext;
return &_context->uc_mcontext;
}
#ifdef _M_ARM_64
constexpr uint32_t FPR_MAGIC = 0x46508001U;
constexpr uint32_t ESR1_MAGIC = 0x45535201U;
struct HostCTXHeader {
uint32_t Magic;
uint32_t Size;
};
struct HostFPRState {
HostCTXHeader Head;
uint32_t FPSR;
uint32_t FPCR;
__uint128_t FPRs[32];
};
struct HostESRState {
HostCTXHeader Head;
uint64_t ESR;
};
static inline uint64_t GetSp(void* ucontext) {
return GetMContext(ucontext)->sp;
}
static inline uint64_t GetPc(void* ucontext) {
return GetMContext(ucontext)->pc;
}
static inline void SetSp(void* ucontext, uint64_t val) {
GetMContext(ucontext)->sp = val;
}
static inline void SetPc(void* ucontext, uint64_t val) {
GetMContext(ucontext)->pc = val;
}
static inline uint64_t GetState(void* ucontext) {
return GetMContext(ucontext)->regs[28];
}
static inline void SetState(void* ucontext, uint64_t val) {
GetMContext(ucontext)->regs[28] = val;
}
static inline uint64_t GetArmReg(void* ucontext, uint32_t id) {
return GetMContext(ucontext)->regs[id];
}
static inline uint64_t GetArmPState(void* ucontext) {
return GetMContext(ucontext)->pstate;
}
static inline uint64_t* GetArmGPRs(void* ucontext) {
return reinterpret_cast<uint64_t*>(GetMContext(ucontext)->regs);
}
static inline void SetArmReg(void* ucontext, uint32_t id, uint64_t val) {
GetMContext(ucontext)->regs[id] = val;
}
static inline __uint128_t GetArmFPR(void* ucontext, uint32_t id) {
auto MContext = GetMContext(ucontext);
HostFPRState* HostState = reinterpret_cast<HostFPRState*>(&MContext->__reserved[0]);
LOGMAN_THROW_A_FMT(HostState->Head.Magic == FPR_MAGIC, "Wrong FPR Magic: 0x{:08x}", HostState->Head.Magic);
return HostState->FPRs[id];
}
static inline uint64_t GetArmESR(void* ucontext) {
auto MContext = GetMContext(ucontext);
size_t i = 0;
auto HostState = reinterpret_cast<HostCTXHeader*>(&MContext->__reserved[i]);
do {
if (HostState->Magic == ESR1_MAGIC) {
auto ESR = reinterpret_cast<HostESRState*>(HostState);
return ESR->ESR;
}
i += HostState->Size;
HostState = reinterpret_cast<HostCTXHeader*>(&MContext->__reserved[i]);
} while (HostState->Size != 0);
return 0;
}
constexpr static uint64_t ESR1_EC = 0b111111U << 26;
constexpr static uint64_t ESR1_EC_DataAbort = 0b100100U << 26;
// Write-Not-Read flag
// When set - Abort is due to a write
constexpr static uint64_t ESR1_WNR = 1 << 6;
// DFSC - Default Status Code
// Translation fault - No page mapped
// Permissions fault - Page mapped but with incorrect permission from access.
constexpr static uint64_t ESR1_DataAbort_DFSC = 0b111111;
constexpr static uint64_t ESR1_DataAbort_TranslationFault_EL0 = 0b000111;
constexpr static uint64_t ESR1_DataAbort_PermissionFault_EL0 = 0b001111;
constexpr static uint64_t ESR1_DataAbort_Level = 0b11;
constexpr static uint64_t ESR1_DataAbort_Level_EL3 = 0b00;
constexpr static uint64_t ESR1_DataAbort_Level_EL2 = 0b01;
constexpr static uint64_t ESR1_DataAbort_Level_EL1 = 0b10;
constexpr static uint64_t ESR1_DataAbort_Level_EL0 = 0b11;
static inline uint32_t GetProtectFlags(void* ucontext) {
uint64_t ESR = GetArmESR(ucontext);
LOGMAN_THROW_A_FMT((ESR & ESR1_EC) == ESR1_EC_DataAbort, "Unknown ESR1 EC type: 0x{:x} != 0x{:x}", ESR & ESR1_EC, ESR1_EC_DataAbort);
uint32_t ProtectFlags {};
if ((ESR & ESR1_DataAbort_Level) == ESR1_DataAbort_Level_EL0) {
// Always a user error for us.
ProtectFlags |= FEXCore::X86State::X86_PF_USER;
}
if (ESR & ESR1_WNR) {
// Fault was due to a write
ProtectFlags |= FEXCore::X86State::X86_PF_WRITE;
}
// PF_PROT is not returned to user on x86, so don't return the difference between permission fault and translation fault.
return ProtectFlags;
}
using ContextBackup = ArmContextBackup;
template<typename T>
static inline void BackupContext(void* ucontext, T* Backup) {
if constexpr (std::is_same<T, ArmContextBackup>::value) {
auto _ucontext = GetUContext(ucontext);
auto _mcontext = GetMContext(ucontext);
memcpy(&Backup->GPRs[0], &_mcontext->regs[0], 31 * sizeof(uint64_t));
Backup->PrevSP = ArchHelpers::Context::GetSp(ucontext);
Backup->PrevPC = ArchHelpers::Context::GetPc(ucontext);
Backup->PState = _mcontext->pstate;
// Host FPR state starts at _mcontext->reserved[0];
HostFPRState* HostState = reinterpret_cast<HostFPRState*>(&_mcontext->__reserved[0]);
LOGMAN_THROW_A_FMT(HostState->Head.Magic == FPR_MAGIC, "Wrong FPR Magic: 0x{:08x}", HostState->Head.Magic);
Backup->FPSR = HostState->FPSR;
Backup->FPCR = HostState->FPCR;
memcpy(&Backup->FPRs[0], &HostState->FPRs[0], 32 * sizeof(__uint128_t));
// Save the signal mask so we can restore it
memcpy(&Backup->sa_mask, &_ucontext->uc_sigmask, sizeof(uint64_t));
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
Backup->StackCookie = STACK_COOKIE_MAGIC;
#endif
} else {
// This must be a runtime error
ERROR_AND_DIE_FMT("Wrong context type");
}
}
template<typename T>
static inline void RestoreContext(void* ucontext, T* Backup) {
if constexpr (std::is_same<T, ArmContextBackup>::value) {
LOGMAN_THROW_A_FMT(Backup->StackCookie == STACK_COOKIE_MAGIC, "Stack cookie didn't match! 0x{:x}", Backup->StackCookie);
auto _ucontext = GetUContext(ucontext);
auto _mcontext = GetMContext(ucontext);
HostFPRState* HostState = reinterpret_cast<HostFPRState*>(&_mcontext->__reserved[0]);
LOGMAN_THROW_A_FMT(HostState->Head.Magic == FPR_MAGIC, "Wrong FPR Magic: 0x{:08x}", HostState->Head.Magic);
memcpy(&HostState->FPRs[0], &Backup->FPRs[0], 32 * sizeof(__uint128_t));
HostState->FPCR = Backup->FPCR;
HostState->FPSR = Backup->FPSR;
// Restore GPRs and other state
_mcontext->pstate = Backup->PState;
ArchHelpers::Context::SetPc(ucontext, Backup->PrevPC);
ArchHelpers::Context::SetSp(ucontext, Backup->PrevSP);
memcpy(&_mcontext->regs[0], &Backup->GPRs[0], 31 * sizeof(uint64_t));
// Restore the signal mask now
memcpy(&_ucontext->uc_sigmask, &Backup->sa_mask, sizeof(uint64_t));
} else {
// This must be a runtime error
ERROR_AND_DIE_FMT("Wrong context type");
}
}
#endif
#ifdef _M_X86_64
static inline uint64_t GetSp(void* ucontext) {
return GetMContext(ucontext)->gregs[REG_RSP];
}
static inline uint64_t GetPc(void* ucontext) {
return GetMContext(ucontext)->gregs[REG_RIP];
}
static inline void SetSp(void* ucontext, uint64_t val) {
GetMContext(ucontext)->gregs[REG_RSP] = val;
}
static inline void SetPc(void* ucontext, uint64_t val) {
GetMContext(ucontext)->gregs[REG_RIP] = val;
}
static inline uint64_t GetState(void* ucontext) {
return GetMContext(ucontext)->gregs[REG_R14];
}
static inline void SetState(void* ucontext, uint64_t val) {
GetMContext(ucontext)->gregs[REG_R14] = val;
}
static inline uint64_t GetArmReg(void* ucontext, uint32_t id) {
ERROR_AND_DIE_FMT("Not impelented for x86 host");
}
static inline void SetArmReg(void* ucontext, uint32_t id, uint64_t val) {
ERROR_AND_DIE_FMT("Not impelented for x86 host");
}
static inline __uint128_t GetArmFPR(void* ucontext, uint32_t id) {
ERROR_AND_DIE_FMT("Not implemented for x86 host");
}
static inline uint64_t GetArmPState(void* ucontext) {
ERROR_AND_DIE_FMT("Not implemented for x86 host");
}
static inline uint64_t* GetArmGPRs(void* ucontext) {
ERROR_AND_DIE_FMT("Not implemented for x86 host");
}
static inline uint32_t GetProtectFlags(void* ucontext) {
return GetMContext(ucontext)->gregs[REG_ERR];
}
using ContextBackup = X86ContextBackup;
template<typename T>
static inline void BackupContext(void* ucontext, T* Backup) {
if constexpr (std::is_same<T, X86ContextBackup>::value) {
auto _ucontext = GetUContext(ucontext);
auto _mcontext = GetMContext(ucontext);
// Copy the GPRs
memcpy(&Backup->GPRs[0], &_mcontext->gregs[0], sizeof(X86ContextBackup::GPRs));
// Copy the FPRState
memcpy(&Backup->FPRState, _mcontext->fpregs, sizeof(X86ContextBackup::FPRState));
// XXX: Save 256bit and 512bit AVX register state
// Save the signal mask so we can restore it
memcpy(&Backup->sa_mask, &_ucontext->uc_sigmask, sizeof(uint64_t));
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
Backup->StackCookie = STACK_COOKIE_MAGIC;
#endif
} else {
// This must be a runtime error
ERROR_AND_DIE_FMT("Wrong context type");
}
}
template<typename T>
static inline void RestoreContext(void* ucontext, T* Backup) {
if constexpr (std::is_same<T, X86ContextBackup>::value) {
LOGMAN_THROW_A_FMT(Backup->StackCookie == STACK_COOKIE_MAGIC, "Stack cookie didn't match! 0x{:x}", Backup->StackCookie);
auto _ucontext = GetUContext(ucontext);
auto _mcontext = GetMContext(ucontext);
// Copy the GPRs
memcpy(&_mcontext->gregs[0], &Backup->GPRs[0], sizeof(X86ContextBackup::GPRs));
// Copy the FPRState
memcpy(_mcontext->fpregs, &Backup->FPRState, sizeof(X86ContextBackup::FPRState));
// Restore the signal mask now
memcpy(&_ucontext->uc_sigmask, &Backup->sa_mask, sizeof(uint64_t));
} else {
// This must be a runtime error
ERROR_AND_DIE_FMT("Wrong context type");
}
}
#endif
#else
#endif
} // namespace FEX::ArchHelpers::Context