Merge pull request #760 from merwie/feat/kernel-raise-exception-windows

feat(libkernel): deliver sceKernelRaiseException on Windows
This commit is contained in:
Adrià 2026-10-07 22:27:39 +02:00 committed by GitHub
commit e674a6384d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 513 additions and 11 deletions

View File

@ -284,6 +284,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_exception_handler PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
if(WIN32)
add_test_executable(guest_raise_exception_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseException.cpp)
target_include_directories(guest_raise_exception_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_raise_exception_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_raise_exception_tests)
add_test(NAME guest_raise_exception COMMAND guest_raise_exception_tests)
set_tests_properties(guest_raise_exception PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_sync_on_address_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyncOnAddress.cpp)
target_include_directories(guest_sync_on_address_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_sync_on_address_tests PRIVATE libkernel libc)

View File

@ -85,6 +85,7 @@ struct PthreadPrivate {
std::atomic<unsigned> references{2};
void* stackAddress = nullptr;
std::size_t stackSize = 0;
std::atomic<int> waitCount{0};
std::atomic<KernelCpumask> affinity{DEFAULT_THREAD_AFFINITY};
std::atomic<int> priority{DEFAULT_THREAD_PRIORITY};
std::mutex nameLock;

View File

@ -174,6 +174,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
const auto entry = args->entry;
void* arg = args->arg;
PthreadPrivate* self = args->self;
TimedWait::BindThreadWaitState(&self->waitCount);
if (!self->stackAddress) SetStackFromHost(self);
currentThread = self;
RegisterStack(self);
@ -407,6 +408,7 @@ Pthread APS5_VABI scePthreadSelf() {
adopted->threadId = std::this_thread::get_id();
adopted->_detached = true;
adopted->references.store(1, std::memory_order_relaxed);
TimedWait::BindThreadWaitState(&adopted->waitCount);
SetStackFromHost(adopted.get());
currentThread = adopted.release();
}

View File

@ -3,11 +3,85 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#include <array>
#include <cstring>
#include <memory>
#include <mutex>
#include <stdexcept>
#ifdef _WIN32
#include <windows.h>
#endif
extern "C" Pthread APS5_VABI scePthreadSelf();
#ifdef _WIN32
extern "C" void Aps5RedirectedEntryStub();
#endif
namespace {
struct GuestMcontext {
std::uint64_t onstack;
std::uint64_t rdi;
std::uint64_t rsi;
std::uint64_t rdx;
std::uint64_t rcx;
std::uint64_t r8;
std::uint64_t r9;
std::uint64_t rax;
std::uint64_t rbx;
std::uint64_t rbp;
std::uint64_t r10;
std::uint64_t r11;
std::uint64_t r12;
std::uint64_t r13;
std::uint64_t r14;
std::uint64_t r15;
std::uint32_t trapno;
std::uint16_t fs;
std::uint16_t gs;
std::uint64_t addr;
std::uint32_t flags;
std::uint16_t es;
std::uint16_t ds;
std::uint64_t err;
std::uint64_t rip;
std::uint64_t cs;
std::uint64_t rflags;
std::uint64_t rsp;
std::uint64_t ss;
std::uint64_t len;
std::uint64_t fpformat;
std::uint64_t ownedfp;
std::uint64_t lbrfrom;
std::uint64_t lbrto;
std::uint64_t aux1;
std::uint64_t aux2;
std::uint64_t fpstate[104];
std::uint64_t fsbase;
std::uint64_t gsbase;
std::uint64_t spare[6];
};
struct GuestUcontext {
std::uint32_t sigmask[4];
std::int32_t reserved[12];
GuestMcontext mcontext;
GuestUcontext* link;
void* stackPointer;
std::uint64_t stackSize;
std::int32_t stackFlags;
std::int32_t stackAlign;
std::int32_t flags;
std::int32_t spare[4];
std::int32_t tail[3];
};
static_assert(offsetof(GuestUcontext, mcontext) == 0x40);
static_assert(offsetof(GuestUcontext, mcontext) + offsetof(GuestMcontext, rsp) == 0xf8);
using GuestExceptionHandler = void (APS5_VABI *)(int, void*);
constexpr std::array<int, 6> AllowedSignals{1, 4, 8, 10, 11, 30};
std::mutex handlersLock;
@ -19,8 +93,179 @@ bool Allowed(int signum) {
return false;
}
GuestExceptionHandler Handler(int signum) {
std::lock_guard lock(handlersLock);
return reinterpret_cast<GuestExceptionHandler>(handlers[signum]);
}
#ifdef _WIN32
constexpr std::size_t RedZone = 128;
constexpr std::size_t HomeArea = 32;
struct Delivery {
GuestExceptionHandler handler;
int signum;
CONTEXT context;
};
void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) {
GuestUcontext ucontext{};
auto& m = ucontext.mcontext;
m.rdi = context.Rdi;
m.rsi = context.Rsi;
m.rdx = context.Rdx;
m.rcx = context.Rcx;
m.r8 = context.R8;
m.r9 = context.R9;
m.rax = context.Rax;
m.rbx = context.Rbx;
m.rbp = context.Rbp;
m.r10 = context.R10;
m.r11 = context.R11;
m.r12 = context.R12;
m.r13 = context.R13;
m.r14 = context.R14;
m.r15 = context.R15;
m.rip = context.Rip;
m.rsp = context.Rsp;
m.rflags = context.EFlags;
m.cs = context.SegCs;
m.ss = context.SegSs;
m.len = sizeof(GuestMcontext);
static_assert(sizeof(context.FltSave) <= sizeof(m.fpstate));
std::memcpy(m.fpstate, &context.FltSave, sizeof(context.FltSave));
handler(signum, &ucontext);
context.Rdi = m.rdi;
context.Rsi = m.rsi;
context.Rdx = m.rdx;
context.Rcx = m.rcx;
context.R8 = m.r8;
context.R9 = m.r9;
context.Rax = m.rax;
context.Rbx = m.rbx;
context.Rbp = m.rbp;
context.R10 = m.r10;
context.R11 = m.r11;
context.R12 = m.r12;
context.R13 = m.r13;
context.R14 = m.r14;
context.R15 = m.r15;
context.Rip = m.rip;
context.Rsp = m.rsp;
context.EFlags = static_cast<DWORD>(m.rflags);
std::memcpy(&context.FltSave, m.fpstate, sizeof(context.FltSave));
}
[[noreturn]] void RedirectedEntry(Delivery* delivery) {
CONTEXT context = delivery->context;
Deliver(delivery->handler, delivery->signum, context);
RtlRestoreContext(&context, nullptr);
std::abort();
}
bool StackWritable(DWORD64 low, DWORD64 high) {
for (DWORD64 address = low; address < high;) {
MEMORY_BASIC_INFORMATION info{};
if (VirtualQuery(reinterpret_cast<void*>(address), &info, sizeof(info)) != sizeof(info)) return false;
if (info.State != MEM_COMMIT || (info.Protect & PAGE_GUARD) != 0 || (info.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) == 0) return false;
address = reinterpret_cast<DWORD64>(info.BaseAddress) + info.RegionSize;
}
return true;
}
void CALLBACK WaitingEntry(ULONG_PTR parameter) {
auto* delivery = reinterpret_cast<Delivery*>(parameter);
const auto handler = delivery->handler;
const int signum = delivery->signum;
delete delivery;
CONTEXT context{};
RtlCaptureContext(&context);
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");
bool Exited(HANDLE native) {
return WaitForSingleObject(native, 0) == WAIT_OBJECT_0;
}
bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
if (thread == scePthreadSelf()) {
CONTEXT context{};
RtlCaptureContext(&context);
Deliver(handler, signum, context);
return true;
}
const auto native = static_cast<HANDLE>(thread->nativeHandle);
auto queued = std::make_unique<Delivery>(Delivery{handler, signum, {}});
if (SuspendThread(native) == static_cast<DWORD>(-1)) {
if (Exited(native)) return false;
throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread");
}
if (Exited(native)) {
ResumeThread(native);
return false;
}
if (thread->waitCount.load(std::memory_order_seq_cst) > 0) {
const bool accepted = QueueUserAPC(WaitingEntry, native, reinterpret_cast<ULONG_PTR>(queued.get())) != 0;
ResumeThread(native);
if (!accepted) throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread");
queued.release();
return true;
}
alignas(16) Delivery delivery{handler, signum, {}};
delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
if (!GetThreadContext(native, &delivery.context)) {
ResumeThread(native);
throw std::runtime_error("sceKernelRaiseException: cannot read the target thread context");
}
const DWORD64 slot = (delivery.context.Rsp - RedZone - sizeof(Delivery)) & ~static_cast<DWORD64>(15);
if (!StackWritable(slot - HomeArea - 8, delivery.context.Rsp - RedZone)) {
ResumeThread(native);
throw std::runtime_error("sceKernelRaiseException: the target thread stack below its red zone is not committed");
}
std::memcpy(reinterpret_cast<void*>(slot), &delivery, sizeof(Delivery));
CONTEXT redirected = delivery.context;
redirected.Rsp = slot - HomeArea - 8;
redirected.Rip = reinterpret_cast<DWORD64>(&Aps5RedirectedEntryStub);
if (!SetThreadContext(native, &redirected)) {
ResumeThread(native);
throw std::runtime_error("sceKernelRaiseException: cannot redirect the target thread");
}
ResumeThread(native);
return true;
}
#endif
}
#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) {
@ -39,10 +284,16 @@ int APS5_VABI sceKernelRemoveExceptionHandler(int signum) {
}
int APS5_VABI sceKernelRaiseException(Pthread thread, int signum) {
(void)thread;
(void)signum;
if (signum != 30) return SCE_KERNEL_ERROR_EINVAL;
if (thread == nullptr || thread->_finished.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH;
const auto handler = Handler(signum);
if (handler == nullptr) throw std::runtime_error("sceKernelRaiseException: no handler installed for the signal");
#ifdef _WIN32
return RaiseOn(thread, handler, signum) ? 0 : SCE_KERNEL_ERROR_ESRCH;
#else
NotImplemented_nid_no_patch(__func__);
return 0;
#endif
}
void APS5_VABI sceKernelDebugRaiseException(int c1, int c2) {

View File

@ -17,8 +17,37 @@ namespace {
constexpr std::uint64_t YIELD_BELOW_NANOS = 50000ULL;
constexpr std::uint64_t LEAD_NANOS = 500000ULL;
thread_local std::atomic<int> ownWaitState{0};
thread_local std::atomic<int>* waitState = &ownWaitState;
#ifdef _WIN32
using NtTestAlertFunction = LONG(NTAPI*)();
void DrainApcs() {
static const auto testAlert = reinterpret_cast<NtTestAlertFunction>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")));
if (testAlert) testAlert();
else SleepEx(0, TRUE);
}
DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) {
waitState->fetch_add(1, std::memory_order_seq_cst);
DWORD result;
do {
result = WaitForMultipleObjectsEx(count, handles, FALSE, milliseconds, TRUE);
} while (result == WAIT_IO_COMPLETION);
waitState->fetch_sub(1, std::memory_order_seq_cst);
DrainApcs();
return result;
}
void AlertableSleep(DWORD milliseconds) {
waitState->fetch_add(1, std::memory_order_seq_cst);
SleepEx(milliseconds, TRUE);
waitState->fetch_sub(1, std::memory_order_seq_cst);
DrainApcs();
}
void WINAPI DestroyWaiter(void* value);
DWORD WaiterSlotIndex() {
@ -36,7 +65,7 @@ bool TimerWait(HANDLE timer, std::uint64_t nanos) {
due.QuadPart = -static_cast<LONGLONG>(nanos / 100ULL);
if (due.QuadPart == 0) due.QuadPart = -1;
if (!SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE)) return false;
return WaitForSingleObject(timer, INFINITE) == WAIT_OBJECT_0;
return AlertableWait(1, &timer, INFINITE) == WAIT_OBJECT_0;
}
DWORD WholeMilliseconds(std::uint64_t nanos) {
@ -51,7 +80,7 @@ void SleepNanosCoarse(std::uint64_t nanos) {
}
thread_local HANDLE timer = CreateHighResolutionTimer();
if (TimerWait(timer, nanos)) return;
Sleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
AlertableSleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
}
#endif
@ -90,7 +119,7 @@ Waiter* ThisThreadWaiter() {
}
bool EventSet(HANDLE event, DWORD milliseconds) {
return WaitForSingleObject(event, milliseconds) == WAIT_OBJECT_0;
return AlertableWait(1, &event, milliseconds) == WAIT_OBJECT_0;
}
bool WaitEventUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
@ -104,7 +133,7 @@ bool WaitEventUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
due.QuadPart = -static_cast<LONGLONG>(bulk / 100ULL);
if (waiter->timer && SetWaitableTimer(waiter->timer, &due, 0, nullptr, nullptr, FALSE)) {
HANDLE handles[2] = {waiter->event, waiter->timer};
if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) == WAIT_OBJECT_0) return true;
if (AlertableWait(2, handles, INFINITE) == WAIT_OBJECT_0) return true;
continue;
}
if (EventSet(waiter->event, WholeMilliseconds(bulk))) return true;
@ -140,7 +169,7 @@ void Condition::unlink(Waiter* waiter) {
}
void Condition::waitSignal(Waiter* waiter) {
WaitForSingleObject(waiter->event, INFINITE);
AlertableWait(1, &waiter->event, INFINITE);
}
bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
@ -150,7 +179,7 @@ bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
unlink(waiter);
return false;
}
EventSet(waiter->event, 0);
WaitForSingleObject(waiter->event, 0);
return true;
}
@ -190,6 +219,10 @@ void Condition::NotifyAll() {
#endif
void BindThreadWaitState(std::atomic<int>* state) {
waitState = state != nullptr ? state : &ownWaitState;
}
bool Coarse() {
#ifdef _WIN32
static const bool coarse = std::getenv("APS5_COARSE_TIMED_WAITS") != nullptr;
@ -233,7 +266,7 @@ void SleepUntil(std::uint64_t deadlineNanos) {
std::uint64_t now = NowNanos();
if (deadlineNanos > now + LEAD_NANOS) {
thread_local HANDLE timer = CreateHighResolutionTimer();
if (!TimerWait(timer, deadlineNanos - now - LEAD_NANOS)) Sleep(WholeMilliseconds(deadlineNanos - now - LEAD_NANOS));
if (!TimerWait(timer, deadlineNanos - now - LEAD_NANOS)) AlertableSleep(WholeMilliseconds(deadlineNanos - now - LEAD_NANOS));
now = NowNanos();
}
while (now < deadlineNanos) {
@ -251,7 +284,7 @@ void PollSleepUntil(std::uint64_t deadlineNanos) {
const std::uint64_t now = NowNanos();
if (deadlineNanos <= now) return;
thread_local HANDLE timer = CreateHighResolutionTimer();
if (!TimerWait(timer, deadlineNanos - now)) Sleep(WholeMilliseconds(deadlineNanos - now));
if (!TimerWait(timer, deadlineNanos - now)) AlertableSleep(WholeMilliseconds(deadlineNanos - now));
#else
SleepUntil(deadlineNanos);
#endif

View File

@ -1,6 +1,7 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
#define CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
@ -9,6 +10,7 @@
namespace TimedWait {
bool Coarse();
void BindThreadWaitState(std::atomic<int>* state);
std::uint64_t NowNanos();
std::uint64_t DeadlineNanos(std::uint64_t microseconds);
std::uint64_t RemainingMicros(std::uint64_t deadlineNanos);

View File

@ -0,0 +1,199 @@
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <thread>
extern "C" {
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler);
int APS5_VABI sceKernelRemoveExceptionHandler(int signum);
int APS5_VABI sceKernelRaiseException(Pthread thread, int signum);
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
Pthread APS5_VABI scePthreadSelf();
int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt);
int APS5_VABI sceKernelDeleteSema(KernelSema sem);
int APS5_VABI sceKernelSignalSema(KernelSema sem, int count);
int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time);
}
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast<int>(0x80020003);
static constexpr int SIGUSR1 = 30;
static constexpr int Repeats = 100;
static void Require(bool value) { if (!value) std::abort(); }
static std::atomic<int> calls{0};
static std::atomic<std::thread::id> handlerThread;
static std::atomic<std::uint64_t> handlerRsp{0};
static std::atomic<std::uintptr_t> handlerFrame{0};
static void APS5_VABI Handler(int signum, void* context) {
Require(signum == SIGUSR1);
std::uint64_t rsp = 0;
std::memcpy(&rsp, static_cast<unsigned char*>(context) + 0xf8, sizeof(rsp));
int local = 0;
handlerFrame.store(reinterpret_cast<std::uintptr_t>(&local));
handlerRsp.store(rsp);
handlerThread.store(std::this_thread::get_id());
calls.fetch_add(1);
}
struct Worker {
std::atomic<bool> started{false};
std::atomic<bool> stop{false};
std::thread::id id;
KernelSema sem = nullptr;
int waitResult = -1;
};
static void* APS5_VABI Busy(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
worker.started.store(true);
volatile std::uint64_t spins = 0;
while (!worker.stop.load()) spins = spins + 1;
return nullptr;
}
static void* APS5_VABI Waiting(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
worker.started.store(true);
worker.waitResult = sceKernelWaitSema(worker.sem, 1, nullptr);
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 constexpr int LeavingRounds = 200;
static std::atomic<int> leavingRound{0};
static void* APS5_VABI Leaving(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
for (int round = 0; round < LeavingRounds; ++round) {
worker.started.store(true);
Require(sceKernelWaitSema(worker.sem, 1, nullptr) == 0);
volatile std::uint64_t spins = 0;
while (leavingRound.load() == round) spins = spins + 1;
}
return nullptr;
}
static std::atomic<bool> finishedReturned{false};
static void* APS5_VABI Finished(void*) {
finishedReturned.store(true);
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);
Require(handlerThread.load() == thread);
Require(handlerRsp.load() != 0 && handlerFrame.load() < handlerRsp.load());
}
int main() {
Require(sceKernelRaiseException(scePthreadSelf(), 11) == SCE_KERNEL_ERROR_EINVAL);
bool rejected = false;
try {
sceKernelRaiseException(scePthreadSelf(), SIGUSR1);
} catch (const std::runtime_error&) {
rejected = true;
}
Require(rejected);
Require(sceKernelInstallExceptionHandler(SIGUSR1, reinterpret_cast<void*>(&Handler)) == 0);
Require(sceKernelRaiseException(scePthreadSelf(), SIGUSR1) == 0);
Require(calls.load() == 1 && handlerThread.load() == std::this_thread::get_id());
Worker busy;
Pthread busyThread = nullptr;
Require(scePthreadCreate(&busyThread, nullptr, Busy, &busy, "busy") == 0);
while (!busy.started.load()) std::this_thread::yield();
for (int raised = 0; raised < Repeats; ++raised) {
Require(sceKernelRaiseException(busyThread, SIGUSR1) == 0);
ExpectDelivery(1 + raised, busy.id);
}
busy.stop.store(true);
Require(scePthreadJoin(busyThread, nullptr) == 0);
Worker waiting;
Require(sceKernelCreateSema(&waiting.sem, "raise", 0, 0, 1, nullptr) == 0);
Pthread waitingThread = nullptr;
Require(scePthreadCreate(&waitingThread, nullptr, Waiting, &waiting, "waiting") == 0);
while (!waiting.started.load()) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
for (int raised = 0; raised < Repeats; ++raised) {
Require(sceKernelRaiseException(waitingThread, SIGUSR1) == 0);
ExpectDelivery(1 + Repeats + raised, waiting.id);
}
Require(sceKernelSignalSema(waiting.sem, 1) == 0);
Require(scePthreadJoin(waitingThread, nullptr) == 0);
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);
Worker leaving;
Require(sceKernelCreateSema(&leaving.sem, "leaving", 0, 0, 1, nullptr) == 0);
Pthread leavingThread = nullptr;
Require(scePthreadCreate(&leavingThread, nullptr, Leaving, &leaving, "leaving") == 0);
for (int round = 0; round < LeavingRounds; ++round) {
while (!leaving.started.load()) std::this_thread::yield();
leaving.started.store(false);
std::this_thread::sleep_for(std::chrono::microseconds(200));
Require(sceKernelSignalSema(leaving.sem, 1) == 0);
for (int spin = 0; spin < round % 16 * 64; ++spin) std::this_thread::yield();
Require(sceKernelRaiseException(leavingThread, SIGUSR1) == 0);
ExpectDelivery(1 + 2 * Repeats + HostRounds + round, leaving.id);
leavingRound.store(round + 1);
}
Require(scePthreadJoin(leavingThread, nullptr) == 0);
Require(sceKernelDeleteSema(leaving.sem) == 0);
Pthread finishedThread = nullptr;
Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0);
while (!finishedReturned.load()) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(sceKernelRaiseException(finishedThread, SIGUSR1) == SCE_KERNEL_ERROR_ESRCH);
Require(scePthreadJoin(finishedThread, nullptr) == 0);
Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0);
}

View File

@ -211,7 +211,12 @@ Throughout the project, every function at every stage either **does exactly what
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler but nothing delivers to it: a host fault still ends the process, and `sceKernelRaiseException` is not implemented. PPSA12544's PS5Util module (Unity) installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection, so delivery must run the handler on the target thread with a FreeBSD `ucontext`
- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux
- [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
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) saves and restores only `CONTEXT_FULL | CONTEXT_FLOATING_POINT` on Windows, so the upper halves of the YMM registers are not preserved if the handler or the entry uses AVX
- [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
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`.
- [Thread cancellation](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) is not modelled: `scePthreadCancel` throws, so no cancel request is ever pending and `scePthreadTestcancel` and `pthread_testcancel` return without acting; the cancel state and type set by `scePthreadSetcancelstate` and `scePthreadSetcanceltype` are stored and not used
- [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws.