Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion core/libs/prx/libkernel/Pthread/src/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ struct ThreadArgs {
PthreadEntry entry;
void* arg;
PthreadPrivate* self;
std::uint32_t guestSignalMask[4];
#ifndef _WIN32
sigset_t signalMask;
#endif
};

extern "C" int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet);

static std::atomic<thread_dtors_func_t> threadDtors{nullptr};
static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
Expand Down Expand Up @@ -378,6 +381,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
TimedWait::BindThreadWaitState(&self->waitCount);
CurrentGuestState().current = self;
RegisterStack(self);
sigprocmask_nid_postfix(3, args->guestSignalMask, nullptr);
#ifndef _WIN32
const sigset_t signalMask = args->signalMask;
#endif
Expand Down Expand Up @@ -532,7 +536,8 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread
}
if (name) p->name = name;
std::promise<bool> start;
auto args = std::make_unique<ThreadArgs>(ThreadArgs{entry, arg, p.get()});
auto args = std::make_unique<ThreadArgs>(ThreadArgs{entry, arg, p.get(), {}});
sigprocmask_nid_postfix(1, nullptr, args->guestSignalMask);
#ifdef _WIN32
SYSTEM_INFO system{};
GetSystemInfo(&system);
Expand Down
49 changes: 34 additions & 15 deletions core/libs/prx/libkernel/System/src/Signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ constexpr int SaRestart = 0x2;
constexpr int SaSiginfo = 0x40;
std::atomic<GuestHandler> handlers[MaxSignal + 1]{};
static_assert(std::atomic<GuestHandler>::is_always_lock_free);
std::atomic<std::uint32_t> blockedMask{0};
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
constexpr int GuestSigBlock = 1;
constexpr int GuestSigUnblock = 2;
constexpr int GuestSigSetmask = 3;
// FreeBSD keeps the blocked and pending sets per thread (kern_sigprocmask); a signal raised
// while blocked waits until the thread unblocks it.
thread_local GuestSignalSet blockedSignals{};
thread_local GuestSignalSet pendingSignals{};
std::uint32_t Bit(int guest) { return 1u << ((guest - 1) % 32); }
std::uint32_t& Word(GuestSignalSet& set, int guest) { return set.bits[(guest - 1) / 32]; }
bool Contains(GuestSignalSet& set, int guest) { return (Word(set, guest) & Bit(guest)) != 0; }
std::mutex registration;
GuestSigaction dispositions[MaxSignal + 1]{};
int NativeSignal(int guest) {
Expand All @@ -64,7 +72,10 @@ void Dispatch(int native) {
// Preserve the guest's persistent registration across CRT delivery.
std::signal(native, Dispatch);
#endif
if ((blockedMask.load() & (1u << (guest - 1))) != 0) return;
if (Contains(blockedSignals, guest)) {
Word(pendingSignals, guest) |= Bit(guest);
return;
}
const auto callback = handlers[guest].load();
if (reinterpret_cast<std::uintptr_t>(callback) > 1) callback(guest);
}
Expand Down Expand Up @@ -188,9 +199,15 @@ int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, Gue
int APS5_VABI raise_nid_postfix(int guest) {
const int native = NativeSignal(guest);
if (!native) { *__error_nid_postfix() = 22; return -1; }
bool ignored = false;
{
std::lock_guard lock(registration);
if (dispositions[guest].handler > 1 && !Delivered(dispositions[guest])) throw std::runtime_error("raise: SA_SIGINFO handlers are not delivered");
ignored = dispositions[guest].handler == 1;
}
if (Contains(blockedSignals, guest)) {
if (!ignored) Word(pendingSignals, guest) |= Bit(guest);
return 0;
}
const int result = std::raise(native);
if (result) *__error_nid_postfix() = 22;
Expand Down Expand Up @@ -222,20 +239,22 @@ int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previ
return 0;
}
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
std::lock_guard lock(registration);
if (previousSet != nullptr) {
previousSet->bits[0] = blockedMask.load();
previousSet->bits[1] = 0;
previousSet->bits[2] = 0;
previousSet->bits[3] = 0;
}
const GuestSignalSet previous = blockedSignals;
if (set != nullptr) {
switch (how) {
case 1: blockedMask.fetch_or(set->bits[0]); break;
case 2: blockedMask.fetch_and(~set->bits[0]); break;
case 3: blockedMask.store(set->bits[0]); break;
default: throw std::invalid_argument("_sigprocmask: invalid how");
if (how < GuestSigBlock || how > GuestSigSetmask) throw std::invalid_argument("_sigprocmask: invalid how");
for (int word = 0; word < 4; ++word) {
if (how == GuestSigBlock) blockedSignals.bits[word] |= set->bits[word];
else if (how == GuestSigUnblock) blockedSignals.bits[word] &= ~set->bits[word];
else blockedSignals.bits[word] = set->bits[word];
}
Word(blockedSignals, GuestSigkill) &= ~Bit(GuestSigkill);
Word(blockedSignals, GuestSigstop) &= ~Bit(GuestSigstop);
}
if (previousSet != nullptr) *previousSet = previous;
for (int guest = 1; guest <= MaxSignal; ++guest) {
if (!Contains(pendingSignals, guest) || Contains(blockedSignals, guest)) continue;
Word(pendingSignals, guest) &= ~Bit(guest);
raise_nid_postfix(guest);
}
return 0;
}
Expand Down
34 changes: 33 additions & 1 deletion core/libs/tests/GuestSignals.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <csignal>
#include <cstdint>
Expand All @@ -12,6 +13,8 @@ Handler APS5_VABI signal_nid_postfix(int, Handler);
int APS5_VABI raise_nid_postfix(int);
int APS5_VABI pthread_kill_nid_postfix(void*, int);
void* APS5_VABI scePthreadSelf();
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
int APS5_VABI sigaction_nid_postfix(int, const void*, void*);
int APS5_VABI sigprocmask_nid_postfix(int, const void*, void*);
int APS5_VABI pthread_sigmask_nid_postfix(int, const void*, void*);
Expand Down Expand Up @@ -39,6 +42,15 @@ void APS5_VABI Callback(int value) {
handlerReturn = reinterpret_cast<std::uintptr_t>(__builtin_return_address(0));
}
static void Require(bool value) { if (!value) std::abort(); }
GuestSignalSet childInherited{{}};
GuestSignalSet childChanged{{}};
static void* APS5_VABI ChildMask(void*) {
const GuestSignalSet hangup{{0x2, 0, 0, 0}};
sigprocmask_nid_postfix(1, nullptr, &childInherited);
sigprocmask_nid_postfix(3, &hangup, nullptr);
sigprocmask_nid_postfix(1, nullptr, &childChanged);
return nullptr;
}
int main() {
const auto invalid = reinterpret_cast<Handler>(static_cast<std::uintptr_t>(-1));
const auto ignore = reinterpret_cast<Handler>(std::uintptr_t{1});
Expand Down Expand Up @@ -139,13 +151,33 @@ int main() {
received = 0;
Require(sigprocmask_nid_postfix(1, &termMask, nullptr) == 0);
Require(raise_nid_postfix(15) == 0 && received == 0);
Require(sigprocmask_nid_postfix(2, &termMask, nullptr) == 0);
Require(sigprocmask_nid_postfix(2, &termMask, nullptr) == 0 && received == 15);
received = 0;
Require(raise_nid_postfix(15) == 0 && received == 15);
GuestSignalSet urgMask{{1u << 15, 0, 0, 0}};
received = 0;
Require(sigprocmask_nid_postfix(1, &urgMask, nullptr) == 0);
Require(raise_nid_postfix(15) == 0 && received == 15);
Require(sigprocmask_nid_postfix(2, &urgMask, nullptr) == 0);
GuestSignalSet wide{{0x20 | 0x100 | 0x10000, 0x1, 0x1, 0x80000000u}};
Require(sigprocmask_nid_postfix(3, &wide, nullptr) == 0);
Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0);
Require(previous.bits[0] == 0x20 && previous.bits[1] == 0x1 && previous.bits[2] == 0x1 && previous.bits[3] == 0x80000000u);
const GuestSignalSet none{{}};
Require(sigprocmask_nid_postfix(3, &none, nullptr) == 0);
Require(signal_nid_postfix(15, ignore) != invalid);
Require(sigprocmask_nid_postfix(1, &termMask, nullptr) == 0 && raise_nid_postfix(15) == 0);
Require(signal_nid_postfix(15, Callback) != invalid);
received = 0;
Require(sigprocmask_nid_postfix(2, &termMask, nullptr) == 0 && received == 0);
GuestSignalSet parentMask{{1u << 14, 0, 0, 0}};
Require(sigprocmask_nid_postfix(3, &parentMask, nullptr) == 0);
Pthread child = nullptr;
Require(scePthreadCreate(&child, nullptr, ChildMask, nullptr, nullptr) == 0);
Require(scePthreadJoin(child, nullptr) == 0);
Require(childInherited.bits[0] == 1u << 14 && childChanged.bits[0] == 0x2);
Require(sigprocmask_nid_postfix(1, nullptr, &previous) == 0 && previous.bits[0] == 1u << 14);
Require(sigprocmask_nid_postfix(3, &none, nullptr) == 0);

Require(GuestAlarm_nid_no_patch(0) == 0);
Require(GuestAlarm_nid_no_patch(5) == 0);
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) on Linux sends the host SIGUSR1, which the library takes over, to the target thread, so the handler runs wherever the thread was interrupted, emulator code included. On the console kernel objects are system calls and libthr defers signals while a thread holds its internal locks; here a handler that needs a host lock the interrupted code holds deadlocks, and a thread that the handler keeps waiting keeps its host locks. Host waits resume after the handler. The host calls Linux does not restart end early: sleeps that do not retry return before their time, and the poll of `sceNetEpollWait` and socket calls with a send or receive timeout return `EINTR` to the guest
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask
- [pthread_sigmask](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) shares the process-wide mask of `sigprocmask` and `_sigprocmask`, which keeps only the first 32 signals; FreeBSD keeps a mask per thread
- [sigprocmask](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) keeps the blocked and pending sets per guest thread, but a host signal that arrives from outside the guest (Ctrl+C, or `SIGTERM` from another process) is checked against the mask of whichever host thread receives it and stays pending on that thread; FreeBSD delivers it to a thread that does not block it
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) on Windows runs the handler wherever the target thread was interrupted. It waits only while the thread is inside winpthreads or restoring a context through `NtContinue`, and throws if that lasts 1 s, so a handler that calls into other host code the thread was interrupted in, such as the C runtime's heap or a lock of the emulator, can deadlock on it or reenter it
- [sceAgcDebugRaiseException](../../core/libs/prx/libSceAgc/Misc/src/Debug.cpp) (libSceAgc) - logs the exception id and returns 0, as retail hardware does nothing per [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c); the trap (`ud2`) OpenAGC describes for development hardware is not reproduced. Neither is verified on a console. The `uint32` exception id is [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/6f0cddd4404ec5203e71fea77e75fc373299ba3f/src/libs/agc.cpp)'s claim; OpenAGC declares no arguments, so the logged id may be leftover register contents
Expand Down
Loading