diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index d46141cfe..6f74e1982 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -422,6 +422,16 @@ if(BUILD_TESTING) set_tests_properties(guest_signals PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kill_tests ${LIBS_INCLUDE_DIR}/tests/GuestKill.cpp) + target_include_directories(guest_kill_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_kill_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_kill_tests) + add_test(NAME guest_kill COMMAND guest_kill_tests) + set_tests_properties(guest_kill PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kill PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_signal_set_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignalSets.cpp) target_include_directories(guest_signal_set_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_signal_set_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index dd7ad433c..f1ee8327e 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -8,6 +8,7 @@ #include extern "C" int* APS5_VABI __error_nid_postfix(); +extern "C" int APS5_VABI getpid_nid_postfix(void); struct GuestSignalSet { std::uint32_t bits[4]; @@ -120,6 +121,12 @@ int APS5_VABI raise_nid_postfix(int guest) { if (result) *__error_nid_postfix() = 22; return result ? -1 : 0; } +int APS5_VABI kill_nid_postfix(int pid, int guest) { + if (guest < 0 || guest > MaxSignal) { *__error_nid_postfix() = 22; return -1; } + const int self = getpid_nid_postfix(); + if (pid != self && pid != 0 && pid != -self) { *__error_nid_postfix() = 3; return -1; } + return guest == 0 ? 0 : raise_nid_postfix(guest); +} int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous) { GuestStack replacement = alternateStack; if (stack != nullptr) { diff --git a/core/libs/tests/GuestKill.cpp b/core/libs/tests/GuestKill.cpp new file mode 100644 index 000000000..4a2d75547 --- /dev/null +++ b/core/libs/tests/GuestKill.cpp @@ -0,0 +1,46 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +using Handler = void (APS5_VABI *)(int); +extern "C" { +Handler APS5_VABI signal_nid_postfix(int, Handler); +int APS5_VABI kill_nid_postfix(int, int); +int APS5_VABI getpid_nid_postfix(void); +int* APS5_VABI __error_nid_postfix(); +} +volatile std::sig_atomic_t received = 0; +void APS5_VABI Callback(int value) { received = value; } +static void Require(bool value) { if (!value) std::abort(); } +static void Fails(int pid, int guest, int error) { + *__error_nid_postfix() = 0; + Require(kill_nid_postfix(pid, guest) == -1 && *__error_nid_postfix() == error); +} +int main() { + const int self = getpid_nid_postfix(); + const auto invalid = reinterpret_cast(static_cast(-1)); + const auto ignore = reinterpret_cast(std::uintptr_t{1}); + for (int target : {self, 0, -self}) Require(kill_nid_postfix(target, 0) == 0); + for (int target : {self + 1, -1, -(self + 1)}) { + Fails(target, 0, 3); + Fails(target, 15, 3); + } + for (int guest : {-1, 129}) { + Fails(self, guest, 22); + Fails(self + 1, guest, 22); + } + Require(signal_nid_postfix(15, Callback) != invalid); + for (int target : {self, 0, -self}) { + received = 0; + Require(kill_nid_postfix(target, 0) == 0 && received == 0); + Require(kill_nid_postfix(target, 15) == 0 && received == 15); + } + received = 0; + Fails(self + 1, 15, 3); + Require(received == 0); + Require(signal_nid_postfix(15, ignore) == Callback); + Require(kill_nid_postfix(self, 15) == 0 && received == 0); + Require(signal_nid_postfix(15, nullptr) == ignore); + Fails(self, 100, 22); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index d9ccaf5b3..99882de41 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -445,5 +445,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideodec2CreateDecoder](../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) decodes HEVC (codec type 974921) and VP9 (2382845) through FFmpeg, besides H.264. The tags come from SharpProspero's `Videodec2CodecType` and EVO-PLAYER-PS5's `sce_videodec2.h`, which records them from hardware runs. HEVC input is converted from length-prefixed NAL units to Annex B as for H.264, and VP9 frames are passed through. Output is the same 8-bit NV12 for every codec; a decoder for HEVC Main10 or VP9 Profile 2 is created, but a frame deeper than 8 bits throws, since the console's 10-bit output layout is unknown, and `QueryDecoderMemoryInfo` reports the 8-bit frame size for it. `sceVideodec2GetPictureInfo` and `sceVideodec2GetAvcPictureInfo` throw for HEVC and VP9 pictures, which use other layouts (`SceVideodec2HevcPictureInfo`, `SceVideodec2Vp9PictureInfo`), and `sceVideodec2GetHevcPictureInfo` and `sceVideodec2GetVp9PictureInfo` are not exported - [syslog](../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:] `, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER`, and errno is preserved. `openlog`, `setlogmask` and `closelog` are not implemented, so the identity, options and mask they set are not applied - [sigaction](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers and other signals (`SIGBUS`, `SIGHUP`, ...) are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`; `raise` of an `SA_SIGINFO` handler on a signal `signal` delivers throws instead of terminating, and `raise` of the other signals still returns `EINVAL` +- [kill](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `kern_kill` and `killpg1` for the only guest process: `EINVAL` for signals outside 0 to 128, signal 0 only checks that the target exists, and its own pid, 0 and minus its own pid (the guest is taken to lead its own process group) signal it through `raise`, so they share `raise`'s limits above. Any other pid, and -1, which skips the caller, report `ESRCH`, so `EPERM` is never reported - [AJM](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix. - [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.