fix(libkernel): take a redirected delivery from rsp and the live registers

A thread suspended inside a system call returns through sysret, which
overwrites rcx and r11, so the delivery address passed in rcx was
garbage when the redirected thread resumed. PPSA12544 crashed that way
on its SaveDataProcessQueue thread, blocked in a host wait when Unity's
GC raised SIGUSR1. The redirected rip is now an entry stub that takes
the delivery from its fixed place above rsp. It also stores the live
general registers into the delivery's context first: the snapshot taken
while the thread was in the system call still holds the call number in
rax instead of its result, which the restore would have handed back to
the wait.

The guest test raises at a thread blocked on a host mutex in 20 rounds
and expects each delivery once the wait returns; it fails without this
change.

AI-assisted: yes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Merijn 2026-10-06 15:41:25 +02:00
parent b550545b4f
commit 3cd2ff9140
2 changed files with 67 additions and 2 deletions

View File

@ -13,6 +13,9 @@
#endif
extern "C" Pthread APS5_VABI scePthreadSelf();
#ifdef _WIN32
extern "C" void Aps5RedirectedEntryStub();
#endif
namespace {
@ -179,6 +182,9 @@ void CALLBACK WaitingEntry(ULONG_PTR parameter) {
Deliver(handler, signum, context);
}
static_assert(HomeArea + 8 == 40, "Aps5RedirectedEntryStub finds the delivery 40 bytes above its stack pointer");
static_assert(offsetof(Delivery, context) == 16 && offsetof(CONTEXT, Rax) == 0x78 && offsetof(CONTEXT, Rbp) == 0xa0 && offsetof(CONTEXT, R15) == 0xf0, "Aps5RedirectedEntryStub stores the live registers into the delivery's context");
void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
if (thread == scePthreadSelf()) {
CONTEXT context{};
@ -212,8 +218,7 @@ void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
std::memcpy(reinterpret_cast<void*>(slot), &delivery, sizeof(Delivery));
CONTEXT redirected = delivery.context;
redirected.Rsp = slot - HomeArea - 8;
redirected.Rip = reinterpret_cast<DWORD64>(&RedirectedEntry);
redirected.Rcx = slot;
redirected.Rip = reinterpret_cast<DWORD64>(&Aps5RedirectedEntryStub);
if (!SetThreadContext(native, &redirected)) {
ResumeThread(native);
throw std::runtime_error("sceKernelRaiseException: cannot redirect the target thread");
@ -224,6 +229,32 @@ void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
}
#ifdef _WIN32
extern "C" [[noreturn]] void Aps5RedirectedEntry(void* delivery) {
RedirectedEntry(static_cast<Delivery*>(delivery));
}
asm(".text\n"
".globl Aps5RedirectedEntryStub\n"
"Aps5RedirectedEntryStub:\n"
" movq %rax, 176(%rsp)\n"
" movq %rcx, 184(%rsp)\n"
" movq %rdx, 192(%rsp)\n"
" movq %rbx, 200(%rsp)\n"
" movq %rbp, 216(%rsp)\n"
" movq %rsi, 224(%rsp)\n"
" movq %rdi, 232(%rsp)\n"
" movq %r8, 240(%rsp)\n"
" movq %r9, 248(%rsp)\n"
" movq %r10, 256(%rsp)\n"
" movq %r11, 264(%rsp)\n"
" movq %r12, 272(%rsp)\n"
" movq %r13, 280(%rsp)\n"
" movq %r14, 288(%rsp)\n"
" movq %r15, 296(%rsp)\n"
" leaq 40(%rsp), %rcx\n"
" jmp Aps5RedirectedEntry\n");
#endif
extern "C" {
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler) {

View File

@ -4,6 +4,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <thread>
@ -67,6 +68,22 @@ static void* APS5_VABI Waiting(void* arg) {
return nullptr;
}
static std::mutex hostLock;
static constexpr int HostRounds = 20;
static std::atomic<int> hostRound{0};
static void* APS5_VABI HostBlocked(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
for (int round = 0; round < HostRounds; ++round) {
while (hostRound.load() != round) std::this_thread::yield();
worker.started.store(true);
std::lock_guard lock(hostLock);
}
return nullptr;
}
static void ExpectDelivery(int before, std::thread::id thread) {
for (int attempt = 0; attempt < 5000 && calls.load() == before; ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(calls.load() == before + 1);
@ -114,5 +131,22 @@ int main() {
Require(waiting.waitResult == 0);
Require(sceKernelDeleteSema(waiting.sem) == 0);
Worker blocked;
Pthread blockedThread = nullptr;
hostLock.lock();
Require(scePthreadCreate(&blockedThread, nullptr, HostBlocked, &blocked, "host blocked") == 0);
for (int round = 0; round < HostRounds; ++round) {
while (!blocked.started.load()) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
Require(sceKernelRaiseException(blockedThread, SIGUSR1) == 0);
hostLock.unlock();
ExpectDelivery(1 + 2 * Repeats + round, blocked.id);
hostLock.lock();
blocked.started.store(false);
hostRound.store(round + 1);
}
hostLock.unlock();
Require(scePthreadJoin(blockedThread, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0);
}