From c8ad92f8783612f8bf4431cb92ed66139cda3dac Mon Sep 17 00:00:00 2001 From: Niko Date: Tue, 20 Oct 2026 22:34:24 +0200 Subject: [PATCH] feat(libSceLibcInternal): export _err, _warn and _getprogname The PS5 3.20 export inventory lists _err, _warn and _getprogname in libSceLibcInternal next to err, warn and getprogname, with the NIDs UhKI6z9WWuo, XQFE8Y58WZM and iI6kGxgXzcU. In FreeBSD 11.4 libc the underscore names are the definitions and the public names are weak references to them (lib/libc/gen/err.c, lib/libc/gen/getprogname.c), and libthr interposes none of them, so each pair is one function. _err and _warn now hand their arguments to verr and vwarn, which use errno at the call as err and warn do; _getprogname returns getprogname(). The debt file records that the console's module was not read. guest_err checks the _warn and _err text for errno 13 with a formatted message, that _err passes its status to the err_set_exit hook, and that _warn without a format returns without calling the hook. guest_program_name checks that _getprogname returns the current __progname pointer. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceLibcInternal/Err.cpp | 13 +++++++++++++ core/libs/prx/libSceLibcInternal/Export.cpp | 4 ++++ core/libs/tests/GuestErr.cpp | 11 +++++++++++ core/libs/tests/GuestProgramName.cpp | 3 +++ docs/dev/debt/libSceLibcInternal.md | 1 + 5 files changed, 32 insertions(+) diff --git a/core/libs/prx/libSceLibcInternal/Err.cpp b/core/libs/prx/libSceLibcInternal/Err.cpp index f16dd24ca1..08f9ae7622 100644 --- a/core/libs/prx/libSceLibcInternal/Err.cpp +++ b/core/libs/prx/libSceLibcInternal/Err.cpp @@ -108,6 +108,13 @@ void APS5_VABI warn_nid_postfix(const char* format, ...) { APS5_ERR_VA_END(args); } +void APS5_VABI _warn_nid_postfix(const char* format, ...) { + APS5_ERR_VA_LIST args; + APS5_ERR_VA_START(args, format); + vwarn_nid_postfix(format, reinterpret_cast(args)); + APS5_ERR_VA_END(args); +} + void APS5_VABI warnx_nid_postfix(const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); @@ -128,6 +135,12 @@ void APS5_VABI warnx_nid_postfix(const char* format, ...) { verrc_nid_postfix(status, code, format, reinterpret_cast(args)); } +[[noreturn]] void APS5_VABI _err_nid_postfix(int status, const char* format, ...) { + APS5_ERR_VA_LIST args; + APS5_ERR_VA_START(args, format); + verr_nid_postfix(status, format, reinterpret_cast(args)); +} + [[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...) { APS5_ERR_VA_LIST args; APS5_ERR_VA_START(args, format); diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 7ca113639e..b8c3288715 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -109,6 +109,10 @@ const char* APS5_VABI getprogname_nid_postfix() { return __progname_nid_postfix; } +const char* APS5_VABI _getprogname_nid_postfix() { + return getprogname_nid_postfix(); +} + void APS5_VABI __cxa_finalize_nid_postfix(void* dsoHandle) { CxaFinalize_nid_no_patch(dsoHandle); } diff --git a/core/libs/tests/GuestErr.cpp b/core/libs/tests/GuestErr.cpp index cf7f656594..14c8772bc2 100644 --- a/core/libs/tests/GuestErr.cpp +++ b/core/libs/tests/GuestErr.cpp @@ -17,9 +17,11 @@ extern "C" { void APS5_VABI err_set_file_nid_postfix(FileStream* stream); void APS5_VABI err_set_exit_nid_postfix(ErrExit function); void APS5_VABI warn_nid_postfix(const char* format, ...); +void APS5_VABI _warn_nid_postfix(const char* format, ...); void APS5_VABI warnc_nid_postfix(int code, const char* format, ...); void APS5_VABI warnx_nid_postfix(const char* format, ...); [[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...); +[[noreturn]] void APS5_VABI _err_nid_postfix(int status, const char* format, ...); [[noreturn]] void APS5_VABI errc_nid_postfix(int status, int code, const char* format, ...); [[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...); void APS5_VABI vwarn_nid_postfix(const char* format, VaList* args); @@ -148,6 +150,9 @@ int main() { *__error_nid_postfix() = 2; warn_nid_postfix(nullptr); Require(capture.Take() == "prog: " + noEntry + "\n", "warn without format"); + *__error_nid_postfix() = 13; + _warn_nid_postfix("open %s", "b.txt"); + Require(capture.Take() == "prog: open b.txt: " + denied + "\n", "_warn"); warnc_nid_postfix(13, "%d-%s", 7, "x"); Require(capture.Take() == "prog: 7-x: " + denied + "\n", "warnc"); warnx_nid_postfix("plain %c", 'q'); @@ -163,6 +168,12 @@ int main() { *__error_nid_postfix() = 2; Require(ExitStatus([] { err_nid_postfix(5, "e"); }) == 5, "err status"); Require(capture.Take() == "prog: e: " + noEntry + "\n", "err text"); + *__error_nid_postfix() = 13; + Require(ExitStatus([] { _err_nid_postfix(7, "%s %d", "f", 8); }) == 7 && g_hookStatus == 7, "_err status"); + Require(capture.Take() == "prog: f 8: " + denied + "\n", "_err text"); + *__error_nid_postfix() = 2; + Require(ExitStatus([] { _warn_nid_postfix(nullptr); }) == -1 && g_hookStatus == 7, "_warn does not exit"); + Require(capture.Take() == "prog: " + noEntry + "\n", "_warn without format"); *__error_nid_postfix() = 2; CallVariant(Variant::Warn, 0, 0, "open %s", "a.txt"); diff --git a/core/libs/tests/GuestProgramName.cpp b/core/libs/tests/GuestProgramName.cpp index 48be372cc8..7f14669adb 100644 --- a/core/libs/tests/GuestProgramName.cpp +++ b/core/libs/tests/GuestProgramName.cpp @@ -4,6 +4,7 @@ extern "C" { const char* APS5_VABI getprogname_nid_postfix(); +const char* APS5_VABI _getprogname_nid_postfix(); extern const char* __progname_nid_postfix; int* APS5_VABI __error_nid_postfix(); } @@ -14,12 +15,14 @@ int main() { const char* const original = __progname_nid_postfix; *__error_nid_postfix() = 45; Require(getprogname_nid_postfix() == original); + Require(_getprogname_nid_postfix() == original); Require(std::strcmp(getprogname_nid_postfix(), "eboot.bin") == 0); Require(*__error_nid_postfix() == 45); char renamed[] = "quake2"; __progname_nid_postfix = renamed; Require(getprogname_nid_postfix() == renamed); + Require(_getprogname_nid_postfix() == renamed); renamed[0] = 'Q'; Require(std::strcmp(getprogname_nid_postfix(), "Quake2") == 0); diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index ff8c271f1f..d61e6d1e18 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -16,4 +16,5 @@ - [getopt, getopt_long](../../../core/libs/prx/libSceLibcInternal/Getopt.cpp) (libSceLibcInternal) follow FreeBSD 11 [getopt.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt.c) and [getopt_long.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt_long.c) with `GNU_COMPATIBLE`: `getopt` does not permute, `getopt_long` permutes unless `POSIXLY_CORRECT` is set in the guest environment or the option string starts with `+`, and the exported `opterr`, `optind`, `optopt`, `optreset` and `optarg` start at 1, 1, 0, 0 and null. Warnings go to the guest's current `stderr` prefixed with `__progname`, where FreeBSD's `warnx` keeps the `stderr` of its first call. A null `getopt` option string, a null argument vector or argument, a negative `optind` and an `optind` moved inside a pending permutation throw where FreeBSD faults. `getopt_long_only` is not exported. That the console's `libSceLibcInternal` uses this FreeBSD code is unverified - [posix_spawnattr_getflags, getpgroup, getschedparam, getschedpolicy, getsigdefault, getsigmask, setpgroup, setschedparam, setschedpolicy and setsigmask](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) read and write the 0x30-byte attribute object at the offsets of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8D860 to 0x8D910: flags at 0, process group at 4, scheduling parameter at 8, policy at 0xC, default and masked signal sets at 0x10 and 0x20), which never fail there and return 0; the layout matched on a PS5 (firmware 13.60). Uninitialized attributes and null pointers, which the console dereferences, throw as the existing setters do - [sys_siglist and sys_nsig](../../../core/libs/prx/libSceLibcInternal/SignalList.cpp) (libSceLibcInternal) hold the 32 descriptions and the count of [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/siglist.c#L73-L107) `siglist.c` (`NSIG` 32, signal 0 to `SIGUSR2`); the console's table was not read +- [_err, _warn](../../../core/libs/prx/libSceLibcInternal/Err.cpp) and [_getprogname](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) are assumed to be the same functions as `err`, `warn` and `getprogname` and run their code; the console's module was not read. FreeBSD 11.4 defines the underscore names and makes the public names weak references to them ([`_err`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/err.c#L70-L79), [`_warn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/err.c#L135-L144), [`_getprogname`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/getprogname.c#L10-L17)) - [inet_aton, inet_addr, inet_ntoa and inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) follow the FreeBSD code in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8ADE0 to 0x8B3D0) and matched it on a PS5 (firmware 13.60) for 20 `inet_aton` inputs; a null text, which the console dereferences, throws. `inet_ntoa_r` formats through libkernel's `__inet_ntop`, so a null buffer with room for the text throws and a null buffer that is too small sets `ENOSPC`, as `__inet_ntop` does for a null destination; the console's `inet_ntoa_r` was not measured with a null buffer