diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index cfaf2a850..aef934025 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -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(slot), &delivery, sizeof(Delivery)); CONTEXT redirected = delivery.context; redirected.Rsp = slot - HomeArea - 8; - redirected.Rip = reinterpret_cast(&RedirectedEntry); - redirected.Rcx = slot; + redirected.Rip = reinterpret_cast(&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)); +} +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) { diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index f07d17217..c6cfd629a 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -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 hostRound{0}; + +static void* APS5_VABI HostBlocked(void* arg) { + auto& worker = *static_cast(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); }