diff --git a/External/FEXCore/Source/Interface/Core/Core.cpp b/External/FEXCore/Source/Interface/Core/Core.cpp index 861ec6732..a88e4b78b 100644 --- a/External/FEXCore/Source/Interface/Core/Core.cpp +++ b/External/FEXCore/Source/Interface/Core/Core.cpp @@ -126,8 +126,7 @@ namespace DefaultFallbackCore { } namespace FEXCore::Context { - Context::Context() - : SignalDelegation {this} { + Context::Context() { FallbackCPUFactory = FEXCore::Core::DefaultFallbackCore::CPUCreationFactory; #ifdef BLOCKSTATS BlockData = std::make_unique(); @@ -870,6 +869,8 @@ namespace FEXCore::Context { --IdleWaitRefCount; IdleWaitCV.notify_all(); + SignalDelegation.UninstallTLSState(Thread); + Thread->State.RunningEvents.WaitingToStart = false; Thread->State.RunningEvents.Running = false; } diff --git a/External/FEXCore/Source/Interface/Core/SignalDelegator.cpp b/External/FEXCore/Source/Interface/Core/SignalDelegator.cpp index c99e186aa..46a4232ab 100644 --- a/External/FEXCore/Source/Interface/Core/SignalDelegator.cpp +++ b/External/FEXCore/Source/Interface/Core/SignalDelegator.cpp @@ -1,16 +1,26 @@ #include "Interface/Context/Context.h" #include "Interface/Core/InternalThreadState.h" #include "Interface/Core/SignalDelegator.h" + +#include #include #include namespace FEXCore { + constexpr static uint32_t SS_AUTODISARM = (1U << 31); + 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{}; + // Guest signal sa_mask is per thread! + SignalDelegator::GuestSAMask Guest_sa_mask[SignalDelegator::MAX_SIGNALS]{}; + uint32_t CurrentSignal{}; }; thread_local ThreadState ThreadData{}; @@ -32,27 +42,48 @@ namespace FEXCore { return false; } + uint64_t SigIsMember(SignalDelegator::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; + } + + void SignalDelegator::SetCurrentSignal(uint32_t Signal) { + ThreadData.CurrentSignal = Signal; + } + void SignalDelegator::HandleSignal(int Signal, void *Info, void *UContext) { // Let the host take first stab at handling the signal - + siginfo_t *SigInfo = static_cast(Info); auto Thread = ThreadData.Thread; SignalHandler &Handler = HostHandlers[Signal]; + + if (!Thread) { + LogMan::Msg::E("Thread has received a signal and hasn't registered itself with the delegate! Programming error!"); + exit(-1); + return; + } + if (Handler.Handler && Handler.Handler(Thread, Signal, Info, UContext)) { // If the host handler handled the fault then we can continue now return; } + // If the signal was sent by the user with kill then we can't block it + // If it was sent by raise() then we /can/ block it + bool SentByUser = SigInfo->si_code <= 0; + if (Signal == SIGCHLD) { // Do some special handling around this signal // If the guest has a signal handler installed with SA_NOCLDSTOP or SA_NOCHLDWAIT then // handle carefully - if (Handler.GuestAction.sa_flags & SA_NOCLDSTOP) { - // No signal is generated in this case - // just safely return - // XXX: If this was a child that exited then don't deliver the signal - // If called from signal then it still need to be delivered - // Handle this + if (Handler.GuestAction.sa_flags & SA_NOCLDSTOP && + !SentByUser) { + // If we were sent the signal from kill, tkill, or tgkill + // then si_code is set to SI_TKILL and should be delivered to the guest + // Otherwise if NOCLDSTOP is set without this code, just drop the signal + return; } if (Handler.GuestAction.sa_flags & SA_NOCLDWAIT) { @@ -63,25 +94,28 @@ namespace FEXCore { } } - // XXX: Setup our state to jump back in to the JIT at the guest handler's location - // TLS is safe on x86-64 and AArch64 hosts - if (!Thread) { - LogMan::Msg::E("Thread has received a signal and hasn't registered itself with the delegate! Programming error!"); + if (SigIsMember(&ThreadData.Guest_sa_mask[ThreadData.CurrentSignal], Signal)) { + // If we are in a signal and our signal mask is blocking this signal then ignore it + return; + } + + ThreadData.CurrentSignal = Signal; + + // We have an emulation thread pointer, we can now modify its state + if (Handler.GuestAction.sigaction_handler.handler == SIG_DFL) { + // XXX: Maybe this should actually go down guest handler state? + signal(Signal, SIG_DFL); + return; + } + else if (Handler.GuestAction.sigaction_handler.handler == SIG_IGN) { + return; } else { - // We have an emulation thread pointer, we can now modify its state - if (Handler.GuestAction.sa_handler == SIG_DFL) { - // XXX: Maybe this should actually go down guest handler state? - signal(Signal, SIG_DFL); + if (Handler.GuestHandler && + Handler.GuestHandler(Thread, Signal, Info, UContext, &Handler.GuestAction, &ThreadData.GuestAltStack)) { return; } - else if (Handler.GuestAction.sa_handler == SIG_IGN) { - return; - } - else { - // XXX: Handle the guest signal and return here - ERROR_AND_DIE("Unhandled guest exception"); - } + ERROR_AND_DIE("Unhandled guest exception"); } // Unhandled crash @@ -99,38 +133,90 @@ namespace FEXCore { } } - void SignalDelegator::InstallHostThunk(int Signal) { + bool SignalDelegator::InstallHostThunk(int Signal) { + SignalHandler &SignalHandler = HostHandlers[Signal]; // If the host thunk is already installed for this, just return - if (HostHandlers[Signal].Installed) { - return; + if (SignalHandler.Installed) { + return false; } // Now install the thunk handler - struct sigaction act{}; - act.sa_sigaction = &SignalHandlerThunk; - act.sa_flags = SA_SIGINFO | SA_RESTART; + SignalHandler.HostAction.sa_sigaction = &SignalHandlerThunk; + SignalHandler.HostAction.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; + + if (SignalHandler.GuestAction.sa_flags & SA_NODEFER) { + // If the guest is using NODEFER then make sure to set it for the host as well + SignalHandler.HostAction.sa_flags |= SA_NODEFER; + } + + /* + * XXX: This isn't quite as straightforward as a memcmp + * There are conflicting definitions between sigset_t and __sigset_t causing problems here + sigset_t EmptySet{}; + sigemptyset(&EmptySet); + if (SignalHandler.GuestAction.sa_mask != EmptySet) { + // If the guest has masked some signals then we need to also mask those signals + SignalHandler.HostAction.sa_mask = SignalHandler.GuestAction.sa_mask; + + // If the guest tried masking SIGILL or SIGBUS then too bad, we actually need this on the host + sigdelset(SignalHandler.HostAction.sa_mask, SIGILL); + sigdelset(SignalHandler.HostAction.sa_mask, SIGBUS); + } + */ // We don't care about the previous handler in this case - int Result = sigaction(Signal, &act, &HostHandlers[Signal].OldAction); + int Result = sigaction(Signal, &SignalHandler.HostAction, &SignalHandler.OldAction); if (Result < 0) { - LogMan::Msg::D("Failed to install host signal thunk for signal %d: %s", Signal, strerror(errno)); + LogMan::Msg::E("Failed to install host signal thunk for signal %d: %s", Signal, strerror(errno)); + return false; + } + + SignalHandler.Installed = true; + return true; + } + + void SignalDelegator::UpdateHostThunk(int Signal) { + SignalHandler &SignalHandler = HostHandlers[Signal]; + bool Changed{}; + + // This only gets called if a guest thunk was already installed and we need to check if we need to update the flags or signal mask + if ((SignalHandler.GuestAction.sa_flags ^ SignalHandler.HostAction.sa_flags) & SA_NODEFER) { + // NODEFER changed, we need to update this + SignalHandler.HostAction.sa_flags |= SignalHandler.GuestAction.sa_flags & SA_NODEFER; + Changed = true; + } + + /* + if ((SignalHandler.GuestAction.sa_mask ^ SignalHandler.HostAction.sa_mask) & ~(SIGILL | SIGBUS)) { + // If the signal ignore mask has updated (avoiding the two we need for the host) then we need to update + SignalHandler.HostAction.sa_mask = SignalHandler.GuestAction.sa_mask; + sigdelset(SignalHandler.HostAction.sa_mask, SIGILL); + sigdelset(SignalHandler.HostAction.sa_mask, SIGBUS); + Changed = true; + } + */ + + if (!Changed) { return; } - HostHandlers[Signal].Installed = true; + // Only update our host signal here + int Result = sigaction(Signal, &SignalHandler.HostAction, nullptr); + if (Result < 0) { + LogMan::Msg::E("Failed to update host signal thunk for signal %d: %s", Signal, strerror(errno)); + } } - SignalDelegator::SignalDelegator(FEXCore::Context::Context *ctx) - : CTX {ctx} { + SignalDelegator::SignalDelegator() { // Register this delegate LogMan::Throw::A(!GlobalDelegator, "Can't register global delegator multiple times!"); GlobalDelegator = this; + // Signal zero isn't real + HostHandlers[0].Installed = true; - // XXX: Let's just say that these are installed for now - // We can't have the guest capturing these yet so it would do nothing - // Resulting in Ctrl-C and Ctrl-\ breaking - HostHandlers[SIGINT].Installed = true; - HostHandlers[SIGQUIT].Installed = true; + // We can't capture SIGKILL or SIGSTOP + HostHandlers[SIGKILL].Installed = true; + HostHandlers[SIGSTOP].Installed = true; // glibc reserves these two signals internally // __SIGRTMIN(32) is used for a "cancellation" signal @@ -142,27 +228,63 @@ namespace FEXCore { 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 = malloc(SIGSTKSZ); + stack_t altstack{}; + altstack.ss_sp = ThreadData.AltStackPtr; + altstack.ss_size = SIGSTKSZ; + altstack.ss_flags = 0; + LogMan::Throw::A(!!altstack.ss_sp, "Couldn't allocate stack pointer"); + + // Register the alt stack + int Result = sigaltstack(&altstack, nullptr); + if (Result == -1) { + LogMan::Msg::E("Failed to install alternative signal stack %s", strerror(errno)); + } } - void SignalDelegator::MaskSignals(int how) { + void SignalDelegator::UninstallTLSState(FEXCore::Core::InternalThreadState *Thread) { + free(ThreadData.AltStackPtr); + + ThreadData.Thread = nullptr; + ThreadData.AltStackPtr = nullptr; + + stack_t altstack{}; + altstack.ss_flags = SS_DISABLE; + + // Uninstall the alt stack + int Result = sigaltstack(&altstack, nullptr); + if (Result == -1) { + LogMan::Msg::E("Failed to uninstall alternative signal stack %s", strerror(errno)); + } + } + + void SignalDelegator::MaskSignals(int how, int Signal) { // If we have a helper thread, we need to mask a significant amount of signals so the an errant thread doesn't receive a signal that it shouldn't sigset_t SignalSet{}; sigemptyset(&SignalSet); - for (int i = 0; i < MAX_SIGNALS; ++i) { - // If it is a synchronous signal then don't ignore it - if (IsSynchronous(i)) { - continue; - } + if (Signal == -1) { + for (int i = 0; i < MAX_SIGNALS; ++i) { + // If it is a synchronous signal then don't ignore it + if (IsSynchronous(i)) { + continue; + } - // Add this signal to the ignore list - sigaddset(&SignalSet, i); + // Add this signal to the ignore list + sigaddset(&SignalSet, i); + } + } + else { + sigaddset(&SignalSet, Signal); } // Be warned, a thread will inherit the signal mask if created from this thread int Result = pthread_sigmask(how, &SignalSet, nullptr); if (Result != 0) { - LogMan::Msg::D("Couldn't register thread to mask signals"); + LogMan::Msg::E("Couldn't register thread to mask signals"); } } @@ -174,6 +296,16 @@ namespace FEXCore { MaskSignals(SIG_UNBLOCK); } + bool SignalDelegator::BlockSignal(int Signal) { + MaskSignals(SIG_BLOCK, Signal); + return true; + } + + bool SignalDelegator::UnblockSignal(int Signal) { + MaskSignals(SIG_UNBLOCK, Signal); + return true; + } + void SignalDelegator::RegisterHostSignalHandler(int Signal, HostSignalDelegatorFunction Func) { // Linux signal handlers are per-process rather than per thread // Multiple threads could be calling in to this @@ -182,7 +314,13 @@ namespace FEXCore { InstallHostThunk(Signal); } - uint64_t SignalDelegator::RegisterGuestSignalHandler(int Signal, const struct sigaction *Action, struct sigaction *OldAction) { + void SignalDelegator::RegisterHostSignalHandlerForGuest(int Signal, HostSignalDelegatorFunctionForGuest Func) { + std::lock_guard lk(HostDelegatorMutex); + HostHandlers[Signal].GuestHandler = Func; + InstallHostThunk(Signal); + } + + uint64_t SignalDelegator::RegisterGuestSignalHandler(int Signal, const GuestSigAction *Action, GuestSigAction *OldAction) { std::lock_guard lk(GuestDelegatorMutex); // If we have an old signal set then give it back @@ -198,10 +336,64 @@ namespace FEXCore { } HostHandlers[Signal].GuestAction = *Action; + ThreadData.Guest_sa_mask[Signal] = Action->sa_mask; // Only attempt to install a new thunk handler if we were installing a new guest action - InstallHostThunk(Signal); + if (!InstallHostThunk(Signal)) { + UpdateHostThunk(Signal); + } } return 0; } + + uint64_t SignalDelegator::RegisterGuestSigAltStack(const stack_t *ss, stack_t *old_ss) { + bool UsingAltStack{}; + uint64_t AltStackBase = reinterpret_cast(ThreadData.GuestAltStack.ss_sp); + uint64_t AltStackEnd = AltStackBase + ThreadData.GuestAltStack.ss_size; + uint64_t GuestSP = ThreadData.Thread->State.State.gregs[X86State::REG_RSP]; + + if (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 + if (ss->ss_flags & ~SS_AUTODISARM) { + // A flag remained that isn't one of the supported ones? + return -EINVAL; + } + + // stack size needs to be MINSIGSTKSZ (0x2000) + if (ss->ss_size < X86_MINSIGSTKSZ) { + return -ENOMEM; + } + + ThreadData.GuestAltStack = *ss; + } + + return 0; + } + } diff --git a/External/FEXCore/Source/Interface/Core/SignalDelegator.h b/External/FEXCore/Source/Interface/Core/SignalDelegator.h index 0fa1ff5b5..e5609aec4 100644 --- a/External/FEXCore/Source/Interface/Core/SignalDelegator.h +++ b/External/FEXCore/Source/Interface/Core/SignalDelegator.h @@ -15,10 +15,26 @@ namespace Core { class SignalDelegator { public: + struct __attribute__((packed)) GuestSAMask { + uint64_t Val; + }; + + struct __attribute__((packed)) GuestSigAction { + union { + void (*handler)(int); + void (*sigaction)(int, siginfo_t *, void*); + } sigaction_handler; + + uint64_t sa_flags; + void (*restorer)(void); + GuestSAMask sa_mask; + }; + // Returns true if the host handled the signal // Arguments are the same as sigaction handler using HostSignalDelegatorFunction = std::function; - SignalDelegator(FEXCore::Context::Context *ctx); + using HostSignalDelegatorFunctionForGuest = std::function; + SignalDelegator(); /** * @brief Registers an emulated thread's object to a TLS object @@ -26,6 +42,7 @@ namespace Core { * Required to know which thread has received the signal when it occurs */ static void RegisterTLSState(FEXCore::Core::InternalThreadState *Thread); + static void UninstallTLSState(FEXCore::Core::InternalThreadState *Thread); /** * @brief Masks a thread from receiving any signals that aren't synchronous @@ -41,37 +58,54 @@ namespace Core { */ static void ResetThreadSignalMask(); + static bool BlockSignal(int Signal); + static bool UnblockSignal(int Signal); + /** * @brief Registers a signal handler for the host to handle a signal * * It's a process level signal handler so one must be careful */ void RegisterHostSignalHandler(int Signal, HostSignalDelegatorFunction Func); + + /** + * @brief Registers a signal handler for the host to handle a signal specifically for guest handling + * + * It's a process level signal handler so one must be careful + */ + void RegisterHostSignalHandlerForGuest(int Signal, HostSignalDelegatorFunctionForGuest Func); + /** * @brief Allows the guest to register a signal handler that is run after the host attempts to resolve the handler first */ - uint64_t RegisterGuestSignalHandler(int Signal, const struct sigaction *Action, struct sigaction *OldAction); + uint64_t RegisterGuestSignalHandler(int Signal, const GuestSigAction *Action, struct GuestSigAction *OldAction); + + uint64_t RegisterGuestSigAltStack(const stack_t *ss, stack_t *old_ss); // Called from the thunk handler to handle the signal void HandleSignal(int Signal, void *Info, void *UContext); - private: - FEXCore::Context::Context *CTX; + void SetCurrentSignal(uint32_t Signal); constexpr static size_t MAX_SIGNALS {64}; + + private: struct SignalHandler { std::atomic Installed{}; + struct sigaction HostAction{}; struct sigaction OldAction{}; HostSignalDelegatorFunction Handler{}; - struct sigaction GuestAction{}; + HostSignalDelegatorFunctionForGuest GuestHandler{}; + GuestSigAction GuestAction{}; }; SignalHandler HostHandlers[MAX_SIGNALS]{}; - void InstallHostThunk(int Signal); + bool InstallHostThunk(int Signal); + void UpdateHostThunk(int Signal); std::mutex HostDelegatorMutex; std::mutex GuestDelegatorMutex; - static void MaskSignals(int how); + static void MaskSignals(int how, int Signal = -1); }; } diff --git a/External/FEXCore/Source/Interface/HLE/Syscalls/Signals.cpp b/External/FEXCore/Source/Interface/HLE/Syscalls/Signals.cpp index fa07b6d18..b387cd413 100644 --- a/External/FEXCore/Source/Interface/HLE/Syscalls/Signals.cpp +++ b/External/FEXCore/Source/Interface/HLE/Syscalls/Signals.cpp @@ -10,7 +10,7 @@ namespace FEXCore::HLE { void RegisterSignals() { - REGISTER_SYSCALL_IMPL(rt_sigaction, [](FEXCore::Core::InternalThreadState *Thread, int signum, const struct sigaction *act, struct sigaction *oldact) -> uint64_t { + REGISTER_SYSCALL_IMPL(rt_sigaction, [](FEXCore::Core::InternalThreadState *Thread, int signum, const SignalDelegator::GuestSigAction *act, SignalDelegator::GuestSigAction *oldact) -> uint64_t { return Thread->CTX->SignalDelegation.RegisterGuestSignalHandler(signum, act, oldact); }); @@ -21,9 +21,7 @@ namespace FEXCore::HLE { }); REGISTER_SYSCALL_IMPL(sigaltstack, [](FEXCore::Core::InternalThreadState *Thread, const stack_t *ss, stack_t *old_ss) -> uint64_t { - // XXX: Pass through SignalDelegator - uint64_t Result = ::sigaltstack(ss, old_ss); - SYSCALL_ERRNO(); + return Thread->CTX->SignalDelegation.RegisterGuestSigAltStack(ss, old_ss); }); } }