feat(libkernel): record exception handlers

sceKernelInstallExceptionHandler and sceKernelRemoveExceptionHandler threw.
They now keep one handler per allowed signal (SIGHUP, SIGILL, SIGFPE,
SIGBUS, SIGSEGV, SIGUSR1, as in shadPS4): another signal or a null handler
returns EINVAL, a second handler for a signal returns EAGAIN, and removing
clears it. Delivery is not implemented and is recorded in the technical
debt; sceKernelRaiseException still throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Merijn 2026-10-05 19:47:30 +02:00
parent 0518f0e021
commit 3b8ef5bc4f
4 changed files with 69 additions and 5 deletions

View File

@ -245,6 +245,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_signals PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_exception_handler_tests ${LIBS_INCLUDE_DIR}/tests/GuestExceptionHandler.cpp)
target_include_directories(guest_exception_handler_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_exception_handler_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_exception_handler_tests)
add_test(NAME guest_exception_handler COMMAND guest_exception_handler_tests)
if(WIN32)
set_tests_properties(guest_exception_handler PROPERTIES 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

@ -2,19 +2,39 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include <array>
#include <mutex>
namespace {
constexpr std::array<int, 6> AllowedSignals{1, 4, 8, 10, 11, 30};
std::mutex handlersLock;
std::array<void*, 32> handlers{};
bool Allowed(int signum) {
for (const int allowed : AllowedSignals)
if (allowed == signum) return true;
return false;
}
}
extern "C" {
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler) {
(void)signum;
(void)handler;
NotImplemented_nid_no_patch(__func__);
if (!Allowed(signum) || handler == nullptr) return SCE_KERNEL_ERROR_EINVAL;
std::lock_guard lock(handlersLock);
if (handlers[signum] != nullptr) return SCE_KERNEL_ERROR_EAGAIN;
handlers[signum] = handler;
return 0;
}
int APS5_VABI sceKernelRemoveExceptionHandler(int signum) {
(void)signum;
NotImplemented_nid_no_patch(__func__);
if (!Allowed(signum)) return SCE_KERNEL_ERROR_EINVAL;
std::lock_guard lock(handlersLock);
handlers[signum] = nullptr;
return 0;
}

View File

@ -0,0 +1,33 @@
#include "SceTypes.hpp"
#include <cstdlib>
#include <initializer_list>
extern "C" {
int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler);
int APS5_VABI sceKernelRemoveExceptionHandler(int signum);
}
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
static constexpr int SCE_KERNEL_ERROR_EAGAIN = static_cast<int>(0x80020023);
static void APS5_VABI Handler(int, void*) {}
static void APS5_VABI Other(int, void*) {}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
auto* handler = reinterpret_cast<void*>(&Handler);
auto* other = reinterpret_cast<void*>(&Other);
for (const int rejected : {0, 2, 9, 31, 128, -1})
Require(sceKernelInstallExceptionHandler(rejected, handler) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelInstallExceptionHandler(30, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelRemoveExceptionHandler(9) == SCE_KERNEL_ERROR_EINVAL);
for (const int signum : {1, 4, 8, 10, 11, 30}) {
Require(sceKernelInstallExceptionHandler(signum, handler) == 0);
Require(sceKernelInstallExceptionHandler(signum, other) == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelRemoveExceptionHandler(signum) == 0);
Require(sceKernelInstallExceptionHandler(signum, other) == 0);
Require(sceKernelRemoveExceptionHandler(signum) == 0);
}
Require(sceKernelRemoveExceptionHandler(30) == 0);
}

View File

@ -83,6 +83,8 @@ Throughout the project, every function at every stage either **does exactly what
### Functional
- [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`
- [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`.
- [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.
- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either.