mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 23:00:17 +02:00
This mode has been broken for a long time because it's mostly untested. Barriers, and backpatching while slow have proven that they work. Maintain the one TSO path, at least until all ARM hardware gains support for x86-TSO memory model mode.
1337 lines
52 KiB
C++
1337 lines
52 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/Utils/Allocator.h>
|
|
#include <FEXCore/Utils/CompilerDefs.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXCore/Utils/MathUtils.h>
|
|
#include <FEXCore/Utils/FPState.h>
|
|
#include <FEXCore/Utils/Profiler.h>
|
|
#include <FEXCore/Utils/ArchHelpers/Arm64.h>
|
|
#include <FEXHeaderUtils/Syscalls.h>
|
|
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <linux/futex.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;
|
|
|
|
static FEX::HLE::ThreadStateObject* GetThreadFromAltStack(const stack_t& alt_stack) {
|
|
// The thread object lives just before the alt-stack begin.
|
|
FEX::HLE::ThreadStateObject* ThreadObject {};
|
|
memcpy(&ThreadObject, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(alt_stack.ss_sp) - 8), sizeof(void*));
|
|
return ThreadObject;
|
|
}
|
|
|
|
static void SignalHandlerThunk(int Signal, siginfo_t* Info, void* UContext) {
|
|
ucontext_t* _context = (ucontext_t*)UContext;
|
|
auto ThreadObject = GetThreadFromAltStack(_context->uc_stack);
|
|
FEXCORE_PROFILE_ACCUMULATION(ThreadObject->Thread, AccumulatedSignalTime);
|
|
ThreadObject->SignalInfo.Delegator->HandleSignal(ThreadObject, 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
|
|
* @{ */
|
|
|
|
void SignalDelegator::HandleSignal(FEX::HLE::ThreadStateObject* Thread, int Signal, void* Info, void* UContext) {
|
|
// Let the host take first stab at handling the signal
|
|
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->Thread, Signal, Info, UContext)) {
|
|
// If the host handler handled the fault then we can continue now
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Handler.FrontendHandler && Handler.FrontendHandler(Thread->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, std::move(Func), Required);
|
|
FrontendRegisterHostSignalHandler(Signal, Required);
|
|
}
|
|
|
|
void SignalDelegator::SpillSRA(FEXCore::Core::InternalThreadState* Thread, void* ucontext, uint32_t IgnoreMask) {
|
|
#ifdef _M_ARM_64
|
|
Thread->CurrentFrame->State.rip = CTX->RestoreRIPFromHostPC(Thread, ArchHelpers::Context::GetPc(ucontext));
|
|
|
|
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 (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));
|
|
}
|
|
}
|
|
|
|
uint32_t EFlags =
|
|
CTX->ReconstructCompactedEFLAGS(Thread, true, ArchHelpers::Context::GetArmGPRs(ucontext), ArchHelpers::Context::GetArmPState(ucontext));
|
|
CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags);
|
|
#endif
|
|
}
|
|
|
|
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) {
|
|
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 (SupportsAVX) {
|
|
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 (SupportsAVX) {
|
|
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 (SupportsAVX) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = CTX->IsAddressInCodeBuffer(Thread, 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);
|
|
|
|
ContextBackup->OriginalRIP = Thread->CurrentFrame->State.rip;
|
|
uint32_t eflags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0);
|
|
|
|
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(Thread, ContextBackup, Frame, Signal, HostSigInfo, ucontext, GuestAction, GuestStack, NewGuestSP, eflags);
|
|
} else {
|
|
NewGuestSP = SetupFrame_ia32(Thread, 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;
|
|
|
|
// Linux clears DF, RF, and TF flags on signal.
|
|
Frame->State.flags[FEXCore::X86State::RFLAG_DF_RAW_LOC] = 1;
|
|
Frame->State.flags[FEXCore::X86State::RFLAG_RF_LOC] = 0;
|
|
Frame->State.flags[FEXCore::X86State::RFLAG_TF_RAW_LOC] = 0;
|
|
|
|
// Linux resets the CS and SS registers on signal handler.
|
|
// This way signal handlers always go back to their original operating mode.
|
|
// Doesn't matter for 32-bit processes as they can only be 32-bit, but does
|
|
// matter for 64-bit processes as they could have potentially installed a 32-bit code segment.
|
|
Frame->State.cs_idx = FEXCore::Core::CPUState::DEFAULT_USER_CS << 3;
|
|
Frame->State.ss_idx = 0;
|
|
Frame->State.cs_cached = Frame->State.CalculateGDTBase(*Frame->State.GetSegmentFromIndex(Frame->State, Frame->State.cs_idx));
|
|
Frame->State.ss_cached = Frame->State.CalculateGDTBase(*Frame->State.GetSegmentFromIndex(Frame->State, Frame->State.ss_idx));
|
|
|
|
// 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);
|
|
ArchHelpers::Context::SetFillSRASingleInst(ucontext, false);
|
|
// 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) {
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread);
|
|
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 (ThreadObject->SignalInfo.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->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), 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) {
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread);
|
|
SignalEvent SignalReason = ThreadObject->SignalReason.load();
|
|
auto Frame = Thread->CurrentFrame;
|
|
|
|
if (SignalReason == SignalEvent::Pause) {
|
|
// Store our thread state so we can come back to this
|
|
StoreThreadState(Thread, Signal, ucontext);
|
|
|
|
if (CTX->IsAddressInCodeBuffer(Thread, 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
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
LOGMAN_THROW_A_FMT(!IsAddressInDispatcher(ArchHelpers::Context::GetPc(ucontext)), "Signals in dispatcher have unsynchronized "
|
|
"context");
|
|
#endif
|
|
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;
|
|
|
|
ThreadObject->SignalReason.store(SignalEvent::Nothing);
|
|
return true;
|
|
}
|
|
|
|
if (SignalReason == 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 (CTX->IsAddressInCodeBuffer(Thread, 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
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
LOGMAN_THROW_A_FMT(!IsAddressInDispatcher(ArchHelpers::Context::GetPc(ucontext)), "Signals in dispatcher have unsynchronized "
|
|
"context");
|
|
#endif
|
|
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 (ThreadObject->ThreadSleeping) {
|
|
// If the thread was sleeping then its idle counter was decremented
|
|
// Reincrement it here to not break logic
|
|
FEX::HLE::_SyscallHandler->TM.IncrementIdleRefCount();
|
|
}
|
|
|
|
ThreadObject->SignalReason.store(SignalEvent::Nothing);
|
|
return true;
|
|
}
|
|
|
|
if (SignalReason == SignalEvent::Return || SignalReason == SignalEvent::ReturnRT) {
|
|
RestoreThreadState(Thread, ucontext, SignalReason == 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;
|
|
|
|
ThreadObject->SignalReason.store(SignalEvent::Nothing);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SignalDelegator::SignalThread(FEXCore::Core::InternalThreadState* Thread, SignalEvent Event) {
|
|
auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread);
|
|
ThreadObject->SignalReason.store(Event);
|
|
FHU::Syscalls::tgkill(ThreadObject->ThreadInfo.PID, ThreadObject->ThreadInfo.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;
|
|
}
|
|
|
|
uint64_t SignalDelegator::GetNewSigMask(int Signal) const {
|
|
const SignalHandler& Handler = HostHandlers[Signal];
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
return NewMask;
|
|
}
|
|
|
|
void SignalDelegator::HandleGuestSignal(FEX::HLE::ThreadStateObject* ThreadObject, int Signal, void* Info, void* UContext) {
|
|
auto Thread = ThreadObject->Thread;
|
|
ucontext_t* _context = (ucontext_t*)UContext;
|
|
auto SigInfo = *static_cast<siginfo_t*>(Info);
|
|
|
|
auto MustDeferSignal = (Thread->CurrentFrame->State.DeferredSignalRefCount.Load() != 0);
|
|
|
|
if (Signal == SIGSEGV && SigInfo.si_code == SEGV_ACCERR && SigInfo.si_addr == reinterpret_cast<void*>(&Thread->InterruptFaultPage)) {
|
|
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->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_READ | PROT_WRITE);
|
|
|
|
if (ThreadObject->SignalInfo.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;
|
|
}
|
|
|
|
const auto& Top = ThreadObject->SignalInfo.DeferredSignalFrames.back();
|
|
Signal = Top.Signal;
|
|
SigInfo = Top.Info;
|
|
// sig mask has been updated at the defer time, recover the original mask
|
|
memcpy(&_context->uc_sigmask, &Top.SigMask, sizeof(uint64_t));
|
|
ThreadObject->SignalInfo.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 InterruptFaultPage");
|
|
#endif
|
|
}
|
|
} else if (FaultSafeUserMemAccess::TryHandleSafeFault(Signal, SigInfo, UContext)) {
|
|
ERROR_AND_DIE_FMT("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(ThreadObject->SignalInfo.DeferredSignalFrames.size() != ThreadObject->SignalInfo.DeferredSignalFrames.capacity(),
|
|
"Deferred signals vector hit "
|
|
"capacity size. This will "
|
|
"likely crash! Asserting now!");
|
|
|
|
ThreadObject->SignalInfo.DeferredSignalFrames.emplace_back(ThreadStateObject::DeferredSignalState {
|
|
.Info = SigInfo,
|
|
.Signal = Signal,
|
|
.SigMask = _context->uc_sigmask.__val[0],
|
|
});
|
|
|
|
uint64_t NewMask = GetNewSigMask(Signal);
|
|
|
|
// 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));
|
|
|
|
// Now update the faulting page permissions so it will fault on write.
|
|
mprotect(reinterpret_cast<void*>(&Thread->InterruptFaultPage), sizeof(Thread->InterruptFaultPage), PROT_NONE);
|
|
|
|
// Postpone the remainder of signal handling logic until we process the SIGSEGV triggered by writing to InterruptFaultPage.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for masked signals
|
|
if (ThreadObject->SignalInfo.CurrentSignalMask.Val & (1ULL << (Signal - 1)) && IsAsyncSignal(&SigInfo, Signal)) {
|
|
// This signal is masked, must defer until the guest updates the signal mask.
|
|
// Add it to the pending signal list
|
|
ThreadObject->SignalInfo.PendingSignals |= 1ULL << (Signal - 1);
|
|
return;
|
|
}
|
|
|
|
// Let the host take first stab at handling the signal
|
|
SignalHandler& Handler = HostHandlers[Signal];
|
|
|
|
// Remove the pending signal
|
|
ThreadObject->SignalInfo.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, &ThreadObject->SignalInfo.GuestAltStack)) {
|
|
uint64_t NewMask = GetNewSigMask(Signal);
|
|
|
|
// 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.
|
|
FEXCORE_TELEMETRY_OR(TYPE_CRASH_MASK, (1ULL << Signal));
|
|
if (Signal == SIGSEGV && reinterpret_cast<uint64_t>(SigInfo.si_addr) >= SyscallHandler::TASK_MAX_64BIT) {
|
|
// Tried accessing invalid non-canonical x86-64 address.
|
|
FEXCORE_TELEMETRY_SET(TYPE_UNHANDLED_NONCANONICAL_ADDRESS, 1);
|
|
}
|
|
SaveTelemetry();
|
|
#endif
|
|
|
|
FEX::HLE::_SyscallHandler->TM.CleanupForExit();
|
|
|
|
// 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);
|
|
}
|
|
|
|
void SignalDelegator::QueueSignal(pid_t tgid, pid_t tid, int Signal, siginfo_t* info, bool IgnoreMask) {
|
|
bool WasIgnored {};
|
|
bool WasMasked {};
|
|
SignalHandler& SignalHandler = HostHandlers[Signal];
|
|
if (SignalHandler.GuestAction.sigaction_handler.handler == SIG_IGN && IgnoreMask) {
|
|
::syscall(SYS_rt_sigaction, Signal, &SignalHandler.OldAction, nullptr, 8);
|
|
WasIgnored = true;
|
|
}
|
|
|
|
// Get the current host signal mask
|
|
uint64_t ThreadSignalMask {};
|
|
const uint64_t SignalMask = 1ULL << (Signal - 1);
|
|
::syscall(SYS_rt_sigprocmask, 0, nullptr, &ThreadSignalMask, 8);
|
|
if (ThreadSignalMask & SignalMask) {
|
|
WasMasked = true;
|
|
|
|
// Signal currently masked, unmask
|
|
ThreadSignalMask &= ~SignalMask;
|
|
::syscall(SYS_rt_sigprocmask, 0, &ThreadSignalMask, &ThreadSignalMask, 8);
|
|
}
|
|
|
|
::syscall(SYSCALL_DEF(rt_tgsigqueueinfo), tgid, tid, Signal, info);
|
|
|
|
if (WasMasked) {
|
|
// Mask again
|
|
::syscall(SYS_rt_sigprocmask, 0, &ThreadSignalMask, nullptr, 8);
|
|
}
|
|
|
|
if (WasIgnored) {
|
|
// Ignore again
|
|
::syscall(SYS_rt_sigaction, Signal, &SignalHandler.HostAction, nullptr, 8);
|
|
}
|
|
}
|
|
|
|
SignalDelegator::SignalDelegator(FEXCore::Context::Context* _CTX, const std::string_view ApplicationName, bool SupportsAVX)
|
|
: CTX {_CTX}
|
|
, ApplicationName {ApplicationName}
|
|
, SupportsAVX {SupportsAVX} {
|
|
// Signal zero isn't real
|
|
HostHandlers[0].Installed = true;
|
|
|
|
// We can't capture SIGKILL or SIGSTOP
|
|
HostHandlers[SIGKILL].Installed = true;
|
|
HostHandlers[SIGSTOP].Installed = true;
|
|
|
|
if (HalfBarrierTSOEnabled()) {
|
|
UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::HalfBarrier;
|
|
} else {
|
|
UnalignedHandlerType = FEXCore::ArchHelpers::Arm64::UnalignedHandlerType::NonAtomic;
|
|
}
|
|
|
|
// 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
|
|
FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread)->SignalInfo.Delegator->UninstallHostHandler(Signal);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
true);
|
|
|
|
const auto PauseHandler = [](FEXCore::Core::InternalThreadState* Thread, int Signal, void* info, void* ucontext) -> bool {
|
|
return FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread)->SignalInfo.Delegator->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 FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread)->SignalInfo.Delegator->HandleDispatcherGuestSignal(
|
|
Thread, Signal, info, ucontext, GuestAction, GuestStack);
|
|
};
|
|
|
|
const auto SigillHandler = [](FEXCore::Core::InternalThreadState* Thread, int Signal, void* info, void* ucontext) -> bool {
|
|
return FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread)->SignalInfo.Delegator->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->CTX->IsAddressInCodeBuffer(Thread, 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;
|
|
}
|
|
|
|
FEXCORE_PROFILE_INSTANT_INCREMENT(Thread, AccumulatedSIGBUSCount, 1);
|
|
const auto Delegator = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread)->SignalInfo.Delegator;
|
|
const auto Result = FEXCore::ArchHelpers::Arm64::HandleUnalignedAccess(Thread, Delegator->GetUnalignedHandlerType(), PC,
|
|
ArchHelpers::Context::GetArmGPRs(ucontext));
|
|
ArchHelpers::Context::SetPc(ucontext, PC + Result.value_or(0));
|
|
return Result.has_value();
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void SignalDelegator::RegisterTLSState(FEX::HLE::ThreadStateObject* Thread) {
|
|
FEXCore::Allocator::RegisterTLSData(Thread->Thread);
|
|
|
|
Thread->SignalInfo.Delegator = this;
|
|
|
|
// Set up our signal alternative stack
|
|
// This is per thread rather than per signal
|
|
Thread->SignalInfo.AltStackPtr = FEXCore::Allocator::mmap(nullptr, SIGSTKSZ * 16, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
FEXCore::Allocator::VirtualName("FEXMem_Misc", reinterpret_cast<void*>(Thread->SignalInfo.AltStackPtr), SIGSTKSZ * 16);
|
|
stack_t altstack {};
|
|
altstack.ss_sp = reinterpret_cast<void*>(reinterpret_cast<uint64_t>(Thread->SignalInfo.AltStackPtr) + 8);
|
|
altstack.ss_size = SIGSTKSZ * 16 - 8;
|
|
altstack.ss_flags = 0;
|
|
LOGMAN_THROW_A_FMT(!!altstack.ss_sp, "Couldn't allocate stack pointer");
|
|
|
|
// Copy the thread object to the start of the alt-stack
|
|
memcpy(Thread->SignalInfo.AltStackPtr, &Thread, sizeof(void*));
|
|
|
|
// Protect the first page of the alt-stack for overflow protection.
|
|
mprotect(Thread->SignalInfo.AltStackPtr, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ);
|
|
|
|
// 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, &Thread->SignalInfo.CurrentSignalMask.Val, 8);
|
|
|
|
if (Thread->Thread) {
|
|
// 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->SignalInfo.DeferredSignalFrames.reserve(8);
|
|
}
|
|
}
|
|
|
|
void SignalDelegator::UninstallTLSState(FEX::HLE::ThreadStateObject* Thread) {
|
|
FEXCore::Allocator::munmap(Thread->SignalInfo.AltStackPtr, SIGSTKSZ * 16);
|
|
|
|
Thread->SignalInfo.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));
|
|
}
|
|
|
|
FEXCore::Allocator::UninstallTLSData(Thread->Thread);
|
|
}
|
|
|
|
void SignalDelegator::FrontendRegisterHostSignalHandler(int Signal, 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, 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, std::move(Func), Required);
|
|
FrontendRegisterFrontendHostSignalHandler(Signal, 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(FEX::HLE::ThreadStateObject* Thread, const stack_t* ss, stack_t* old_ss) {
|
|
bool UsingAltStack {};
|
|
uint64_t AltStackBase = reinterpret_cast<uint64_t>(Thread->SignalInfo.GuestAltStack.ss_sp);
|
|
uint64_t AltStackEnd = AltStackBase + Thread->SignalInfo.GuestAltStack.ss_size;
|
|
uint64_t GuestSP = Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP];
|
|
|
|
if (!(Thread->SignalInfo.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 = Thread->SignalInfo.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
|
|
Thread->SignalInfo.GuestAltStack = *ss;
|
|
return 0;
|
|
}
|
|
|
|
// stack size needs to be MINSIGSTKSZ (0x2000)
|
|
if (ss->ss_size < X86_MINSIGSTKSZ) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
Thread->SignalInfo.GuestAltStack = *ss;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void CheckForPendingSignals(const FEX::HLE::ThreadStateObject* Thread) {
|
|
// Do we have any pending signals that became unmasked?
|
|
uint64_t PendingSignals = ~Thread->SignalInfo.CurrentSignalMask.Val & Thread->SignalInfo.PendingSignals;
|
|
if (PendingSignals != 0) {
|
|
for (int i = 0; i < 64; ++i) {
|
|
if (PendingSignals & (1ULL << i)) {
|
|
FHU::Syscalls::tgkill(Thread->ThreadInfo.PID, Thread->ThreadInfo.TID, i + 1);
|
|
// We might not even return here which is spooky
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t SignalDelegator::GuestSigProcMask(FEX::HLE::ThreadStateObject* Thread, 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 = Thread->SignalInfo.CurrentSignalMask.Val;
|
|
|
|
if (!!set) {
|
|
uint64_t IgnoredSignalsMask = ~((1ULL << (SIGKILL - 1)) | (1ULL << (SIGSTOP - 1)));
|
|
if (how == SIG_BLOCK) {
|
|
Thread->SignalInfo.CurrentSignalMask.Val |= *set & IgnoredSignalsMask;
|
|
} else if (how == SIG_UNBLOCK) {
|
|
Thread->SignalInfo.CurrentSignalMask.Val &= ~(*set & IgnoredSignalsMask);
|
|
} else if (how == SIG_SETMASK) {
|
|
Thread->SignalInfo.CurrentSignalMask.Val = *set & IgnoredSignalsMask;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
uint64_t HostMask = Thread->SignalInfo.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(Thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t SignalDelegator::GuestSigPending(FEX::HLE::ThreadStateObject* Thread, uint64_t* set, size_t sigsetsize) {
|
|
if (sigsetsize > sizeof(uint64_t)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*set = Thread->SignalInfo.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(FEX::HLE::ThreadStateObject* Thread, 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
|
|
Thread->SignalInfo.PreviousSuspendMask = Thread->SignalInfo.CurrentSignalMask;
|
|
// Set the new mask
|
|
Thread->SignalInfo.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
|
|
Thread->SignalInfo.CurrentSignalMask = Thread->SignalInfo.PreviousSuspendMask;
|
|
|
|
CheckForPendingSignals(Thread);
|
|
|
|
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, bool SupportsAVX) {
|
|
return fextl::make_unique<FEX::HLE::SignalDelegator>(CTX, ApplicationName, SupportsAVX);
|
|
}
|
|
} // namespace FEX::HLE
|