Improve signal emulation (#4108)

* improve signal emulation

* make the sce function use the new posix ones

* ifdefing away the issues

* fix me being very tired yesterday night

* let macOS handle SIGRT signals with the native sigaction call instead of an early error return

* windows still has no clue what the fuck is going on

* the loathsome clang-formatter

* fix oact

* return the guest handler, not the host one

* Clear any existing signal mask for game threads.

* don't rely on implementation specific things

* Fix Windows support and sceKernelRaiseException bug

* Review suggestions

@kalaposfos13 suggested I push these.

---------

Co-authored-by: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com>
This commit is contained in:
kalaposfos13 2026-03-09 13:20:14 +01:00 committed by GitHub
parent cc6af03adf
commit 0579569f13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 277 additions and 74 deletions

View File

@ -788,14 +788,11 @@ static bool PatchesIllegalInstructionHandler(void* context) {
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
const auto status =
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
[[unlikely]] {
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
}
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
reinterpret_cast<u64>(code_address),
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}",
reinterpret_cast<u64>(code_address),
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
return false;
}
}

View File

@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/kernel/threads/exception.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/libs.h"
@ -13,23 +15,24 @@
#else
#include <csignal>
#endif
#include <unordered_set>
namespace Libraries::Kernel {
#ifdef _WIN32
// Windows doesn't have native versions of these, and we don't need to use them either.
static s32 NativeToOrbisSignal(s32 s) {
s32 NativeToOrbisSignal(s32 s) {
return s;
}
static s32 OrbisToNativeSignal(s32 s) {
s32 OrbisToNativeSignal(s32 s) {
return s;
}
#else
static s32 NativeToOrbisSignal(s32 s) {
s32 NativeToOrbisSignal(s32 s) {
switch (s) {
case SIGHUP:
return POSIX_SIGHUP;
@ -89,12 +92,21 @@ static s32 NativeToOrbisSignal(s32 s) {
return POSIX_SIGUSR1;
case SIGUSR2:
return POSIX_SIGUSR2;
case _SIGEMT:
return POSIX_SIGEMT;
case _SIGINFO:
return POSIX_SIGINFO;
case 0:
return 128;
default:
if (s > 0 && s < 128) {
return s;
}
UNREACHABLE_MSG("Unknown signal {}", s);
}
}
static s32 OrbisToNativeSignal(s32 s) {
s32 OrbisToNativeSignal(s32 s) {
switch (s) {
case POSIX_SIGHUP:
return SIGHUP;
@ -108,6 +120,8 @@ static s32 OrbisToNativeSignal(s32 s) {
return SIGTRAP;
case POSIX_SIGABRT:
return SIGABRT;
case POSIX_SIGEMT:
return _SIGEMT;
case POSIX_SIGFPE:
return SIGFPE;
case POSIX_SIGKILL:
@ -150,22 +164,33 @@ static s32 OrbisToNativeSignal(s32 s) {
return SIGPROF;
case POSIX_SIGWINCH:
return SIGWINCH;
case POSIX_SIGINFO:
return _SIGINFO;
case POSIX_SIGUSR1:
return SIGUSR1;
case POSIX_SIGUSR2:
return SIGUSR2;
case 128:
return 0;
default:
if (s > 0 && s < 128) {
return s;
}
UNREACHABLE_MSG("Unknown signal {}", s);
}
}
#endif
std::array<SceKernelExceptionHandler, 32> Handlers{};
#ifdef __APPLE__
#define sigisemptyset(x) (*(x) == 0)
#endif
std::array<OrbisKernelExceptionHandler, 130> Handlers{};
#ifndef _WIN64
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) {
const auto handler = Handlers[native_signum];
const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
if (handler) {
auto ctx = Ucontext{};
#ifdef __APPLE__
@ -214,6 +239,8 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context
ctx.uc_mcontext.mc_addr = reinterpret_cast<uint64_t>(inf->si_addr);
#endif
handler(NativeToOrbisSignal(native_signum), &ctx);
} else {
UNREACHABLE_MSG("Unhandled exception");
}
}
#else
@ -221,7 +248,7 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
const char* thrName = (char*)arg1;
int native_signum = reinterpret_cast<uintptr_t>(arg2);
LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName);
const auto handler = Handlers[native_signum];
const auto handler = Handlers[NativeToOrbisSignal(native_signum)];
if (handler) {
auto ctx = Ucontext{};
ctx.uc_mcontext.mc_r8 = context->R8;
@ -243,76 +270,105 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) {
ctx.uc_mcontext.mc_fs = context->SegFs;
ctx.uc_mcontext.mc_gs = context->SegGs;
handler(NativeToOrbisSignal(native_signum), &ctx);
} else {
UNREACHABLE_MSG("Unhandled exception");
}
}
#endif
int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) {
if (signum > POSIX_SIGUSR2) {
return ORBIS_KERNEL_ERROR_EINVAL;
s32 PS4_SYSV_ABI posix_sigemptyset(Sigset* s) {
s->bits[0] = 0;
s->bits[1] = 0;
return 0;
}
bool PS4_SYSV_ABI posix_sigisemptyset(Sigset* s) {
return s->bits[0] == 0 && s->bits[1] == 0;
}
s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) {
if (sig < 1 || sig > 128 || sig == POSIX_SIGTHR || sig == POSIX_SIGKILL ||
sig == POSIX_SIGSTOP) {
*__Error() = POSIX_EINVAL;
return ORBIS_FAIL;
}
#ifdef _WIN32
LOG_ERROR(Lib_Kernel, "(STUBBED) called, sig: {}", sig);
Handlers[sig] = reinterpret_cast<OrbisKernelExceptionHandler>(
act ? act->__sigaction_handler.sigaction : nullptr);
#else
s32 native_sig = OrbisToNativeSignal(sig);
if (native_sig == SIGVTALRM) {
LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE-reserved signal {}!", sig);
*__Error() = POSIX_EINVAL;
return ORBIS_FAIL;
}
#ifndef __APPLE__
if (native_sig >= __SIGRTMIN && native_sig < SIGRTMIN) {
LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE libc-reserved signal {}!", sig);
*__Error() = POSIX_EINVAL;
return ORBIS_FAIL;
}
#else
if (native_sig > SIGUSR2) {
LOG_ERROR(Lib_Kernel,
"Guest is attempting to use SIGRT signals, which aren't available on this "
"platform (signal: {})!",
sig);
}
LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
int const native_signum = OrbisToNativeSignal(signum);
#ifdef __APPLE__
ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!");
#endif
ASSERT_MSG(!Handlers[native_signum], "Invalid parameters");
Handlers[native_signum] = handler;
#ifndef _WIN64
if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
LOG_INFO(Lib_Kernel, "called, sig: {}, native sig: {}", sig, native_sig);
struct sigaction native_act{};
if (act) {
native_act.sa_flags = act->sa_flags; // todo check compatibility, on Linux it seems fine
native_act.sa_sigaction =
reinterpret_cast<decltype(native_act.sa_sigaction)>(SigactionHandler);
if (!posix_sigisemptyset(&act->sa_mask)) {
LOG_ERROR(Lib_Kernel, "Unhandled sa_mask: {:x}", act->sa_mask.bits[0]);
}
}
auto const prev_handler = Handlers[sig];
Handlers[sig] = reinterpret_cast<OrbisKernelExceptionHandler>(
act ? act->__sigaction_handler.sigaction : nullptr);
if (native_sig == SIGSEGV || native_sig == SIGBUS || native_sig == SIGILL) {
return ORBIS_OK; // These are handled in Core::SignalHandler
}
struct sigaction act = {};
act.sa_flags = SA_SIGINFO | SA_RESTART;
act.sa_sigaction = reinterpret_cast<decltype(act.sa_sigaction)>(SigactionHandler);
sigemptyset(&act.sa_mask);
sigaction(native_signum, &act, nullptr);
#endif
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
if (signum > POSIX_SIGUSR2) {
return ORBIS_KERNEL_ERROR_EINVAL;
if (native_sig > 127) {
LOG_WARNING(Lib_Kernel, "We can't install a handler for native signal {}!", native_sig);
return ORBIS_OK;
}
int const native_signum = OrbisToNativeSignal(signum);
if (!Handlers[native_signum]) {
LOG_WARNING(Lib_Kernel, "removing non-installed handler for signum {}", signum);
return ORBIS_KERNEL_ERROR_EINVAL;
struct sigaction native_oact{};
s32 ret = sigaction(native_sig, act ? &native_act : nullptr, oact ? &native_oact : nullptr);
if (oact) {
oact->sa_flags = native_oact.sa_flags;
oact->__sigaction_handler.sigaction =
reinterpret_cast<decltype(oact->__sigaction_handler.sigaction)>(prev_handler);
if (!sigisemptyset(&native_oact.sa_mask)) {
LOG_ERROR(Lib_Kernel, "Unhandled sa_mask");
}
}
Handlers[native_signum] = nullptr;
#ifndef _WIN64
if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) {
struct sigaction action{};
action.sa_sigaction = Core::SignalHandler;
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&action.sa_mask);
ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0,
"Failed to reinstate original signal handler for signal {}", native_signum);
} else {
struct sigaction act = {};
act.sa_flags = SA_SIGINFO | SA_RESTART;
act.sa_sigaction = nullptr;
sigemptyset(&act.sa_mask);
sigaction(native_signum, &act, nullptr);
if (ret < 0) {
LOG_ERROR(Lib_Kernel, "sigaction failed: {}", strerror(errno));
*__Error() = ErrnoToSceKernelError(errno);
return ORBIS_FAIL;
}
#endif
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
if (signum != POSIX_SIGUSR1) {
return ORBIS_KERNEL_ERROR_EINVAL;
s32 PS4_SYSV_ABI posix_pthread_kill(PthreadT thread, s32 sig) {
if (sig < 1 || sig > 128) { // off-by-one error?
return POSIX_EINVAL;
}
LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name);
int const native_signum = OrbisToNativeSignal(signum);
LOG_WARNING(Lib_Kernel, "Raising signal {} on thread '{}'", sig, thread->name);
int const native_signum = OrbisToNativeSignal(sig);
#ifndef _WIN64
const auto pthr = reinterpret_cast<pthread_t>(thread->native_thr.GetHandle());
const auto ret = pthread_kill(pthr, native_signum);
if (ret != 0) {
LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name,
strerror(ret));
strerror(errno));
}
#else
USER_APC_OPTION option;
@ -326,6 +382,67 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
return ORBIS_OK;
}
// libkernel has a check in sceKernelInstallExceptionHandler and sceKernelRemoveExceptionHandler for
// validating if the application requested a handler for an allowed signal or not. However, that is
// just a wrapper for sigaction, which itself does not have any such restrictions, and therefore
// this check is ridiculously trivial to go around. This, however, means that we need to support all
// 127 - 3 possible signals, even if realistically, only homebrew will use most of them.
static std::unordered_set<s32> orbis_allowed_signals{
POSIX_SIGHUP, POSIX_SIGILL, POSIX_SIGFPE, POSIX_SIGBUS, POSIX_SIGSEGV, POSIX_SIGUSR1,
};
int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, OrbisKernelExceptionHandler handler) {
if (!orbis_allowed_signals.contains(signum)) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
if (Handlers[signum] != nullptr) {
return ORBIS_KERNEL_ERROR_EAGAIN;
}
LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum);
Sigaction act = {};
act.sa_flags = POSIX_SA_SIGINFO | POSIX_SA_RESTART;
act.__sigaction_handler.sigaction =
reinterpret_cast<decltype(act.__sigaction_handler.sigaction)>(handler);
posix_sigemptyset(&act.sa_mask);
s32 ret = posix_sigaction(signum, &act, nullptr);
if (ret < 0) {
LOG_ERROR(Lib_Kernel, "Failed to add handler for signal {}: {}", signum,
strerror(*__Error()));
return ErrnoToSceKernelError(*__Error());
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) {
if (!orbis_allowed_signals.contains(signum)) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
int const native_signum = OrbisToNativeSignal(signum);
Handlers[signum] = nullptr;
Sigaction act = {};
act.sa_flags = POSIX_SA_SIGINFO;
act.__sigaction_handler.sigaction = nullptr;
posix_sigemptyset(&act.sa_mask);
s32 ret = posix_sigaction(signum, &act, nullptr);
if (ret < 0) {
LOG_ERROR(Lib_Kernel, "Failed to remove handler for signal {}: {}", signum,
strerror(*__Error()));
return ErrnoToSceKernelError(*__Error());
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) {
if (signum != POSIX_SIGUSR1) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
s32 ret = posix_pthread_kill(thread, signum);
if (ret < 0) {
return ErrnoToSceKernelError(ret);
}
return ret;
}
s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) {
if (unk != 0) {
return ORBIS_KERNEL_ERROR_EINVAL;
@ -352,6 +469,13 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
sceKernelDebugRaiseExceptionOnReleaseMode);
LIB_FUNCTION("WkwEd3N7w0Y", "libkernel", 1, "libkernel", sceKernelInstallExceptionHandler);
LIB_FUNCTION("Qhv5ARAoOEc", "libkernel", 1, "libkernel", sceKernelRemoveExceptionHandler);
LIB_FUNCTION("KiJEPEWRyUY", "libkernel", 1, "libkernel", posix_sigaction);
LIB_FUNCTION("+F7C-hdk7+E", "libkernel", 1, "libkernel", posix_sigemptyset);
LIB_FUNCTION("yH-uQW3LbX0", "libkernel", 1, "libkernel", posix_pthread_kill);
LIB_FUNCTION("KiJEPEWRyUY", "libScePosix", 1, "libkernel", posix_sigaction);
LIB_FUNCTION("+F7C-hdk7+E", "libScePosix", 1, "libkernel", posix_sigemptyset);
LIB_FUNCTION("yH-uQW3LbX0", "libScePosix", 1, "libkernel", posix_pthread_kill);
}
} // namespace Libraries::Kernel

View File

@ -11,7 +11,7 @@ class SymbolsResolver;
namespace Libraries::Kernel {
using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
using OrbisKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
constexpr s32 POSIX_SIGHUP = 1;
constexpr s32 POSIX_SIGINT = 2;
@ -47,6 +47,23 @@ constexpr s32 POSIX_SIGUSR2 = 31;
constexpr s32 POSIX_SIGTHR = 32;
constexpr s32 POSIX_SIGLIBRT = 33;
#ifdef __linux__
constexpr s32 _SIGEMT = 128;
constexpr s32 _SIGINFO = 129;
#elif !defined(_WIN32)
constexpr s32 _SIGEMT = SIGEMT;
constexpr s32 _SIGINFO = SIGINFO;
#endif
constexpr s32 POSIX_SA_NOCLDSTOP = 1;
constexpr s32 POSIX_SA_NOCLDWAIT = 2;
constexpr s32 POSIX_SA_SIGINFO = 4;
constexpr s32 POSIX_SA_ONSTACK = 0x08000000;
constexpr s32 POSIX_SA_RESTART = 0x10000000;
constexpr s32 POSIX_SA_NODEFER = 0x40000000;
constexpr s32 POSIX_SA_RESETHAND = 0x80000000;
constexpr s32 POSIX_SA_RESTORER = 0x04000000;
struct Mcontext {
u64 mc_onstack;
u64 mc_rdi;
@ -101,17 +118,74 @@ struct Sigset {
u64 bits[2];
};
union Sigval {
/* Members as suggested by Annex C of POSIX 1003.1b. */
int sival_int;
void* sival_ptr;
/* 6.0 compatibility */
int sigval_int;
void* sigval_ptr;
};
struct Siginfo {
int _si_signo; /* signal number */
int _si_errno; /* errno association */
/*
* Cause of signal, one of the SI_ macros or signal-specific
* values, i.e. one of the FPE_... values for SIGFPE. This
* value is equivalent to the second argument to an old-style
* FreeBSD signal handler.
*/
int _si_code; /* signal code */
s32 _si_pid; /* sending process */
u32 _si_uid; /* sender's ruid */
int _si_status; /* exit value */
void* _si_addr; /* faulting instruction */
union Sigval _si_value; /* signal value */
union {
struct {
int _trapno; /* machine specific trap code */
} _fault;
struct {
int _timerid;
int _overrun;
} _timer;
struct {
int _mqd;
} _mesgq;
struct {
long _band; /* band event for SIGPOLL */
} _poll; /* was this ever used ? */
struct {
long __spare1__;
int __spare2__[7];
} __spare__;
} _reason;
};
struct Sigaction {
union {
void (*handler)(int);
void (*sigaction)(int, struct Siginfo*, void*);
} __sigaction_handler;
int sa_flags;
Sigset sa_mask;
};
struct Ucontext {
struct Sigset uc_sigmask;
int field1_0x10[12];
struct Mcontext uc_mcontext;
struct Ucontext* uc_link;
struct ExStack uc_stack;
Mcontext uc_mcontext;
Ucontext* uc_link;
ExStack uc_stack;
int uc_flags;
int __spare[4];
int field7_0x4f4[3];
};
s32 NativeToOrbisSignal(s32 s);
s32 OrbisToNativeSignal(s32 s);
void RegisterException(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel

View File

@ -665,6 +665,7 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
LIB_FUNCTION("Jmi+9w9u0E4", "libkernel", 1, "libkernel", posix_pthread_create_name_np);
LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);

View File

@ -22,6 +22,10 @@
#include "core/tls.h"
#include "ipc/ipc.h"
#ifndef _WIN32
#include <signal.h>
#endif
namespace Core {
static PS4_SYSV_ABI void ProgramExitFunc() {
@ -107,6 +111,11 @@ void Linker::Execute(const std::vector<std::string>& args) {
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("Game:Main");
#ifndef _WIN32 // Clear any existing signal mask for game threads.
sigset_t emptyset;
sigemptyset(&emptyset);
pthread_sigmask(SIG_SETMASK, &emptyset, nullptr);
#endif
if (auto& ipc = IPC::Instance()) {
ipc.WaitForStart();
}

View File

@ -21,7 +21,7 @@
#ifndef _WIN32
namespace Libraries::Kernel {
void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context);
extern std::array<SceKernelExceptionHandler, 32> Handlers;
extern std::array<OrbisKernelExceptionHandler, 32> Handlers;
} // namespace Libraries::Kernel
#endif
@ -86,7 +86,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
// If the guest has installed a custom signal handler, and the access violation didn't
// come from HLE memory tracking, pass the signal on
if (Libraries::Kernel::Handlers[sig]) {
if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
Libraries::Kernel::SigactionHandler(sig, info,
reinterpret_cast<ucontext_t*>(raw_context));
return;
@ -99,7 +99,7 @@ void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
}
case SIGILL:
if (!signals->DispatchIllegalInstruction(raw_context)) {
if (Libraries::Kernel::Handlers[sig]) {
if (Libraries::Kernel::Handlers[Libraries::Kernel::NativeToOrbisSignal(sig)]) {
Libraries::Kernel::SigactionHandler(sig, info,
reinterpret_cast<ucontext_t*>(raw_context));
return;

View File

@ -10,10 +10,8 @@
#ifdef _WIN32
#define SIGSLEEP -1
#elif defined(__APPLE__)
#define SIGSLEEP SIGVTALRM
#else
#define SIGSLEEP SIGRTMAX
#define SIGSLEEP SIGVTALRM
#endif
namespace Core {