Extends SignalDelegator duty

It needs to track guest SigAltAction and have its own definitions for
sigaction and sa_mask. sa_mask specifically is defined differently
between the kernel and glibc.

Also fixes some issues with the SignalDelegator that posix tests found
This commit is contained in:
Ryan Houdek committed 2020-08-11 00:33:49 +01:00
1 parent 211aebcfa4
commit f705baf2f0
4 files changed
+287 -62

No files matched your search

+3 -2
View File
@@ -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<FEXCore::BlockSamplingData>();
@@ -870,6 +869,8 @@ namespace FEXCore::Context {
--IdleWaitRefCount;
IdleWaitCV.notify_all();
SignalDelegation.UninstallTLSState(Thread);
Thread->State.RunningEvents.WaitingToStart = false;
Thread->State.RunningEvents.Running = false;
}
+241 -49
View File
@@ -1,16 +1,26 @@
#include "Interface/Context/Context.h"
#include "Interface/Core/InternalThreadState.h"
#include "Interface/Core/SignalDelegator.h"
#include <FEXCore/Core/X86Enums.h>
#include <LogManager.h>
#include <string.h>
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<siginfo_t*>(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<std::mutex> lk(HostDelegatorMutex);
HostHandlers[Signal].GuestHandler = Func;
InstallHostThunk(Signal);
}
uint64_t SignalDelegator::RegisterGuestSignalHandler(int Signal, const GuestSigAction *Action, GuestSigAction *OldAction) {
std::lock_guard<std::mutex> 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<uint64_t>(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;
}
}
+41 -7
View File
@@ -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<bool(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext)>;
SignalDelegator(FEXCore::Context::Context *ctx);
using HostSignalDelegatorFunctionForGuest = std::function<bool(FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext, GuestSigAction *GuestAction, stack_t *GuestStack)>;
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<bool> 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);
};
}
+2 -4
View File
@@ -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);
});
}
}