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:
parent
b550545b4f
commit
3cd2ff9140
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue