diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index fd9b04e5c..f16f611dd 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -284,6 +284,15 @@ if(BUILD_TESTING) set_tests_properties(guest_exception_handler PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") 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:$") + 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) diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index ffd954255..2f3fb16eb 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -85,6 +85,7 @@ struct PthreadPrivate { std::atomic references{2}; void* stackAddress = nullptr; std::size_t stackSize = 0; + std::atomic waitCount{0}; std::atomic affinity{DEFAULT_THREAD_AFFINITY}; std::atomic priority{DEFAULT_THREAD_PRIORITY}; std::mutex nameLock; diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index d71ca02e8..b3c368d80 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -174,6 +174,7 @@ static void RunThread(std::unique_ptr 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(); } diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index ba53dbdba..e0ac735c3 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -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 +#include +#include #include +#include +#ifdef _WIN32 +#include +#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 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(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(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(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(info.BaseAddress) + info.RegionSize; + } + return true; +} + +void CALLBACK WaitingEntry(ULONG_PTR parameter) { + auto* delivery = reinterpret_cast(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(thread->nativeHandle); + auto queued = std::make_unique(Delivery{handler, signum, {}}); + if (SuspendThread(native) == static_cast(-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(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(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(slot), &delivery, sizeof(Delivery)); + CONTEXT redirected = delivery.context; + redirected.Rsp = slot - HomeArea - 8; + redirected.Rip = reinterpret_cast(&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)); +} +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) { diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index 08c0b0d77..fd8813901 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -17,8 +17,37 @@ namespace { constexpr std::uint64_t YIELD_BELOW_NANOS = 50000ULL; constexpr std::uint64_t LEAD_NANOS = 500000ULL; +thread_local std::atomic ownWaitState{0}; +thread_local std::atomic* waitState = &ownWaitState; + #ifdef _WIN32 +using NtTestAlertFunction = LONG(NTAPI*)(); + +void DrainApcs() { + static const auto testAlert = reinterpret_cast(reinterpret_cast(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(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((nanos + 999999ULL) / 1000000ULL)); + AlertableSleep(static_cast((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(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* 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 diff --git a/core/libs/prx/libkernel/Time/include/TimedWait.hpp b/core/libs/prx/libkernel/Time/include/TimedWait.hpp index 774c62dc6..bddd4189e 100644 --- a/core/libs/prx/libkernel/Time/include/TimedWait.hpp +++ b/core/libs/prx/libkernel/Time/include/TimedWait.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP #define CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP +#include #include #include #include @@ -9,6 +10,7 @@ namespace TimedWait { bool Coarse(); +void BindThreadWaitState(std::atomic* state); std::uint64_t NowNanos(); std::uint64_t DeadlineNanos(std::uint64_t microseconds); std::uint64_t RemainingMicros(std::uint64_t deadlineNanos); diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp new file mode 100644 index 000000000..4d17541a8 --- /dev/null +++ b/core/libs/tests/GuestRaiseException.cpp @@ -0,0 +1,199 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +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(0x80020016); +static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); +static constexpr int SIGUSR1 = 30; +static constexpr int Repeats = 100; + +static void Require(bool value) { if (!value) std::abort(); } + +static std::atomic calls{0}; +static std::atomic handlerThread; +static std::atomic handlerRsp{0}; +static std::atomic handlerFrame{0}; + +static void APS5_VABI Handler(int signum, void* context) { + Require(signum == SIGUSR1); + std::uint64_t rsp = 0; + std::memcpy(&rsp, static_cast(context) + 0xf8, sizeof(rsp)); + int local = 0; + handlerFrame.store(reinterpret_cast(&local)); + handlerRsp.store(rsp); + handlerThread.store(std::this_thread::get_id()); + calls.fetch_add(1); +} + +struct Worker { + std::atomic started{false}; + std::atomic stop{false}; + std::thread::id id; + KernelSema sem = nullptr; + int waitResult = -1; +}; + +static void* APS5_VABI Busy(void* arg) { + auto& worker = *static_cast(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(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 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 constexpr int LeavingRounds = 200; +static std::atomic leavingRound{0}; + +static void* APS5_VABI Leaving(void* arg) { + auto& worker = *static_cast(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 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(&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); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4f90a15c1..87cfc4400 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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.