Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp
T
Ryan Houdek 5a35e119fe Telemetry: Adds tracker for non-canonical memory access crash
This may be useful for tracking TSO faulting when it manages to fetch
stale data. While most TSO crashes are due to nullptr dereferences, this
can still check for the corruption case.
2024-03-21 20:47:36 -07:00

2166 lines
85 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: LinuxSyscalls|common
desc: Handles host -> host and host -> guest signal routing, emulates procmask & co
$end_info$
*/
#include "LinuxSyscalls/SignalDelegator.h"
#include "LinuxSyscalls/Syscalls.h"
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/SignalDelegator.h>
#include <FEXCore/Core/X86Enums.h>
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/HLE/Linux/ThreadManagement.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Utils/FPState.h>
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <atomic>
#include <string.h>
#include <errno.h>
#include <exception>
#include <functional>
#include <linux/futex.h>
#include <signal.h>
#include <syscall.h>
#include <sys/mman.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include <utility>
// For older build environments
#ifndef SS_AUTODISARM
#define SS_AUTODISARM (1U << 31)
#endif
namespace FEX::HLE {
#ifdef _M_X86_64
__attribute__((naked))
static void sigrestore() {
__asm volatile("syscall;"
:: "a" (0xF)
: "memory");
}
#endif
constexpr static uint32_t X86_MINSIGSTKSZ = 0x2000U;
// We can only have one delegator per process
static SignalDelegator *GlobalDelegator{};
struct ThreadState {
FEXCore::Core::InternalThreadState *Thread{};
void *AltStackPtr{};
stack_t GuestAltStack {
.ss_sp = nullptr,
.ss_flags = SS_DISABLE, // By default the guest alt stack is disabled
.ss_size = 0,
};
// This is the thread's current signal mask
GuestSAMask CurrentSignalMask{};
// The mask prior to a suspend
GuestSAMask PreviousSuspendMask{};
uint64_t PendingSignals{};
};
thread_local ThreadState ThreadData{};
static void SignalHandlerThunk(int Signal, siginfo_t *Info, void *UContext) {
GlobalDelegator->HandleSignal(Signal, Info, UContext);
}
uint64_t SigIsMember(GuestSAMask *Set, int Signal) {
// Signal 0 isn't real, so everything is offset by one inside the set
Signal -= 1;
return (Set->Val >> Signal) & 1;
}
uint64_t SetSignal(GuestSAMask *Set, int Signal) {
// Signal 0 isn't real, so everything is offset by one inside the set
Signal -= 1;
return Set->Val | (1ULL << Signal);
}
/**
* @name Signal frame setup
* @{ */
// Total number of layouts that siginfo supports.
enum class SigInfoLayout {
LAYOUT_KILL,
LAYOUT_TIMER,
LAYOUT_POLL,
LAYOUT_FAULT,
LAYOUT_FAULT_RIP,
LAYOUT_CHLD,
LAYOUT_RT,
LAYOUT_SYS,
};
// Calculate the siginfo layout based on Signal and si_code.
static SigInfoLayout CalculateSigInfoLayout(int Signal, int si_code) {
if (si_code > SI_USER && si_code < SI_KERNEL) {
// For signals that are not considered RT.
if (Signal == SIGSEGV ||
Signal == SIGBUS ||
Signal == SIGTRAP) {
// Regular FAULT layout.
return SigInfoLayout::LAYOUT_FAULT;
}
else if (Signal == SIGILL ||
Signal == SIGFPE) {
// Fault layout but addr refers to RIP.
return SigInfoLayout::LAYOUT_FAULT_RIP;
}
else if (Signal == SIGCHLD) {
// Child layout
return SigInfoLayout::LAYOUT_CHLD;
}
else if (Signal == SIGPOLL) {
// Poll layout
return SigInfoLayout::LAYOUT_POLL;
}
else if (Signal == SIGSYS) {
// Sys layout
return SigInfoLayout::LAYOUT_SYS;
}
}
else {
// Negative si_codes are kernel specific things.
if (si_code == SI_TIMER) {
return SigInfoLayout::LAYOUT_TIMER;
}
else if (si_code == SI_SIGIO) {
return SigInfoLayout::LAYOUT_POLL;
}
else if (si_code < 0) {
return SigInfoLayout::LAYOUT_RT;
}
}
return SigInfoLayout::LAYOUT_KILL;
}
void SignalDelegator::HandleSignal(int Signal, void *Info, void *UContext) {
// Let the host take first stab at handling the signal
auto Thread = GetTLSThread();
if (!Thread) {
LogMan::Msg::AFmt("[{}] Thread has received a signal and hasn't registered itself with the delegate! Programming error!", FHU::Syscalls::gettid());
}
else {
SignalHandler &Handler = HostHandlers[Signal];
for (auto &HandlerFunc : Handler.Handlers) {
if (HandlerFunc(Thread, Signal, Info, UContext)) {
// If the host handler handled the fault then we can continue now
return;
}
}
if (Handler.FrontendHandler &&
Handler.FrontendHandler(Thread, Signal, Info, UContext)) {
return;
}
// Now let the frontend handle the signal
// It's clearly a guest signal and this ends up being an OS specific issue
HandleGuestSignal(Thread, Signal, Info, UContext);
}
}
void SignalDelegator::RegisterHostSignalHandler(int Signal, HostSignalDelegatorFunction Func, bool Required) {
SetHostSignalHandler(Signal, Func, Required);
FrontendRegisterHostSignalHandler(Signal, Func, Required);
}
void SignalDelegator::SpillSRA(FEXCore::Core::InternalThreadState *Thread, void *ucontext, uint32_t IgnoreMask) {
#ifdef _M_ARM_64
for (size_t i = 0; i < Config.SRAGPRCount; i++) {
const uint8_t SRAIdxMap = Config.SRAGPRMapping[i];
if (IgnoreMask & (1U << SRAIdxMap)) {
// Skip this one, it's already spilled
continue;
}
Thread->CurrentFrame->State.gregs[i] = ArchHelpers::Context::GetArmReg(ucontext, SRAIdxMap);
}
if (Config.SupportsAVX) {
// TODO: This doesn't save the upper 128-bits of the 256-bit registers.
// This needs to be implemented still.
for (size_t i = 0; i < Config.SRAFPRCount; i++) {
auto FPR = ArchHelpers::Context::GetArmFPR(ucontext, Config.SRAFPRMapping[i]);
memcpy(&Thread->CurrentFrame->State.xmm.avx.data[i][0], &FPR, sizeof(__uint128_t));
}
} else {
for (size_t i = 0; i < Config.SRAFPRCount; i++) {
auto FPR = ArchHelpers::Context::GetArmFPR(ucontext, Config.SRAFPRMapping[i]);
memcpy(&Thread->CurrentFrame->State.xmm.sse.data[i][0], &FPR, sizeof(__uint128_t));
}
}
#endif
}
static uint32_t ConvertSignalToTrapNo(int Signal, siginfo_t *HostSigInfo) {
switch (Signal) {
case SIGSEGV:
if (HostSigInfo->si_code == SEGV_MAPERR ||
HostSigInfo->si_code == SEGV_ACCERR) {
// Protection fault
return FEXCore::X86State::X86_TRAPNO_PF;
}
break;
}
// Unknown mapping, fall back to old behaviour and just pass signal
return Signal;
}
static uint32_t ConvertSignalToError(void *ucontext, int Signal, siginfo_t *HostSigInfo) {
switch (Signal) {
case SIGSEGV:
if (HostSigInfo->si_code == SEGV_MAPERR ||
HostSigInfo->si_code == SEGV_ACCERR) {
// Protection fault
// Always a user fault for us
return ArchHelpers::Context::GetProtectFlags(ucontext);
}
break;
}
// Not a page fault issue
return 0;
}
template <typename T>
static void SetXStateInfo(T* xstate, bool is_avx_enabled) {
auto* fpstate = &xstate->fpstate;
fpstate->sw_reserved.magic1 = FEXCore::x86_64::fpx_sw_bytes::FP_XSTATE_MAGIC;
fpstate->sw_reserved.extended_size = is_avx_enabled ? sizeof(T) : 0;
fpstate->sw_reserved.xfeatures |= FEXCore::x86_64::fpx_sw_bytes::FEATURE_FP |
FEXCore::x86_64::fpx_sw_bytes::FEATURE_SSE;
if (is_avx_enabled) {
fpstate->sw_reserved.xfeatures |= FEXCore::x86_64::fpx_sw_bytes::FEATURE_YMM;
}
fpstate->sw_reserved.xstate_size = fpstate->sw_reserved.extended_size;
if (is_avx_enabled) {
xstate->xstate_hdr.xfeatures = 0;
}
}
ArchHelpers::Context::ContextBackup* SignalDelegator::StoreThreadState(FEXCore::Core::InternalThreadState *Thread, int Signal, void *ucontext) {
// We can end up getting a signal at any point in our host state
// Jump to a handler that saves all state so we can safely return
uint64_t OldSP = ArchHelpers::Context::GetSp(ucontext);
uintptr_t NewSP = OldSP;
size_t StackOffset = sizeof(ArchHelpers::Context::ContextBackup);
// We need to back up behind the host's red zone
// We do this on the guest side as well
// (does nothing on arm hosts)
NewSP -= ArchHelpers::Context::ContextBackup::RedZoneSize;
NewSP -= StackOffset;
NewSP = FEXCore::AlignDown(NewSP, 16);
auto Context = reinterpret_cast<ArchHelpers::Context::ContextBackup*>(NewSP);
ArchHelpers::Context::BackupContext(ucontext, Context);
// Retain the action pointer so we can see it when we return
Context->Signal = Signal;
// Save guest state
// We can't guarantee if registers are in context or host GPRs
// So we need to save everything
memcpy(&Context->GuestState, &Thread->CurrentFrame->State, sizeof(FEXCore::Core::CPUState));
// Set the new SP
ArchHelpers::Context::SetSp(ucontext, NewSP);
Context->Flags = 0;
Context->FPStateLocation = 0;
Context->UContextLocation = 0;
Context->SigInfoLocation = 0;
Context->InSyscallInfo = 0;
// Store fault to top status and then reset it
Context->FaultToTopAndGeneratedException = Thread->CurrentFrame->SynchronousFaultData.FaultToTopAndGeneratedException;
Thread->CurrentFrame->SynchronousFaultData.FaultToTopAndGeneratedException = false;
return Context;
}
void SignalDelegator::RestoreThreadState(FEXCore::Core::InternalThreadState *Thread, void *ucontext, RestoreType Type) {
const bool IsAVXEnabled = Config.SupportsAVX;
uint64_t OldSP{};
if (Type == RestoreType::TYPE_PAUSE) [[unlikely]] {
OldSP = ArchHelpers::Context::GetSp(ucontext);
}
else {
// Some fun introspection here.
// We store a pointer to our host-stack on the guest stack.
// We need to inspect the guest state coming in, so we can get our host stack back.
uint64_t GuestSP = Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP];
if (Is64BitMode) {
// Signal frame layout on stack needs to be as follows
// void* ReturnPointer
// ucontext_t
// siginfo_t
// FP state
// Host stack location
GuestSP += sizeof(FEXCore::x86_64::ucontext_t);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86_64::ucontext_t));
GuestSP += sizeof(siginfo_t);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(siginfo_t));
if (IsAVXEnabled) {
GuestSP += sizeof(FEXCore::x86_64::xstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86_64::xstate));
} else {
GuestSP += sizeof(FEXCore::x86_64::_libc_fpstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86_64::_libc_fpstate));
}
}
else {
if (Type == RestoreType::TYPE_NONREALTIME) {
// Signal frame layout on stack needs to be as follows
// SigFrame_i32
// FPState
// Host stack location
// Remove the 4-byte pretcode /AND/ a legacy argument that is ignored.
GuestSP += sizeof(SigFrame_i32) - 8;
GuestSP = FEXCore::AlignUp(GuestSP, alignof(SigFrame_i32));
if (IsAVXEnabled) {
GuestSP += sizeof(FEXCore::x86::xstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86::xstate));
} else {
GuestSP += sizeof(FEXCore::x86::_libc_fpstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86::_libc_fpstate));
}
}
else {
// Signal frame layout on stack needs to be as follows
// RTSigFrame_i32
// FPState
// Host stack location
// Remove the 4-byte pretcode.
GuestSP += sizeof(RTSigFrame_i32) - 4;
GuestSP = FEXCore::AlignUp(GuestSP, alignof(RTSigFrame_i32));
if (IsAVXEnabled) {
GuestSP += sizeof(FEXCore::x86::xstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86::xstate));
} else {
GuestSP += sizeof(FEXCore::x86::_libc_fpstate);
GuestSP = FEXCore::AlignUp(GuestSP, alignof(FEXCore::x86::_libc_fpstate));
}
}
}
OldSP = *reinterpret_cast<uint64_t*>(GuestSP);
}
uintptr_t NewSP = OldSP;
auto Context = reinterpret_cast<ArchHelpers::Context::ContextBackup*>(NewSP);
// Restore host state
ArchHelpers::Context::RestoreContext(ucontext, Context);
// Reset the guest state
memcpy(&Thread->CurrentFrame->State, &Context->GuestState, sizeof(FEXCore::Core::CPUState));
if (Context->UContextLocation) {
auto Frame = Thread->CurrentFrame;
if (Context->Flags &ArchHelpers::Context::ContextFlags::CONTEXT_FLAG_INJIT) {
// XXX: Unsupported since it needs state reconstruction
// If we are in the JIT then SRA might need to be restored to values from the context
// We can't currently support this since it might result in tearing without real state reconstruction
}
if (Is64BitMode) {
RestoreFrame_x64(Thread, Context, Frame, ucontext);
}
else {
if (Type == RestoreType::TYPE_NONREALTIME) {
RestoreFrame_ia32(Thread, Context, Frame, ucontext);
}
else {
RestoreRTFrame_ia32(Thread, Context, Frame, ucontext);
}
}
}
}
void SignalDelegator::RestoreFrame_x64(FEXCore::Core::InternalThreadState *Thread, ArchHelpers::Context::ContextBackup* Context, FEXCore::Core::CpuStateFrame *Frame, void *ucontext) {
const bool IsAVXEnabled = Config.SupportsAVX;
auto *guest_uctx = reinterpret_cast<FEXCore::x86_64::ucontext_t*>(Context->UContextLocation);
[[maybe_unused]] auto *guest_siginfo = reinterpret_cast<siginfo_t*>(Context->SigInfoLocation);
// If the guest modified the RIP then we need to take special precautions here
if (Context->OriginalRIP != guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_RIP] ||
Context->FaultToTopAndGeneratedException) {
// Restore previous `InSyscallInfo` structure.
Frame->InSyscallInfo = Context->InSyscallInfo;
// Hack! Go back to the top of the dispatcher top
// This is only safe inside the JIT rather than anything outside of it
ArchHelpers::Context::SetPc(ucontext, Config.AbsoluteLoopTopAddressFillSRA);
// Set our state register to point to our guest thread data
ArchHelpers::Context::SetState(ucontext, reinterpret_cast<uint64_t>(Frame));
Frame->State.rip = guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_RIP];
// XXX: Full context setting
CTX->SetFlagsFromCompactedEFLAGS(Thread, guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_EFL]);
#define COPY_REG(x) \
Frame->State.gregs[FEXCore::X86State::REG_##x] = guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_##x];
COPY_REG(R8);
COPY_REG(R9);
COPY_REG(R10);
COPY_REG(R11);
COPY_REG(R12);
COPY_REG(R13);
COPY_REG(R14);
COPY_REG(R15);
COPY_REG(RDI);
COPY_REG(RSI);
COPY_REG(RBP);
COPY_REG(RBX);
COPY_REG(RDX);
COPY_REG(RAX);
COPY_REG(RCX);
COPY_REG(RSP);
#undef COPY_REG
auto *xstate = reinterpret_cast<FEXCore::x86_64::xstate*>(guest_uctx->uc_mcontext.fpregs);
auto *fpstate = &xstate->fpstate;
// Copy float registers
memcpy(Frame->State.mm, fpstate->_st, sizeof(Frame->State.mm));
if (IsAVXEnabled) {
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][0], &fpstate->_xmm[i], sizeof(__uint128_t));
}
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][2], &xstate->ymmh.ymmh_space[i], sizeof(__uint128_t));
}
} else {
memcpy(Frame->State.xmm.sse.data, fpstate->_xmm, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
Frame->State.FCW = fpstate->fcw;
Frame->State.AbridgedFTW = fpstate->ftw;
// Deconstruct FSW
Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (fpstate->fsw >> 8) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (fpstate->fsw >> 9) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (fpstate->fsw >> 10) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (fpstate->fsw >> 14) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (fpstate->fsw >> 11) & 0b111;
}
}
void SignalDelegator::RestoreFrame_ia32(FEXCore::Core::InternalThreadState *Thread, ArchHelpers::Context::ContextBackup* Context, FEXCore::Core::CpuStateFrame *Frame, void *ucontext) {
const bool IsAVXEnabled = Config.SupportsAVX;
SigFrame_i32 *guest_uctx = reinterpret_cast<SigFrame_i32*>(Context->UContextLocation);
// If the guest modified the RIP then we need to take special precautions here
if (Context->OriginalRIP != guest_uctx->sc.ip ||
Context->FaultToTopAndGeneratedException) {
// Restore previous `InSyscallInfo` structure.
Frame->InSyscallInfo = Context->InSyscallInfo;
// Hack! Go back to the top of the dispatcher top
// This is only safe inside the JIT rather than anything outside of it
ArchHelpers::Context::SetPc(ucontext, Config.AbsoluteLoopTopAddressFillSRA);
// Set our state register to point to our guest thread data
ArchHelpers::Context::SetState(ucontext, reinterpret_cast<uint64_t>(Frame));
// XXX: Full context setting
CTX->SetFlagsFromCompactedEFLAGS(Thread, guest_uctx->sc.flags);
Frame->State.rip = guest_uctx->sc.ip;
Frame->State.cs_idx = guest_uctx->sc.cs;
Frame->State.ds_idx = guest_uctx->sc.ds;
Frame->State.es_idx = guest_uctx->sc.es;
Frame->State.fs_idx = guest_uctx->sc.fs;
Frame->State.gs_idx = guest_uctx->sc.gs;
Frame->State.ss_idx = guest_uctx->sc.ss;
Frame->State.cs_cached = Frame->State.gdt[Frame->State.cs_idx >> 3].base;
Frame->State.ds_cached = Frame->State.gdt[Frame->State.ds_idx >> 3].base;
Frame->State.es_cached = Frame->State.gdt[Frame->State.es_idx >> 3].base;
Frame->State.fs_cached = Frame->State.gdt[Frame->State.fs_idx >> 3].base;
Frame->State.gs_cached = Frame->State.gdt[Frame->State.gs_idx >> 3].base;
Frame->State.ss_cached = Frame->State.gdt[Frame->State.ss_idx >> 3].base;
#define COPY_REG(x, y) \
Frame->State.gregs[FEXCore::X86State::REG_##x] = guest_uctx->sc.y;
COPY_REG(RDI, di);
COPY_REG(RSI, si);
COPY_REG(RBP, bp);
COPY_REG(RBX, bx);
COPY_REG(RDX, dx);
COPY_REG(RAX, ax);
COPY_REG(RCX, cx);
COPY_REG(RSP, sp);
#undef COPY_REG
auto *xstate = reinterpret_cast<FEXCore::x86::xstate*>(guest_uctx->sc.fpstate);
auto *fpstate = &xstate->fpstate;
// Copy float registers
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_MMS; ++i) {
// 32-bit st register size is only 10 bytes. Not padded to 16byte like x86-64
memcpy(&Frame->State.mm[i], &fpstate->_st[i], 10);
}
// Extended XMM state
if (IsAVXEnabled) {
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][0], &fpstate->_xmm[i], sizeof(__uint128_t));
}
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][2], &xstate->ymmh.ymmh_space[i], sizeof(__uint128_t));
}
} else {
memcpy(Frame->State.xmm.sse.data, fpstate->_xmm, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
Frame->State.FCW = fpstate->fcw;
Frame->State.AbridgedFTW = FEXCore::FPState::ConvertToAbridgedFTW(fpstate->ftw);
// Deconstruct FSW
Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (fpstate->fsw >> 8) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (fpstate->fsw >> 9) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (fpstate->fsw >> 10) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (fpstate->fsw >> 14) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (fpstate->fsw >> 11) & 0b111;
}
}
void SignalDelegator::RestoreRTFrame_ia32(FEXCore::Core::InternalThreadState *Thread, ArchHelpers::Context::ContextBackup* Context, FEXCore::Core::CpuStateFrame *Frame, void *ucontext) {
const bool IsAVXEnabled = Config.SupportsAVX;
RTSigFrame_i32 *guest_uctx = reinterpret_cast<RTSigFrame_i32*>(Context->UContextLocation);
// If the guest modified the RIP then we need to take special precautions here
if (Context->OriginalRIP != guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_EIP] ||
Context->FaultToTopAndGeneratedException) {
// Restore previous `InSyscallInfo` structure.
Frame->InSyscallInfo = Context->InSyscallInfo;
// Hack! Go back to the top of the dispatcher top
// This is only safe inside the JIT rather than anything outside of it
ArchHelpers::Context::SetPc(ucontext, Config.AbsoluteLoopTopAddressFillSRA);
// Set our state register to point to our guest thread data
ArchHelpers::Context::SetState(ucontext, reinterpret_cast<uint64_t>(Frame));
// XXX: Full context setting
CTX->SetFlagsFromCompactedEFLAGS(Thread, guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_EFL]);
Frame->State.rip = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_EIP];
Frame->State.cs_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_CS];
Frame->State.ds_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_DS];
Frame->State.es_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_ES];
Frame->State.fs_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_FS];
Frame->State.gs_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_GS];
Frame->State.ss_idx = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_SS];
Frame->State.cs_cached = Frame->State.gdt[Frame->State.cs_idx >> 3].base;
Frame->State.ds_cached = Frame->State.gdt[Frame->State.ds_idx >> 3].base;
Frame->State.es_cached = Frame->State.gdt[Frame->State.es_idx >> 3].base;
Frame->State.fs_cached = Frame->State.gdt[Frame->State.fs_idx >> 3].base;
Frame->State.gs_cached = Frame->State.gdt[Frame->State.gs_idx >> 3].base;
Frame->State.ss_cached = Frame->State.gdt[Frame->State.ss_idx >> 3].base;
#define COPY_REG(x) \
Frame->State.gregs[FEXCore::X86State::REG_##x] = guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_##x];
COPY_REG(RDI);
COPY_REG(RSI);
COPY_REG(RBP);
COPY_REG(RBX);
COPY_REG(RDX);
COPY_REG(RAX);
COPY_REG(RCX);
COPY_REG(RSP);
#undef COPY_REG
auto *xstate = reinterpret_cast<FEXCore::x86::xstate*>(guest_uctx->uc.uc_mcontext.fpregs);
auto *fpstate = &xstate->fpstate;
// Copy float registers
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_MMS; ++i) {
// 32-bit st register size is only 10 bytes. Not padded to 16byte like x86-64
memcpy(&Frame->State.mm[i], &fpstate->_st[i], 10);
}
// Extended XMM state
if (IsAVXEnabled) {
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][0], &fpstate->_xmm[i], sizeof(__uint128_t));
}
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&Frame->State.xmm.avx.data[i][2], &xstate->ymmh.ymmh_space[i], sizeof(__uint128_t));
}
} else {
memcpy(Frame->State.xmm.sse.data, fpstate->_xmm, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
Frame->State.FCW = fpstate->fcw;
Frame->State.AbridgedFTW = FEXCore::FPState::ConvertToAbridgedFTW(fpstate->ftw);
// Deconstruct FSW
Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] = (fpstate->fsw >> 8) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] = (fpstate->fsw >> 9) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] = (fpstate->fsw >> 10) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] = (fpstate->fsw >> 14) & 1;
Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] = (fpstate->fsw >> 11) & 0b111;
}
}
uint64_t SignalDelegator::SetupFrame_x64(
FEXCore::Core::InternalThreadState *Thread, ArchHelpers::Context::ContextBackup* ContextBackup, FEXCore::Core::CpuStateFrame *Frame,
int Signal, siginfo_t *HostSigInfo, void *ucontext,
GuestSigAction *GuestAction, stack_t *GuestStack,
uint64_t NewGuestSP, const uint32_t eflags) {
// Back up past the redzone, which is 128bytes
// 32-bit doesn't have a redzone
NewGuestSP -= 128;
const bool IsAVXEnabled = Config.SupportsAVX;
// On 64-bit the kernel sets up the siginfo_t and ucontext_t regardless of SA_SIGINFO set.
// This allows the application to /always/ get the siginfo and ucontext even if it didn't set this flag.
//
// Signal frame layout on stack needs to be as follows
// void* ReturnPointer
// ucontext_t
// siginfo_t
// FP state
// Host stack location
NewGuestSP -= sizeof(uint64_t);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(uint64_t));
uint64_t HostStackLocation = NewGuestSP;
if (IsAVXEnabled) {
NewGuestSP -= sizeof(FEXCore::x86_64::xstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86_64::xstate));
} else {
NewGuestSP -= sizeof(FEXCore::x86_64::_libc_fpstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86_64::_libc_fpstate));
}
uint64_t FPStateLocation = NewGuestSP;
NewGuestSP -= sizeof(siginfo_t);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(siginfo_t));
uint64_t SigInfoLocation = NewGuestSP;
NewGuestSP -= sizeof(FEXCore::x86_64::ucontext_t);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86_64::ucontext_t));
uint64_t UContextLocation = NewGuestSP;
ContextBackup->FPStateLocation = FPStateLocation;
ContextBackup->UContextLocation = UContextLocation;
ContextBackup->SigInfoLocation = SigInfoLocation;
FEXCore::x86_64::ucontext_t *guest_uctx = reinterpret_cast<FEXCore::x86_64::ucontext_t*>(UContextLocation);
siginfo_t *guest_siginfo = reinterpret_cast<siginfo_t*>(SigInfoLocation);
// Store where the host context lives in the guest stack.
*(uint64_t*)HostStackLocation = (uint64_t)ContextBackup;
// We have extended float information
guest_uctx->uc_flags = FEXCore::x86_64::UC_FP_XSTATE |
FEXCore::x86_64::UC_SIGCONTEXT_SS |
FEXCore::x86_64::UC_STRICT_RESTORE_SS;
// Pointer to where the fpreg memory is
guest_uctx->uc_mcontext.fpregs = reinterpret_cast<FEXCore::x86_64::_libc_fpstate*>(FPStateLocation);
auto *xstate = reinterpret_cast<FEXCore::x86_64::xstate*>(FPStateLocation);
SetXStateInfo(xstate, IsAVXEnabled);
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_RIP] = ContextBackup->OriginalRIP;
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_EFL] = eflags;
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_CSGSFS] = 0;
// aarch64 and x86_64 siginfo_t matches. We can just copy this over
// SI_USER could also potentially have random data in it, needs to be bit perfect
// For guest faults we don't have a real way to reconstruct state to a real guest RIP
*guest_siginfo = *HostSigInfo;
if (ContextBackup->FaultToTopAndGeneratedException) {
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_TRAPNO] = Frame->SynchronousFaultData.TrapNo;
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_ERR] = Frame->SynchronousFaultData.err_code;
// Overwrite si_code
guest_siginfo->si_code = Thread->CurrentFrame->SynchronousFaultData.si_code;
Signal = Frame->SynchronousFaultData.Signal;
}
else {
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_TRAPNO] = ConvertSignalToTrapNo(Signal, HostSigInfo);
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_ERR] = ConvertSignalToError(ucontext, Signal, HostSigInfo);
}
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_OLDMASK] = 0;
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_CR2] = 0;
#define COPY_REG(x) \
guest_uctx->uc_mcontext.gregs[FEXCore::x86_64::FEX_REG_##x] = Frame->State.gregs[FEXCore::X86State::REG_##x];
COPY_REG(R8);
COPY_REG(R9);
COPY_REG(R10);
COPY_REG(R11);
COPY_REG(R12);
COPY_REG(R13);
COPY_REG(R14);
COPY_REG(R15);
COPY_REG(RDI);
COPY_REG(RSI);
COPY_REG(RBP);
COPY_REG(RBX);
COPY_REG(RDX);
COPY_REG(RAX);
COPY_REG(RCX);
COPY_REG(RSP);
#undef COPY_REG
auto* fpstate = &xstate->fpstate;
// Copy float registers
memcpy(fpstate->_st, Frame->State.mm, sizeof(Frame->State.mm));
if (IsAVXEnabled) {
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&fpstate->_xmm[i], &Frame->State.xmm.avx.data[i][0], sizeof(__uint128_t));
}
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) {
memcpy(&xstate->ymmh.ymmh_space[i], &Frame->State.xmm.avx.data[i][2], sizeof(__uint128_t));
}
} else {
memcpy(fpstate->_xmm, Frame->State.xmm.sse.data, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
fpstate->fcw = Frame->State.FCW;
fpstate->ftw = Frame->State.AbridgedFTW;
// Reconstruct FSW
fpstate->fsw =
(Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] << 11) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] << 8) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] << 9) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] << 10) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] << 14);
// Copy over signal stack information
guest_uctx->uc_stack.ss_flags = GuestStack->ss_flags;
guest_uctx->uc_stack.ss_sp = GuestStack->ss_sp;
guest_uctx->uc_stack.ss_size = GuestStack->ss_size;
// Apparently RAX is always set to zero in case of badly misbehaving C applications and variadics.
Frame->State.gregs[FEXCore::X86State::REG_RAX] = 0;
Frame->State.gregs[FEXCore::X86State::REG_RDI] = Signal;
Frame->State.gregs[FEXCore::X86State::REG_RSI] = SigInfoLocation;
Frame->State.gregs[FEXCore::X86State::REG_RDX] = UContextLocation;
// Set up the new SP for stack handling
// The host is required to provide us a restorer.
// If the guest didn't provide a restorer then the application should fail with a SIGSEGV.
// TODO: Emulate SIGSEGV when the guest doesn't provide a restorer.
NewGuestSP -= 8;
if (GuestAction->restorer) {
*(uint64_t*)NewGuestSP = (uint64_t)GuestAction->restorer;
}
else {
// XXX: Emulate SIGSEGV here
// *(uint64_t*)NewGuestSP = SignalReturn;
}
return NewGuestSP;
}
uint64_t SignalDelegator::SetupFrame_ia32(
ArchHelpers::Context::ContextBackup* ContextBackup, FEXCore::Core::CpuStateFrame *Frame,
int Signal, siginfo_t *HostSigInfo, void *ucontext,
GuestSigAction *GuestAction, stack_t *GuestStack,
uint64_t NewGuestSP, const uint32_t eflags) {
const bool IsAVXEnabled = Config.SupportsAVX;
const uint64_t SignalReturn = reinterpret_cast<uint64_t>(VDSOPointers.VDSO_kernel_sigreturn);
NewGuestSP -= sizeof(uint64_t);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(uint64_t));
uint64_t HostStackLocation = NewGuestSP;
if (IsAVXEnabled) {
NewGuestSP -= sizeof(FEXCore::x86::xstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86::xstate));
} else {
NewGuestSP -= sizeof(FEXCore::x86::_libc_fpstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86::_libc_fpstate));
}
uint64_t FPStateLocation = NewGuestSP;
NewGuestSP -= sizeof(SigFrame_i32);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(SigFrame_i32));
uint64_t SigFrameLocation = NewGuestSP;
ContextBackup->FPStateLocation = FPStateLocation;
ContextBackup->UContextLocation = SigFrameLocation;
ContextBackup->SigInfoLocation = 0;
SigFrame_i32 *guest_uctx = reinterpret_cast<SigFrame_i32*>(SigFrameLocation);
// Store where the host context lives in the guest stack.
*(uint64_t*)HostStackLocation = (uint64_t)ContextBackup;
// Pointer to where the fpreg memory is
guest_uctx->sc.fpstate = static_cast<uint32_t>(FPStateLocation);
auto *xstate = reinterpret_cast<FEXCore::x86::xstate*>(FPStateLocation);
SetXStateInfo(xstate, IsAVXEnabled);
guest_uctx->sc.cs = Frame->State.cs_idx;
guest_uctx->sc.ds = Frame->State.ds_idx;
guest_uctx->sc.es = Frame->State.es_idx;
guest_uctx->sc.fs = Frame->State.fs_idx;
guest_uctx->sc.gs = Frame->State.gs_idx;
guest_uctx->sc.ss = Frame->State.ss_idx;
if (ContextBackup->FaultToTopAndGeneratedException) {
guest_uctx->sc.trapno = Frame->SynchronousFaultData.TrapNo;
guest_uctx->sc.err = Frame->SynchronousFaultData.err_code;
Signal = Frame->SynchronousFaultData.Signal;
}
else {
guest_uctx->sc.trapno = ConvertSignalToTrapNo(Signal, HostSigInfo);
guest_uctx->sc.err = ConvertSignalToError(ucontext, Signal, HostSigInfo);
}
guest_uctx->sc.ip = ContextBackup->OriginalRIP;
guest_uctx->sc.flags = eflags;
guest_uctx->sc.sp_at_signal = 0;
#define COPY_REG(x, y) \
guest_uctx->sc.x = Frame->State.gregs[FEXCore::X86State::REG_##y];
COPY_REG(di, RDI);
COPY_REG(si, RSI);
COPY_REG(bp, RBP);
COPY_REG(bx, RBX);
COPY_REG(dx, RDX);
COPY_REG(ax, RAX);
COPY_REG(cx, RCX);
COPY_REG(sp, RSP);
#undef COPY_REG
auto *fpstate = &xstate->fpstate;
// Copy float registers
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_MMS; ++i) {
// 32-bit st register size is only 10 bytes. Not padded to 16byte like x86-64
memcpy(&fpstate->_st[i], &Frame->State.mm[i], 10);
}
// Extended XMM state
fpstate->status = FEXCore::x86::fpstate_magic::MAGIC_XFPSTATE;
if (IsAVXEnabled) {
for (size_t i = 0; i < std::size(Frame->State.xmm.avx.data); i++) {
memcpy(&fpstate->_xmm[i], &Frame->State.xmm.avx.data[i][0], sizeof(__uint128_t));
}
for (size_t i = 0; i < std::size(Frame->State.xmm.avx.data); i++) {
memcpy(&xstate->ymmh.ymmh_space[i], &Frame->State.xmm.avx.data[i][2], sizeof(__uint128_t));
}
} else {
memcpy(fpstate->_xmm, Frame->State.xmm.sse.data, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
fpstate->fcw = Frame->State.FCW;
// Reconstruct FSW
fpstate->fsw =
(Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] << 11) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] << 8) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] << 9) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] << 10) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] << 14);
fpstate->ftw = FEXCore::FPState::ConvertFromAbridgedFTW(fpstate->fsw, Frame->State.mm, Frame->State.AbridgedFTW);
// Curiously non-rt signals don't support altstack. So that state doesn't exist here.
// Copy over the signal information.
guest_uctx->Signal = Signal;
// Retcode needs to be bit-exact for debuggers
constexpr static uint8_t retcode[] = {
0x58, // pop eax
0xb8, // mov
0x77, 0x00, 0x00, 0x00, // 32-bit sigreturn
0xcd, 0x80, // int 0x80
};
memcpy(guest_uctx->retcode, &retcode, sizeof(retcode));
// 32-bit Guest can provide its own restorer or we need to provide our own.
// On a real host this restorer will live in VDSO.
constexpr uint32_t SA_RESTORER = 0x04000000;
const bool HasRestorer = (GuestAction->sa_flags & SA_RESTORER) == SA_RESTORER;
if (HasRestorer) {
guest_uctx->pretcode = (uint32_t)(uint64_t)GuestAction->restorer;
}
else {
guest_uctx->pretcode = SignalReturn;
LOGMAN_THROW_AA_FMT(SignalReturn < 0x1'0000'0000ULL, "This needs to be below 4GB");
}
// Support regparm=3
Frame->State.gregs[FEXCore::X86State::REG_RAX] = Signal;
Frame->State.gregs[FEXCore::X86State::REG_RDX] = 0;
Frame->State.gregs[FEXCore::X86State::REG_RCX] = 0;
return NewGuestSP;
}
uint64_t SignalDelegator::SetupRTFrame_ia32(
ArchHelpers::Context::ContextBackup* ContextBackup, FEXCore::Core::CpuStateFrame *Frame,
int Signal, siginfo_t *HostSigInfo, void *ucontext,
GuestSigAction *GuestAction, stack_t *GuestStack,
uint64_t NewGuestSP, const uint32_t eflags) {
const bool IsAVXEnabled = Config.SupportsAVX;
const uint64_t SignalReturn = reinterpret_cast<uint64_t>(VDSOPointers.VDSO_kernel_rt_sigreturn);
NewGuestSP -= sizeof(uint64_t);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(uint64_t));
uint64_t HostStackLocation = NewGuestSP;
if (IsAVXEnabled) {
NewGuestSP -= sizeof(FEXCore::x86::xstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86::xstate));
} else {
NewGuestSP -= sizeof(FEXCore::x86::_libc_fpstate);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(FEXCore::x86::_libc_fpstate));
}
uint64_t FPStateLocation = NewGuestSP;
NewGuestSP -= sizeof(RTSigFrame_i32);
NewGuestSP = FEXCore::AlignDown(NewGuestSP, alignof(RTSigFrame_i32));
uint64_t SigFrameLocation = NewGuestSP;
RTSigFrame_i32 *guest_uctx = reinterpret_cast<RTSigFrame_i32*>(SigFrameLocation);
// Store where the host context lives in the guest stack.
*(uint64_t*)HostStackLocation = (uint64_t)ContextBackup;
ContextBackup->FPStateLocation = FPStateLocation;
ContextBackup->UContextLocation = SigFrameLocation;
ContextBackup->SigInfoLocation = 0; // Part of frame.
// We have extended float information
guest_uctx->uc.uc_flags = FEXCore::x86::UC_FP_XSTATE;
guest_uctx->uc.uc_link = 0;
// Pointer to where the fpreg memory is
guest_uctx->uc.uc_mcontext.fpregs = static_cast<uint32_t>(FPStateLocation);
auto *xstate = reinterpret_cast<FEXCore::x86::xstate*>(FPStateLocation);
SetXStateInfo(xstate, IsAVXEnabled);
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_CS] = Frame->State.cs_idx;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_DS] = Frame->State.ds_idx;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_ES] = Frame->State.es_idx;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_FS] = Frame->State.fs_idx;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_GS] = Frame->State.gs_idx;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_SS] = Frame->State.ss_idx;
if (ContextBackup->FaultToTopAndGeneratedException) {
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_TRAPNO] = Frame->SynchronousFaultData.TrapNo;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_ERR] = Frame->SynchronousFaultData.err_code;
Signal = Frame->SynchronousFaultData.Signal;
}
else {
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_TRAPNO] = ConvertSignalToTrapNo(Signal, HostSigInfo);
guest_uctx->info.si_code = HostSigInfo->si_code;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_ERR] = ConvertSignalToError(ucontext, Signal, HostSigInfo);
}
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_EIP] = ContextBackup->OriginalRIP;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_EFL] = eflags;
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_UESP] = Frame->State.gregs[FEXCore::X86State::REG_RSP];
guest_uctx->uc.uc_mcontext.cr2 = 0;
#define COPY_REG(x) \
guest_uctx->uc.uc_mcontext.gregs[FEXCore::x86::FEX_REG_##x] = Frame->State.gregs[FEXCore::X86State::REG_##x];
COPY_REG(RDI);
COPY_REG(RSI);
COPY_REG(RBP);
COPY_REG(RBX);
COPY_REG(RDX);
COPY_REG(RAX);
COPY_REG(RCX);
COPY_REG(RSP);
#undef COPY_REG
auto *fpstate = &xstate->fpstate;
// Copy float registers
for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_MMS; ++i) {
// 32-bit st register size is only 10 bytes. Not padded to 16byte like x86-64
memcpy(&fpstate->_st[i], &Frame->State.mm[i], 10);
}
// Extended XMM state
fpstate->status = FEXCore::x86::fpstate_magic::MAGIC_XFPSTATE;
if (IsAVXEnabled) {
for (size_t i = 0; i < std::size(Frame->State.xmm.avx.data); i++) {
memcpy(&fpstate->_xmm[i], &Frame->State.xmm.avx.data[i][0], sizeof(__uint128_t));
}
for (size_t i = 0; i < std::size(Frame->State.xmm.avx.data); i++) {
memcpy(&xstate->ymmh.ymmh_space[i], &Frame->State.xmm.avx.data[i][2], sizeof(__uint128_t));
}
} else {
memcpy(fpstate->_xmm, Frame->State.xmm.sse.data, sizeof(Frame->State.xmm.sse.data));
}
// FCW store default
fpstate->fcw = Frame->State.FCW;
// Reconstruct FSW
fpstate->fsw =
(Frame->State.flags[FEXCore::X86State::X87FLAG_TOP_LOC] << 11) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C0_LOC] << 8) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C1_LOC] << 9) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C2_LOC] << 10) |
(Frame->State.flags[FEXCore::X86State::X87FLAG_C3_LOC] << 14);
fpstate->ftw = FEXCore::FPState::ConvertFromAbridgedFTW(fpstate->fsw, Frame->State.mm, Frame->State.AbridgedFTW);
// Copy over signal stack information
guest_uctx->uc.uc_stack.ss_flags = GuestStack->ss_flags;
guest_uctx->uc.uc_stack.ss_sp = static_cast<uint32_t>(reinterpret_cast<uint64_t>(GuestStack->ss_sp));
guest_uctx->uc.uc_stack.ss_size = GuestStack->ss_size;
// Setup siginfo
if (ContextBackup->FaultToTopAndGeneratedException) {
guest_uctx->info.si_code = Frame->SynchronousFaultData.si_code;
}
else {
guest_uctx->info.si_code = HostSigInfo->si_code;
}
// These three elements are in every siginfo
guest_uctx->info.si_signo = HostSigInfo->si_signo;
guest_uctx->info.si_errno = HostSigInfo->si_errno;
const SigInfoLayout Layout = CalculateSigInfoLayout(Signal, guest_uctx->info.si_code);
switch (Layout) {
case SigInfoLayout::LAYOUT_KILL:
guest_uctx->info._sifields._kill.pid = HostSigInfo->si_pid;
guest_uctx->info._sifields._kill.uid = HostSigInfo->si_uid;
break;
case SigInfoLayout::LAYOUT_TIMER:
guest_uctx->info._sifields._timer.tid = HostSigInfo->si_timerid;
guest_uctx->info._sifields._timer.overrun = HostSigInfo->si_overrun;
guest_uctx->info._sifields._timer.sigval.sival_int = HostSigInfo->si_int;
break;
case SigInfoLayout::LAYOUT_POLL:
guest_uctx->info._sifields._poll.band= HostSigInfo->si_band;
guest_uctx->info._sifields._poll.fd= HostSigInfo->si_fd;
break;
case SigInfoLayout::LAYOUT_FAULT:
// Macro expansion to get the si_addr
// This is the address trying to be accessed, not the RIP
guest_uctx->info._sifields._sigfault.addr = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(HostSigInfo->si_addr));
break;
case SigInfoLayout::LAYOUT_FAULT_RIP:
// Macro expansion to get the si_addr
// Can't really give a real result here. Pull from the context for now
guest_uctx->info._sifields._sigfault.addr = ContextBackup->OriginalRIP;
break;
case SigInfoLayout::LAYOUT_CHLD:
guest_uctx->info._sifields._sigchld.pid = HostSigInfo->si_pid;
guest_uctx->info._sifields._sigchld.uid = HostSigInfo->si_uid;
guest_uctx->info._sifields._sigchld.status = HostSigInfo->si_status;
guest_uctx->info._sifields._sigchld.utime = HostSigInfo->si_utime;
guest_uctx->info._sifields._sigchld.stime = HostSigInfo->si_stime;
break;
case SigInfoLayout::LAYOUT_RT:
guest_uctx->info._sifields._rt.pid = HostSigInfo->si_pid;
guest_uctx->info._sifields._rt.uid = HostSigInfo->si_uid;
guest_uctx->info._sifields._rt.sigval.sival_int = HostSigInfo->si_int;
break;
case SigInfoLayout::LAYOUT_SYS:
guest_uctx->info._sifields._sigsys.call_addr = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(HostSigInfo->si_call_addr));
guest_uctx->info._sifields._sigsys.syscall = HostSigInfo->si_syscall;
// We need to lie about the architecture here.
// Otherwise we would expose incorrect information to the guest.
constexpr uint32_t AUDIT_LE = 0x4000'0000U;
constexpr uint32_t MACHINE_I386 = 3; // This matches the ELF definition.
guest_uctx->info._sifields._sigsys.arch = AUDIT_LE | MACHINE_I386;
break;
}
// Setup the guest stack context.
guest_uctx->Signal = Signal;
guest_uctx->pinfo = (uint32_t)(uint64_t)&guest_uctx->info;
guest_uctx->puc = (uint32_t)(uint64_t)&guest_uctx->uc;
// Retcode needs to be bit-exact for debuggers
constexpr static uint8_t rt_retcode[] = {
0xb8, // mov
0xad, 0x00, 0x00, 0x00, // 32-bit rt_sigreturn
0xcd, 0x80, // int 0x80
0x0, // Pad
};
memcpy(guest_uctx->retcode, &rt_retcode, sizeof(rt_retcode));
// 32-bit Guest can provide its own restorer or we need to provide our own.
// On a real host this restorer will live in VDSO.
constexpr uint32_t SA_RESTORER = 0x04000000;
const bool HasRestorer = (GuestAction->sa_flags & SA_RESTORER) == SA_RESTORER;
if (HasRestorer) {
guest_uctx->pretcode = (uint32_t)(uint64_t)GuestAction->restorer;
}
else {
guest_uctx->pretcode = SignalReturn;
LOGMAN_THROW_AA_FMT(SignalReturn < 0x1'0000'0000ULL, "This needs to be below 4GB");
}
// Support regparm=3
Frame->State.gregs[FEXCore::X86State::REG_RAX] = Signal;
Frame->State.gregs[FEXCore::X86State::REG_RDX] = guest_uctx->pinfo;
Frame->State.gregs[FEXCore::X86State::REG_RCX] = guest_uctx->puc;
return NewGuestSP;
}
bool SignalDelegator::HandleDispatcherGuestSignal(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext, GuestSigAction *GuestAction, stack_t *GuestStack) {
auto ContextBackup = StoreThreadState(Thread, Signal, ucontext);
auto Frame = Thread->CurrentFrame;
// Ref count our faults
// We use this to track if it is safe to clear cache
++Thread->CurrentFrame->SignalHandlerRefCounter;
uint64_t OldPC = ArchHelpers::Context::GetPc(ucontext);
const bool WasInJIT = Thread->CPUBackend->IsAddressInCodeBuffer(OldPC);
// Spill the SRA regardless of signal handler type
// We are going to be returning to the top of the dispatcher which will fill again
// Otherwise we might load garbage
if (WasInJIT) {
uint32_t IgnoreMask{};
#ifdef _M_ARM_64
if (Frame->InSyscallInfo != 0) {
// We are in a syscall, this means we are in a weird register state
// We need to spill SRA but only some of it, since some values have already been spilled
// Lower 16 bits tells us which registers are already spilled to the context
// So we ignore spilling those ones
IgnoreMask = Frame->InSyscallInfo & 0xFFFF;
}
else {
// We must spill everything
IgnoreMask = 0;
}
#endif
// We are in jit, SRA must be spilled
SpillSRA(Thread, ucontext, IgnoreMask);
ContextBackup->Flags |= ArchHelpers::Context::ContextFlags::CONTEXT_FLAG_INJIT;
// We are leaving the syscall information behind. Make sure to store the previous state.
ContextBackup->InSyscallInfo = Thread->CurrentFrame->InSyscallInfo;
Thread->CurrentFrame->InSyscallInfo = 0;
} else {
if (!IsAddressInDispatcher(OldPC)) {
// This is likely to cause issues but in some cases it isn't fatal
// This can also happen if we have put a signal on hold, then we just reenabled the signal
// So we are in the syscall handler
// Only throw a log message in this case
if constexpr (false) {
// XXX: Messages in the signal handler can cause us to crash
LogMan::Msg::EFmt("Signals in dispatcher have unsynchronized context");
}
}
}
uint64_t OldGuestSP = Frame->State.gregs[FEXCore::X86State::REG_RSP];
uint64_t NewGuestSP = OldGuestSP;
// altstack is only used if the signal handler was setup with SA_ONSTACK
if (GuestAction->sa_flags & SA_ONSTACK) {
// Additionally the altstack is only used if the enabled (SS_DISABLE flag is not set)
if (!(GuestStack->ss_flags & SS_DISABLE)) {
// If our guest is already inside of the alternative stack
// Then that means we are hitting recursive signals and we need to walk back the stack correctly
uint64_t AltStackBase = reinterpret_cast<uint64_t>(GuestStack->ss_sp);
uint64_t AltStackEnd = AltStackBase + GuestStack->ss_size;
if (OldGuestSP >= AltStackBase &&
OldGuestSP <= AltStackEnd) {
// We are already in the alt stack, the rest of the code will handle adjusting this
}
else {
NewGuestSP = AltStackEnd;
}
}
}
// siginfo_t
siginfo_t *HostSigInfo = reinterpret_cast<siginfo_t*>(info);
// Backup where we think the RIP currently is
ContextBackup->OriginalRIP = CTX->RestoreRIPFromHostPC(Thread, ArchHelpers::Context::GetPc(ucontext));
// Calculate eflags upfront.
uint32_t eflags = CTX->ReconstructCompactedEFLAGS(Thread, WasInJIT, ArchHelpers::Context::GetArmGPRs(ucontext), ArchHelpers::Context::GetArmPState(ucontext));
if (Is64BitMode) {
NewGuestSP = SetupFrame_x64(Thread, ContextBackup, Frame, Signal, HostSigInfo, ucontext, GuestAction, GuestStack, NewGuestSP, eflags);
}
else {
const bool SigInfoFrame = (GuestAction->sa_flags & SA_SIGINFO) == SA_SIGINFO;
if (SigInfoFrame) {
NewGuestSP = SetupRTFrame_ia32(ContextBackup, Frame, Signal, HostSigInfo, ucontext, GuestAction, GuestStack, NewGuestSP, eflags);
}
else {
NewGuestSP = SetupFrame_ia32(ContextBackup, Frame, Signal, HostSigInfo, ucontext, GuestAction, GuestStack, NewGuestSP, eflags);
}
}
Frame->State.rip = reinterpret_cast<uint64_t>(GuestAction->sigaction_handler.sigaction);
Frame->State.gregs[FEXCore::X86State::REG_RSP] = NewGuestSP;
// The guest starts its signal frame with a zero initialized FPU
// Set that up now. Little bit costly but it's a requirement
// This state will be restored on rt_sigreturn
memset(Frame->State.xmm.avx.data, 0, sizeof(Frame->State.xmm));
memset(Frame->State.mm, 0, sizeof(Frame->State.mm));
Frame->State.FCW = 0x37F;
Frame->State.AbridgedFTW = 0;
// Set the new PC
ArchHelpers::Context::SetPc(ucontext, Config.AbsoluteLoopTopAddressFillSRA);
// Set our state register to point to our guest thread data
ArchHelpers::Context::SetState(ucontext, reinterpret_cast<uint64_t>(Frame));
return true;
}
bool SignalDelegator::HandleSIGILL(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) {
if (ArchHelpers::Context::GetPc(ucontext) == Config.SignalHandlerReturnAddress ||
ArchHelpers::Context::GetPc(ucontext) == Config.SignalHandlerReturnAddressRT) {
RestoreThreadState(Thread, ucontext,
ArchHelpers::Context::GetPc(ucontext) == Config.SignalHandlerReturnAddressRT ? RestoreType::TYPE_REALTIME : RestoreType::TYPE_NONREALTIME);
// Ref count our faults
// We use this to track if it is safe to clear cache
--Thread->CurrentFrame->SignalHandlerRefCounter;
if (Thread->DeferredSignalFrames.size() != 0) {
// If we have more deferred frames to process then mprotect back to PROT_NONE.
// It will have been RW coming in to this sigreturn and now we need to remove permissions
// to ensure FEX trampolines back to the SIGSEGV deferred handler.
mprotect(reinterpret_cast<void*>(Thread->CurrentFrame->State.DeferredSignalFaultAddress), 4096, PROT_NONE);
}
return true;
}
if (ArchHelpers::Context::GetPc(ucontext) == Config.PauseReturnInstruction) {
RestoreThreadState(Thread, ucontext, RestoreType::TYPE_PAUSE);
// Ref count our faults
// We use this to track if it is safe to clear cache
--Thread->CurrentFrame->SignalHandlerRefCounter;
return true;
}
return false;
}
bool SignalDelegator::HandleSignalPause(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) {
FEXCore::Core::SignalEvent SignalReason = Thread->SignalReason.load();
auto Frame = Thread->CurrentFrame;
if (SignalReason == FEXCore::Core::SignalEvent::Pause) {
// Store our thread state so we can come back to this
StoreThreadState(Thread, Signal, ucontext);
if (Thread->CPUBackend->IsAddressInCodeBuffer(ArchHelpers::Context::GetPc(ucontext))) {
// We are in jit, SRA must be spilled
ArchHelpers::Context::SetPc(ucontext, Config.ThreadPauseHandlerAddressSpillSRA);
} else {
// We are in non-jit, SRA is already spilled
LOGMAN_THROW_A_FMT(!IsAddressInDispatcher(ArchHelpers::Context::GetPc(ucontext)),
"Signals in dispatcher have unsynchronized context");
ArchHelpers::Context::SetPc(ucontext, Config.ThreadPauseHandlerAddress);
}
// Set our state register to point to our guest thread data
ArchHelpers::Context::SetState(ucontext, reinterpret_cast<uint64_t>(Frame));
// Ref count our faults
// We use this to track if it is safe to clear cache
++Thread->CurrentFrame->SignalHandlerRefCounter;
Thread->SignalReason.store(FEXCore::Core::SignalEvent::Nothing);
return true;
}
if (SignalReason == FEXCore::Core::SignalEvent::Stop) {
// Our thread is stopping
// We don't care about anything at this point
// Set the stack to our starting location when we entered the core and get out safely
ArchHelpers::Context::SetSp(ucontext, Frame->ReturningStackLocation);
// Our ref counting doesn't matter anymore
Thread->CurrentFrame->SignalHandlerRefCounter = 0;
// Set the new PC
if (Thread->CPUBackend->IsAddressInCodeBuffer(ArchHelpers::Context::GetPc(ucontext))) {
// We are in jit, SRA must be spilled
ArchHelpers::Context::SetPc(ucontext, Config.ThreadStopHandlerAddressSpillSRA);
} else {
// We are in non-jit, SRA is already spilled
LOGMAN_THROW_A_FMT(!IsAddressInDispatcher(ArchHelpers::Context::GetPc(ucontext)),
"Signals in dispatcher have unsynchronized context");
ArchHelpers::Context::SetPc(ucontext, Config.ThreadStopHandlerAddress);
}
// We need to be a little bit careful here
// If we were already paused (due to GDB) and we are immediately stopping (due to gdb kill)
// Then we need to ensure we don't double decrement our idle thread counter
if (Thread->RunningEvents.ThreadSleeping) {
// If the thread was sleeping then its idle counter was decremented
// Reincrement it here to not break logic
FEX::HLE::_SyscallHandler->TM.IncrementIdleRefCount();
}
Thread->SignalReason.store(FEXCore::Core::SignalEvent::Nothing);
return true;
}
if (SignalReason == FEXCore::Core::SignalEvent::Return ||
SignalReason == FEXCore::Core::SignalEvent::ReturnRT) {
RestoreThreadState(Thread, ucontext, SignalReason == FEXCore::Core::SignalEvent::ReturnRT ? RestoreType::TYPE_REALTIME : RestoreType::TYPE_NONREALTIME);
// Ref count our faults
// We use this to track if it is safe to clear cache
--Thread->CurrentFrame->SignalHandlerRefCounter;
Thread->SignalReason.store(FEXCore::Core::SignalEvent::Nothing);
return true;
}
return false;
}
void SignalDelegator::SignalThread(FEXCore::Core::InternalThreadState *Thread, FEXCore::Core::SignalEvent Event) {
if (Event == FEXCore::Core::SignalEvent::Pause &&
Thread->RunningEvents.Running.load() == false) {
// Skip signaling a thread if it is already paused.
return;
}
Thread->SignalReason.store(Event);
FHU::Syscalls::tgkill(Thread->ThreadManager.PID, Thread->ThreadManager.TID, SignalDelegator::SIGNAL_FOR_PAUSE);
}
/** @} */
static bool IsAsyncSignal(const siginfo_t* Info, int Signal) {
if (Info->si_code <= SI_USER) {
// If the signal is not from the kernel then it is always async.
// This is because synchronous signals can be sent through tgkill,sigqueue and other methods.
// SI_USER == 0 and all negative si_code values come from the user.
return true;
}
else {
// If the signal is from the kernel then it is async only if it isn't an explicit synchronous signal.
switch (Signal) {
// These are all synchronous signals.
case SIGBUS:
case SIGFPE:
case SIGILL:
case SIGSEGV:
case SIGTRAP:
return false;
default: break;
}
}
// Everything else is async and can be deferred.
return true;
}
void SignalDelegator::HandleGuestSignal(FEXCore::Core::InternalThreadState *Thread, int Signal, void *Info, void *UContext) {
ucontext_t* _context = (ucontext_t*)UContext;
auto SigInfo = *static_cast<siginfo_t*>(Info);
constexpr bool SupportDeferredSignals = true;
if (SupportDeferredSignals) {
auto MustDeferSignal = (Thread->CurrentFrame->State.DeferredSignalRefCount.Load() != 0);
if (Signal == SIGSEGV &&
SigInfo.si_code == SEGV_ACCERR &&
SigInfo.si_addr == reinterpret_cast<void*>(Thread->CurrentFrame->State.DeferredSignalFaultAddress)) {
if (!MustDeferSignal) {
// We just reached the end of the outermost signal-deferring section and faulted to check for pending signals.
// Pull a signal frame off the stack.
mprotect(reinterpret_cast<void*>(Thread->CurrentFrame->State.DeferredSignalFaultAddress), 4096, PROT_READ | PROT_WRITE);
if (Thread->DeferredSignalFrames.empty()) {
// No signals to defer. Just set the fault page back to RW and continue execution.
// This occurs as a minor race condition between the refcount decrement and the access to the fault page.
return;
}
auto Top = Thread->DeferredSignalFrames.back();
Signal = Top.Signal;
SigInfo = Top.Info;
Thread->DeferredSignalFrames.pop_back();
// Until we re-protect the page to PROT_NONE, FEX will now *permanently* defer signals and /not/ check them.
//
// In order to return /back/ to a sane state, we wait for the rt_sigreturn to happen.
// rt_sigreturn will check if there are any more deferred signals to handle
// - If there are deferred signals
// - mprotect back to PROT_NONE
// - sigreturn will trampoline out to the previous fault address check, SIGSEGV and restart
// - If there are *no* deferred signals
// - No need to mprotect, it is already RW
}
else {
#ifdef _M_ARM_64
// If RefCount != 0 then that means we hit an access with nested signal-deferring sections.
// Increment the PC past the `str zr, [x1]` to continue code execution until we reach the outermost section.
ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetPc(UContext) + 4);
return;
#else
// X86 should always be doing a refcount compare and branch since we can't guarantee instruction size.
// ARM64 just always does the access to reduce branching overhead.
ERROR_AND_DIE_FMT("X86 shouldn't hit this DeferredSignalFaultAddress");
#endif
}
}
else if (Signal == SIGSEGV &&
SigInfo.si_code == SEGV_ACCERR &&
FaultSafeMemcpy::IsFaultLocation(ArchHelpers::Context::GetPc(UContext))) {
// If you want to emulate EFAULT behaviour then enable this if-statement.
// Do this once we find an application that depends on this.
if constexpr (false) {
// Return from the subroutine, returning EFAULT.
ArchHelpers::Context::SetArmReg(UContext, 0, EFAULT);
ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetArmReg(UContext, 30));
return;
}
else {
LogMan::Msg::AFmt("Received invalid data to syscall. Crashing now!");
}
}
else {
if (IsAsyncSignal(&SigInfo, Signal) && MustDeferSignal) {
// If the signal is asynchronous (as determined by si_code) and FEX is in a state of needing
// to defer the signal, then add the signal to the thread's signal queue.
LOGMAN_THROW_A_FMT(Thread->DeferredSignalFrames.size() != Thread->DeferredSignalFrames.capacity(),
"Deferred signals vector hit capacity size. This will likely crash! Asserting now!");
Thread->DeferredSignalFrames.emplace_back(FEXCore::Core::InternalThreadState::DeferredSignalState {
.Info = SigInfo,
.Signal = Signal,
});
// Now update the faulting page permissions so it will fault on write.
mprotect(reinterpret_cast<void*>(Thread->CurrentFrame->State.DeferredSignalFaultAddress), 4096, PROT_NONE);
// Postpone the remainder of signal handling logic until we process the SIGSEGV triggered by writing to DeferredSignalFaultAddress.
return;
}
}
}
// Let the host take first stab at handling the signal
SignalHandler &Handler = HostHandlers[Signal];
// Remove the pending signal
ThreadData.PendingSignals &= ~(1ULL << (Signal - 1));
// We have an emulation thread pointer, we can now modify its state
if (Handler.GuestAction.sigaction_handler.handler == SIG_DFL) {
if (Handler.DefaultBehaviour == DEFAULT_TERM ||
Handler.DefaultBehaviour == DEFAULT_COREDUMP) {
// Let the signal fall through to the unhandled path
// This way the parent process can know it died correctly
}
}
else if (Handler.GuestAction.sigaction_handler.handler == SIG_IGN) {
return;
}
else {
if (Handler.GuestHandler &&
Handler.GuestHandler(Thread, Signal, &SigInfo, UContext, &Handler.GuestAction, &ThreadData.GuestAltStack)) {
// Set up a new mask based on this signals signal mask
uint64_t NewMask = Handler.GuestAction.sa_mask.Val;
// If NODEFER then the new signal mask includes this signal
if (!(Handler.GuestAction.sa_flags & SA_NODEFER)) {
NewMask |= (1ULL << (Signal - 1));
}
// Walk our required signals and stop masking them if requested
for (size_t i = 0; i < MAX_SIGNALS; ++i) {
if (HostHandlers[i + 1].Required.load(std::memory_order_relaxed)) {
// Never mask our required signals
NewMask &= ~(1ULL << i);
}
}
// Update our host signal mask so we don't hit race conditions with signals
// This allows us to maintain the expected signal mask through the guest signal handling and then all the way back again
memcpy(&_context->uc_sigmask, &NewMask, sizeof(uint64_t));
// We handled this signal, continue running
return;
}
ERROR_AND_DIE_FMT("Unhandled guest exception");
}
// Unhandled crash
// Call back in to the previous handler
if (Handler.OldAction.sa_flags & SA_SIGINFO) {
Handler.OldAction.sigaction(Signal, &SigInfo, UContext);
}
else if (Handler.OldAction.handler == SIG_IGN ||
(Handler.OldAction.handler == SIG_DFL &&
Handler.DefaultBehaviour == DEFAULT_IGNORE)) {
// Do nothing
}
else if (Handler.OldAction.handler == SIG_DFL &&
(Handler.DefaultBehaviour == DEFAULT_COREDUMP ||
Handler.DefaultBehaviour == DEFAULT_TERM)) {
#ifndef FEX_DISABLE_TELEMETRY
// In the case of signals that cause coredump or terminate, save telemetry early.
// FEX is hard crashing at this point and won't hit regular shutdown routines.
// Add the signal to the crash mask.
CrashMask |= (1ULL << Signal);
if (Signal == SIGSEGV &&
reinterpret_cast<uint64_t>(SigInfo.si_addr) >= SyscallHandler::TASK_MAX_64BIT) {
// Tried accessing invalid non-canonical x86-64 address.
UnhandledNonCanonical = true;
}
SaveTelemetry();
#endif
// Reassign back to DFL and crash
signal(Signal, SIG_DFL);
if (SigInfo.si_code != SI_KERNEL) {
// If the signal wasn't sent by the kernel then we need to reraise it.
// This is necessary since returning from this signal handler now might just continue executing.
// eg: If sent from tgkill then the signal gets dropped and returns.
FHU::Syscalls::tgkill(::getpid(), FHU::Syscalls::gettid(), Signal);
}
}
else {
Handler.OldAction.handler(Signal);
}
}
void SignalDelegator::SaveTelemetry() {
#ifndef FEX_DISABLE_TELEMETRY
if (!ApplicationName.empty()) {
FEXCore::Telemetry::Shutdown(ApplicationName);
}
#endif
}
bool SignalDelegator::InstallHostThunk(int Signal) {
SignalHandler &SignalHandler = HostHandlers[Signal];
// If the host thunk is already installed for this, just return
if (SignalHandler.Installed) {
return false;
}
// Default flags for us
SignalHandler.HostAction.sa_flags = SA_SIGINFO | SA_ONSTACK;
bool Result = UpdateHostThunk(Signal);
SignalHandler.Installed = Result;
return Result;
}
bool SignalDelegator::UpdateHostThunk(int Signal) {
SignalHandler &SignalHandler = HostHandlers[Signal];
// Now install the thunk handler
SignalHandler.HostAction.sigaction = SignalHandlerThunk;
auto CheckAndAddFlags = [](uint64_t HostFlags, uint64_t GuestFlags, uint64_t Flags) {
// If any of the flags don't match then update to the newest set
if ((HostFlags ^ GuestFlags) & Flags) {
// Remove all the flags from the host that we are testing for
HostFlags &= ~Flags;
// Copy over the guest flags being set
HostFlags |= GuestFlags & Flags;
}
return HostFlags;
};
// Don't allow the guest to override flags for
// SA_SIGINFO : Host always needs SA_SIGINFO
// SA_ONSTACK : Host always needs the altstack
// SA_RESETHAND : We don't support one shot handlers
// SA_RESTORER : We always need our host side restorer on x86-64, Couldn't use guest restorer anyway
SignalHandler.HostAction.sa_flags = CheckAndAddFlags(
SignalHandler.HostAction.sa_flags,
SignalHandler.GuestAction.sa_flags,
SA_NOCLDSTOP | SA_NOCLDWAIT | SA_NODEFER | SA_RESTART);
#ifdef _M_X86_64
#define SA_RESTORER 0x04000000
SignalHandler.HostAction.sa_flags |= SA_RESTORER;
SignalHandler.HostAction.restorer = sigrestore;
#endif
// Walk the signals we have that are required and make sure to remove it from the mask
// This'll likely be SIGILL, SIGBUS, SIG63
// If the guest has masked some signals then we need to also mask those signals
for (size_t i = 1; i < HostHandlers.size(); ++i) {
if (HostHandlers[i].Required.load(std::memory_order_relaxed)) {
SignalHandler.HostAction.sa_mask &= ~(1ULL << (i - 1));
}
else if (SigIsMember(&SignalHandler.GuestAction.sa_mask, i)) {
SignalHandler.HostAction.sa_mask |= (1ULL << (i - 1));
}
}
// Check for SIG_IGN
if (SignalHandler.GuestAction.sigaction_handler.handler == SIG_IGN &&
HostHandlers[Signal].Required.load(std::memory_order_relaxed) == false) {
// We are ignoring this signal on the guest
// Which means we need to ignore it on the host as well
SignalHandler.HostAction.handler = SIG_IGN;
}
// Check for SIG_DFL
if (SignalHandler.GuestAction.sigaction_handler.handler == SIG_DFL &&
HostHandlers[Signal].Required.load(std::memory_order_relaxed) == false) {
// Default handler on guest and default handler on host
// With coredump and terminate then expect fireworks, but that is what the guest wants
SignalHandler.HostAction.handler = SIG_DFL;
}
// Only update the old action if we haven't ever been installed
const int Result = ::syscall(SYS_rt_sigaction, Signal, &SignalHandler.HostAction, SignalHandler.Installed ? nullptr : &SignalHandler.OldAction, 8);
if (Result < 0) {
// Signal 32 and 33 are consumed by glibc. We don't handle this atm
LogMan::Msg::AFmt("Failed to install host signal thunk for signal {}: {}", Signal, strerror(errno));
return false;
}
return true;
}
void SignalDelegator::UninstallHostHandler(int Signal) {
SignalHandler &SignalHandler = HostHandlers[Signal];
::syscall(SYS_rt_sigaction, Signal, &SignalHandler.OldAction, nullptr, 8);
}
SignalDelegator::SignalDelegator(FEXCore::Context::Context *_CTX, const std::string_view ApplicationName)
: CTX {_CTX}
, ApplicationName {ApplicationName} {
// Register this delegate
LOGMAN_THROW_AA_FMT(!GlobalDelegator, "Can't register global delegator multiple times!");
GlobalDelegator = this;
// Signal zero isn't real
HostHandlers[0].Installed = true;
// We can't capture SIGKILL or SIGSTOP
HostHandlers[SIGKILL].Installed = true;
HostHandlers[SIGSTOP].Installed = true;
// Most signals default to termination
// These ones are slightly different
static constexpr std::array<std::pair<int, SignalDelegator::DefaultBehaviour>, 14> SignalDefaultBehaviours = {{
{SIGQUIT, DEFAULT_COREDUMP},
{SIGILL, DEFAULT_COREDUMP},
{SIGTRAP, DEFAULT_COREDUMP},
{SIGABRT, DEFAULT_COREDUMP},
{SIGBUS, DEFAULT_COREDUMP},
{SIGFPE, DEFAULT_COREDUMP},
{SIGSEGV, DEFAULT_COREDUMP},
{SIGCHLD, DEFAULT_IGNORE},
{SIGCONT, DEFAULT_IGNORE},
{SIGURG, DEFAULT_IGNORE},
{SIGXCPU, DEFAULT_COREDUMP},
{SIGXFSZ, DEFAULT_COREDUMP},
{SIGSYS, DEFAULT_COREDUMP},
{SIGWINCH, DEFAULT_IGNORE},
}};
for (const auto &[Signal, Behaviour] : SignalDefaultBehaviours) {
HostHandlers[Signal].DefaultBehaviour = Behaviour;
}
// Register frontend SIGILL handler for forced assertion.
RegisterFrontendHostSignalHandler(SIGILL, [](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) -> bool {
ucontext_t* _context = (ucontext_t*)ucontext;
auto &mcontext = _context->uc_mcontext;
uint64_t PC{};
#ifdef _M_ARM_64
PC = mcontext.pc;
#else
PC = mcontext.gregs[REG_RIP];
#endif
if (PC == reinterpret_cast<uint64_t>(&FEXCore::Assert::ForcedAssert)) {
// This is a host side assert. Don't deliver this to the guest
// We want to actually break here
GlobalDelegator->UninstallHostHandler(Signal);
return true;
}
return false;
}, true);
const auto PauseHandler = [](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) -> bool {
return GlobalDelegator->HandleSignalPause(Thread, Signal, info, ucontext);
};
const auto GuestSignalHandler = [](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext, GuestSigAction *GuestAction, stack_t *GuestStack) -> bool {
return GlobalDelegator->HandleDispatcherGuestSignal(Thread, Signal, info, ucontext, GuestAction, GuestStack);
};
const auto SigillHandler = [](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) -> bool {
return GlobalDelegator->HandleSIGILL(Thread, Signal, info, ucontext);
};
// Register SIGILL signal handler.
RegisterHostSignalHandler(SIGILL, SigillHandler, true);
#ifdef _M_ARM_64
// Register SIGBUS signal handler.
const auto SigbusHandler = [](FEXCore::Core::InternalThreadState *Thread, int Signal, void *_info, void *ucontext) -> bool {
const auto PC = ArchHelpers::Context::GetPc(ucontext);
if (!Thread->CPUBackend->IsAddressInCodeBuffer(PC)) {
// Wasn't a sigbus in JIT code
return false;
}
siginfo_t* info = reinterpret_cast<siginfo_t*>(_info);
if (info->si_code != BUS_ADRALN) {
// This only handles alignment problems
return false;
}
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(Thread, GlobalDelegator->ParanoidTSO(), PC, ArchHelpers::Context::GetArmGPRs(ucontext));
ArchHelpers::Context::SetPc(ucontext, PC + Result.second);
return Result.first;
};
RegisterHostSignalHandler(SIGBUS, SigbusHandler, true);
#endif
// Register pause signal handler.
RegisterHostSignalHandler(SignalDelegator::SIGNAL_FOR_PAUSE, PauseHandler, true);
// Guest signal handlers.
for (uint32_t Signal = 0; Signal <= SignalDelegator::MAX_SIGNALS; ++Signal) {
RegisterHostSignalHandlerForGuest(Signal, GuestSignalHandler);
}
}
SignalDelegator::~SignalDelegator() {
for (int i = 0; i < MAX_SIGNALS; ++i) {
if (i == 0 ||
i == SIGKILL ||
i == SIGSTOP ||
!HostHandlers[i].Installed
) {
continue;
}
::syscall(SYS_rt_sigaction, i, &HostHandlers[i].OldAction, nullptr, 8);
HostHandlers[i].Installed = false;
}
GlobalDelegator = nullptr;
}
FEXCore::Core::InternalThreadState *SignalDelegator::GetTLSThread() {
return ThreadData.Thread;
}
void SignalDelegator::RegisterTLSState(FEXCore::Core::InternalThreadState *Thread) {
ThreadData.Thread = Thread;
// Set up our signal alternative stack
// This is per thread rather than per signal
ThreadData.AltStackPtr = FEXCore::Allocator::mmap(nullptr, SIGSTKSZ * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
stack_t altstack{};
altstack.ss_sp = ThreadData.AltStackPtr;
altstack.ss_size = SIGSTKSZ * 16;
altstack.ss_flags = 0;
LOGMAN_THROW_AA_FMT(!!altstack.ss_sp, "Couldn't allocate stack pointer");
// Register the alt stack
const int Result = sigaltstack(&altstack, nullptr);
if (Result == -1) {
LogMan::Msg::EFmt("Failed to install alternative signal stack {}", strerror(errno));
}
// Get the current host signal mask
::syscall(SYS_rt_sigprocmask, 0, nullptr, &ThreadData.CurrentSignalMask.Val, 8);
if (Thread != (FEXCore::Core::InternalThreadState*)UINTPTR_MAX) {
// Reserve a small amount of deferred signal frames. Usually the stack won't be utilized beyond
// 1 or 2 signals but add a few more just in case.
Thread->DeferredSignalFrames.reserve(8);
}
}
void SignalDelegator::UninstallTLSState(FEXCore::Core::InternalThreadState *Thread) {
FEXCore::Allocator::munmap(ThreadData.AltStackPtr, SIGSTKSZ * 16);
ThreadData.AltStackPtr = nullptr;
stack_t altstack{};
altstack.ss_flags = SS_DISABLE;
// Uninstall the alt stack
const int Result = sigaltstack(&altstack, nullptr);
if (Result == -1) {
LogMan::Msg::EFmt("Failed to uninstall alternative signal stack {}", strerror(errno));
}
ThreadData.Thread = nullptr;
}
void SignalDelegator::FrontendRegisterHostSignalHandler(int Signal, HostSignalDelegatorFunction Func, bool Required) {
// Linux signal handlers are per-process rather than per thread
// Multiple threads could be calling in to this
std::lock_guard lk(HostDelegatorMutex);
HostHandlers[Signal].Required = Required;
InstallHostThunk(Signal);
}
void SignalDelegator::FrontendRegisterFrontendHostSignalHandler(int Signal, HostSignalDelegatorFunction Func, bool Required) {
// Linux signal handlers are per-process rather than per thread
// Multiple threads could be calling in to this
std::lock_guard lk(HostDelegatorMutex);
HostHandlers[Signal].Required = Required;
InstallHostThunk(Signal);
}
void SignalDelegator::RegisterHostSignalHandlerForGuest(int Signal, FEX::HLE::HostSignalDelegatorFunctionForGuest Func) {
std::lock_guard lk(HostDelegatorMutex);
HostHandlers[Signal].GuestHandler = std::move(Func);
}
void SignalDelegator::RegisterFrontendHostSignalHandler(int Signal, HostSignalDelegatorFunction Func, bool Required) {
SetFrontendHostSignalHandler(Signal, Func, Required);
FrontendRegisterFrontendHostSignalHandler(Signal, Func, Required);
}
uint64_t SignalDelegator::RegisterGuestSignalHandler(int Signal, const GuestSigAction *Action, GuestSigAction *OldAction) {
std::lock_guard lk(GuestDelegatorMutex);
// Invalid signal specified
if (Signal > MAX_SIGNALS) {
return -EINVAL;
}
// If we have an old signal set then give it back
if (OldAction) {
*OldAction = HostHandlers[Signal].GuestAction;
}
// Now assign the new action
if (Action) {
// These signal dispositions can't be changed on Linux
if (Signal == SIGKILL || Signal == SIGSTOP) {
return -EINVAL;
}
HostHandlers[Signal].GuestAction = *Action;
// Only attempt to install a new thunk handler if we were installing a new guest action
if (!InstallHostThunk(Signal)) {
UpdateHostThunk(Signal);
}
}
return 0;
}
void SignalDelegator::CheckXIDHandler() {
std::lock_guard lk(GuestDelegatorMutex);
std::lock_guard lk2(HostDelegatorMutex);
constexpr size_t SIGNAL_SETXID = 33;
kernel_sigaction CurrentAction{};
// Only update the old action if we haven't ever been installed
const int Result = ::syscall(SYS_rt_sigaction, SIGNAL_SETXID, nullptr, &CurrentAction, 8);
if (Result < 0) {
LogMan::Msg::AFmt("Failed to get status of XID signal");
return;
}
SignalHandler &HostHandler = HostHandlers[SIGNAL_SETXID];
if (CurrentAction.handler != HostHandler.HostAction.handler) {
// GLIBC overwrote our XID handler, reinstate our handler
const int Result = ::syscall(SYS_rt_sigaction, SIGNAL_SETXID, &HostHandler.HostAction, nullptr, 8);
if (Result < 0) {
LogMan::Msg::AFmt("Failed to reinstate our XID signal handler {}", strerror(errno));
}
}
}
uint64_t SignalDelegator::RegisterGuestSigAltStack(const stack_t *ss, stack_t *old_ss) {
auto Thread = GetTLSThread();
bool UsingAltStack{};
uint64_t AltStackBase = reinterpret_cast<uint64_t>(ThreadData.GuestAltStack.ss_sp);
uint64_t AltStackEnd = AltStackBase + ThreadData.GuestAltStack.ss_size;
uint64_t GuestSP = Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP];
if (!(ThreadData.GuestAltStack.ss_flags & SS_DISABLE) &&
GuestSP >= AltStackBase &&
GuestSP <= AltStackEnd) {
UsingAltStack = true;
}
// If we have an old signal set then give it back
if (old_ss) {
*old_ss = ThreadData.GuestAltStack;
if (UsingAltStack) {
// We are currently operating on the alt stack
// Let the guest know
old_ss->ss_flags |= SS_ONSTACK;
}
else {
old_ss->ss_flags |= SS_DISABLE;
}
}
// Now assign the new action
if (ss) {
// If we tried setting the alt stack while we are using it then throw an error
if (UsingAltStack) {
return -EPERM;
}
// We need to check for invalid flags
// The only flag that can be passed is SS_AUTODISARM and SS_DISABLE
if ((ss->ss_flags & ~SS_ONSTACK) & // SS_ONSTACK is ignored
~(SS_AUTODISARM | SS_DISABLE)) {
// A flag remained that isn't one of the supported ones?
return -EINVAL;
}
if (ss->ss_flags & SS_DISABLE) {
// If SS_DISABLE Is specified then the rest of the details are ignored
ThreadData.GuestAltStack = *ss;
return 0;
}
// stack size needs to be MINSIGSTKSZ (0x2000)
if (ss->ss_size < X86_MINSIGSTKSZ) {
return -ENOMEM;
}
ThreadData.GuestAltStack = *ss;
}
return 0;
}
static void CheckForPendingSignals(FEXCore::Core::InternalThreadState *Thread) {
// Do we have any pending signals that became unmasked?
uint64_t PendingSignals = ~ThreadData.CurrentSignalMask.Val & ThreadData.PendingSignals;
if (PendingSignals != 0) {
for (int i = 0; i < 64; ++i) {
if (PendingSignals & (1ULL << i)) {
FHU::Syscalls::tgkill(Thread->ThreadManager.PID, Thread->ThreadManager.TID, i + 1);
// We might not even return here which is spooky
}
}
}
}
uint64_t SignalDelegator::GuestSigProcMask(int how, const uint64_t *set, uint64_t *oldset) {
// The order in which we handle signal mask setting is important here
// old and new can point to the same location in memory.
// Even if the pointers are to same memory location, we must store the original signal mask
// coming in to the syscall.
// 1) Store old mask
// 2) Set mask to new mask if exists
// 3) Give old mask back
auto OldSet = ThreadData.CurrentSignalMask.Val;
if (!!set) {
uint64_t IgnoredSignalsMask = ~((1ULL << (SIGKILL - 1)) | (1ULL << (SIGSTOP - 1)));
if (how == SIG_BLOCK) {
ThreadData.CurrentSignalMask.Val |= *set & IgnoredSignalsMask;
}
else if (how == SIG_UNBLOCK) {
ThreadData.CurrentSignalMask.Val &= ~(*set & IgnoredSignalsMask);
}
else if (how == SIG_SETMASK) {
ThreadData.CurrentSignalMask.Val = *set & IgnoredSignalsMask;
}
else {
return -EINVAL;
}
uint64_t HostMask = ThreadData.CurrentSignalMask.Val;
// Now actually set the host mask
// This will hide from the guest that we are not actually setting all of the masks it wants
for (size_t i = 0; i < MAX_SIGNALS; ++i) {
if (HostHandlers[i + 1].Required.load(std::memory_order_relaxed)) {
// If it is a required host signal then we can't mask it
HostMask &= ~(1ULL << i);
}
}
::syscall(SYS_rt_sigprocmask, SIG_SETMASK, &HostMask, nullptr, 8);
}
if (!!oldset) {
*oldset = OldSet;
}
CheckForPendingSignals(GetTLSThread());
return 0;
}
uint64_t SignalDelegator::GuestSigPending(uint64_t *set, size_t sigsetsize) {
if (sigsetsize > sizeof(uint64_t)) {
return -EINVAL;
}
*set = ThreadData.PendingSignals;
sigset_t HostSet{};
if (sigpending(&HostSet) == 0) {
uint64_t HostSignals{};
for (size_t i = 0; i < MAX_SIGNALS; ++i) {
if (sigismember(&HostSet, i + 1)) {
HostSignals |= (1ULL << i);
}
}
// Merge the real pending signal mask as well
*set |= HostSignals;
}
return 0;
}
uint64_t SignalDelegator::GuestSigSuspend(uint64_t *set, size_t sigsetsize) {
if (sigsetsize > sizeof(uint64_t)) {
return -EINVAL;
}
uint64_t IgnoredSignalsMask = ~((1ULL << (SIGKILL - 1)) | (1ULL << (SIGSTOP - 1)));
// Backup the mask
ThreadData.PreviousSuspendMask = ThreadData.CurrentSignalMask;
// Set the new mask
ThreadData.CurrentSignalMask.Val = *set & IgnoredSignalsMask;
sigset_t HostSet{};
sigemptyset(&HostSet);
for (int32_t i = 0; i < MAX_SIGNALS; ++i) {
if (*set & (1ULL << i)) {
sigaddset(&HostSet, i + 1);
}
}
// Additionally we must always listen to SIGNAL_FOR_PAUSE
// This technically forces us in to a race but should be fine
// SIGBUS and SIGILL can't happen so we don't need to listen for them
//sigaddset(&HostSet, SIGNAL_FOR_PAUSE);
// Spin this in a loop until we aren't sigsuspended
// This can happen in the case that the guest has sent signal that we can't block
uint64_t Result = sigsuspend(&HostSet);
// Restore Previous signal mask we are emulating
// XXX: Might be unsafe if the signal handler adjusted the thread's signal mask
// But since we don't support the guest adjusting the mask through the context object
// then this is safe-ish
ThreadData.CurrentSignalMask = ThreadData.PreviousSuspendMask;
CheckForPendingSignals(GetTLSThread());
return Result == -1 ? -errno : Result;
}
uint64_t SignalDelegator::GuestSigTimedWait(uint64_t *set, siginfo_t *info, const struct timespec *timeout, size_t sigsetsize) {
if (sigsetsize > sizeof(uint64_t)) {
return -EINVAL;
}
uint64_t Result = ::syscall(SYS_rt_sigtimedwait, set, info, timeout);
return Result == -1 ? -errno : Result;
}
uint64_t SignalDelegator::GuestSignalFD(int fd, const uint64_t *set, size_t sigsetsize, int flags) {
if (sigsetsize > sizeof(uint64_t)) {
return -EINVAL;
}
sigset_t HostSet{};
sigemptyset(&HostSet);
for (size_t i = 0; i < MAX_SIGNALS; ++i) {
if (HostHandlers[i + 1].Required.load(std::memory_order_relaxed)) {
// For now skip our internal signals
continue;
}
if (*set & (1ULL << i)) {
sigaddset(&HostSet, i + 1);
}
}
// XXX: This is a barebones implementation just to get applications that listen for SIGCHLD to work
// In the future we need our own listern thread that forwards the result
// Thread is necessary to prevent deadlocks for a thread that has signaled on the same thread listening to the FD and blocking is enabled
uint64_t Result = signalfd(fd, &HostSet, flags);
return Result == -1 ? -errno : Result;
}
fextl::unique_ptr<FEX::HLE::SignalDelegator> CreateSignalDelegator(FEXCore::Context::Context *CTX, const std::string_view ApplicationName) {
return fextl::make_unique<FEX::HLE::SignalDelegator>(CTX, ApplicationName);
}
}