diff --git a/core/libs/prx/libc/include/ErrorMessages.hpp b/core/libs/prx/libc/include/ErrorMessages.hpp new file mode 100644 index 0000000000..6176ddba5e --- /dev/null +++ b/core/libs/prx/libc/include/ErrorMessages.hpp @@ -0,0 +1,8 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_ERRORMESSAGES_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_ERRORMESSAGES_HPP + +#include + +extern "C" void FreeBsdErrorMessage_nid_no_patch(int error, char* buffer, std::size_t length); + +#endif diff --git a/core/libs/prx/libc/src/Errors.cpp b/core/libs/prx/libc/src/Errors.cpp index 04d0187df9..236b86b25e 100644 --- a/core/libs/prx/libc/src/Errors.cpp +++ b/core/libs/prx/libc/src/Errors.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/WindowsFormatting.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ErrorMessages.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -45,32 +46,37 @@ constexpr const char* messages[] = { static_assert(sizeof(messages) / sizeof(messages[0]) == 97); bool Known(int error) { return error >= 0 && error < 97; } bool NamedByStrerror(int error) { return error > 0 && error < 95; } + +void Describe(int error, char (&text)[128]) { + if (error == 0) std::snprintf(text, sizeof(text), "No error"); + else if (NamedByStrerror(error)) std::snprintf(text, sizeof(text), "%s", messages[error]); + else std::snprintf(text, sizeof(text), "error_code=0x%08x", static_cast(error)); +} } extern "C" { +void FreeBsdErrorMessage_nid_no_patch(int error, char* buffer, std::size_t length) { + const int saved = *__error_nid_postfix(); + if (Known(error)) std::snprintf(buffer, length, "%s", messages[error]); + else std::snprintf(buffer, length, "Unknown error: %d", error); + *__error_nid_postfix() = saved; +} + char* APS5_VABI strerror_nid_postfix(int error) { thread_local char buffer[128]; - if (error == 0) std::snprintf(buffer, sizeof(buffer), "No error"); - else if (NamedByStrerror(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]); - else std::snprintf(buffer, sizeof(buffer), "error_code=0x%08x", static_cast(error)); + Describe(error, buffer); return buffer; } int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length) { const int saved = *__error_nid_postfix(); - char temporary[128]; - if (Known(error)) std::snprintf(temporary, sizeof(temporary), "%s", messages[error]); - else std::snprintf(temporary, sizeof(temporary), "Unknown error: %d", error); - const auto required = std::strlen(temporary) + 1; - int result = Known(error) ? 0 : 22; - if (Known(error) && (!buffer || length < required)) result = 34; - if (buffer && length) { - const auto count = required <= length ? required - 1 : length - 1; - std::memcpy(buffer, temporary, count); - buffer[count] = '\0'; - } + char text[128]; + Describe(error, text); *__error_nid_postfix() = saved; - return result; + const auto required = std::strlen(text) + 1; + if (!buffer || length < required) return 34; + std::memcpy(buffer, text, required); + return 0; } void APS5_VABI perror_nid_postfix(const char* prefix) { diff --git a/core/libs/prx/libc/src/WideConversion.cpp b/core/libs/prx/libc/src/WideConversion.cpp index 109dae7f16..f89e3716e7 100644 --- a/core/libs/prx/libc/src/WideConversion.cpp +++ b/core/libs/prx/libc/src/WideConversion.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/WindowsFormatting.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ErrorMessages.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/SonyCrt.hpp" #include @@ -10,7 +11,6 @@ #include #include #include -#include extern "C" void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error) { SonyIgnoreHandlerS(message, pointer, error); @@ -41,8 +41,7 @@ extern "C" int APS5_VABI wcscpy_s_nid_postfix(std::uint16_t* dest, std::size_t s namespace { std::size_t ReadErrorMessage(int error, char (&message)[128]) { - const int result = strerror_r_nid_postfix(error, message, sizeof(message)); - if (result != 0 && result != 22) throw std::runtime_error("strerror_s: error message exceeds internal buffer"); + FreeBsdErrorMessage_nid_no_patch(error, message, sizeof(message)); return std::strlen(message); } diff --git a/core/libs/tests/GuestErrors.cpp b/core/libs/tests/GuestErrors.cpp index 1946147d26..9388c78d08 100644 --- a/core/libs/tests/GuestErrors.cpp +++ b/core/libs/tests/GuestErrors.cpp @@ -58,40 +58,43 @@ int main() { Require(std::strcmp(parent, "Invalid argument") == 0); char buffer[128]; for (int error = 0; error <= 96; ++error) { - Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0); - Require(std::strcmp(buffer, freebsd[error]) == 0); char coded[32]; std::snprintf(coded, sizeof(coded), "error_code=0x%08x", static_cast(error)); const char* expected = error == 0 ? "No error" : error < 95 ? freebsd[error] : coded; + Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0); + Require(std::strcmp(buffer, expected) == 0); Require(std::strcmp(strerror_nid_postfix(error), expected) == 0); Require(*__error_nid_postfix() == 13); } - Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22); - Require(std::strcmp(buffer, "Unknown error: -1") == 0); - Require(*__error_nid_postfix() == 13); - struct UnknownError { - int error; - const char* text; - const char* coded; - }; - for (const UnknownError& unknown : {UnknownError{97, "Unknown error: 97", "error_code=0x00000061"}, - UnknownError{9999, "Unknown error: 9999", "error_code=0x0000270f"}, - UnknownError{INT_MAX, "Unknown error: 2147483647", "error_code=0x7fffffff"}, - UnknownError{-1, "Unknown error: -1", "error_code=0xffffffff"}, - UnknownError{INT_MIN, "Unknown error: -2147483648", "error_code=0x80000000"}}) { - Require(strerror_r_nid_postfix(unknown.error, buffer, sizeof(buffer)) == 22); - Require(std::strcmp(buffer, unknown.text) == 0); + for (const int error : {97, 100, 1000, 9999, INT_MAX, -1, INT_MIN}) { + char coded[32]; + std::snprintf(coded, sizeof(coded), "error_code=0x%08x", static_cast(error)); + Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0); + Require(std::strcmp(buffer, coded) == 0); Require(*__error_nid_postfix() == 13); - Require(std::strcmp(strerror_nid_postfix(unknown.error), unknown.coded) == 0); + Require(std::strcmp(strerror_nid_postfix(error), coded) == 0); Require(*__error_nid_postfix() == 13); } - char sentinel[] = "xyz"; - Require(strerror_r_nid_postfix(22, sentinel, 1) == 34); - Require(sentinel[0] == 0 && sentinel[1] == 'y'); - sentinel[0] = 'x'; - Require(strerror_r_nid_postfix(22, sentinel, 0) == 34 && sentinel[0] == 'x'); + struct ShortBuffer { + int error; + const char* text; + }; + for (const ShortBuffer& entry : {ShortBuffer{0, "No error"}, ShortBuffer{22, "Invalid argument"}, + ShortBuffer{95, "error_code=0x0000005f"}, ShortBuffer{-1, "error_code=0xffffffff"}}) { + const std::size_t required = std::strlen(entry.text) + 1; + for (const std::size_t length : {std::size_t{0}, std::size_t{1}, std::size_t{5}, required - 1}) { + char canary[64]; + std::memset(canary, 0xcc, sizeof(canary)); + Require(strerror_r_nid_postfix(entry.error, canary, length) == 34); + for (const char byte : canary) Require(static_cast(byte) == 0xcc); + Require(*__error_nid_postfix() == 13); + } + char exact[64]; + std::memset(exact, 0xcc, sizeof(exact)); + Require(strerror_r_nid_postfix(entry.error, exact, required) == 0); + Require(std::strcmp(exact, entry.text) == 0); + Require(static_cast(exact[required]) == 0xcc); + } Require(strerror_r_nid_postfix(22, nullptr, 0) == 34); - char truncated[4]; - Require(strerror_r_nid_postfix(97, truncated, sizeof(truncated)) == 22 && std::strcmp(truncated, "Unk") == 0); - Require(strerror_r_nid_postfix(-1, truncated, sizeof(truncated)) == 22 && std::strcmp(truncated, "Unk") == 0); + Require(strerror_r_nid_postfix(22, nullptr, sizeof(buffer)) == 34); } diff --git a/core/libs/tests/GuestSafeErrors.cpp b/core/libs/tests/GuestSafeErrors.cpp index b253f14d6c..fc6c158966 100644 --- a/core/libs/tests/GuestSafeErrors.cpp +++ b/core/libs/tests/GuestSafeErrors.cpp @@ -60,12 +60,13 @@ void CheckMessages() { CheckMessage(2, "No such file or directory"); CheckMessage(11, "Resource deadlock avoided"); CheckMessage(35, "Resource temporarily unavailable"); + CheckMessage(95, "State not recoverable"); CheckMessage(96, "Previous owner died"); CheckMessage(-1, "Unknown error: -1"); CheckMessage(97, "Unknown error: 97"); CheckMessage(std::numeric_limits::min(), "Unknown error: -2147483648"); CheckMessage(std::numeric_limits::max(), "Unknown error: 2147483647"); - for (int error = 0; error < 97; ++error) { + for (int error = 1; error < 95; ++error) { char expected[128]; Require(strerror_r_nid_postfix(error, expected, sizeof(expected)) == 0, "existing guest error table"); CheckMessage(error, expected); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 1c2a506f08..4f22833432 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -47,7 +47,7 @@ - [wcsncat_s and wcscat_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) refuse a copy as overlapping only when the written range `[dest + len1, dest + len1 + len2)` meets the read range `[src, src + len2)`. The rule comes from a static reading of the console's `wcsncat_s` in `libc.prx` (not run on a console); `wcscat_s` gets it through `wcsncat_s`, and its own console code was not read. The narrow `strncat_s` keeps a wider rule, `[dest, dest + used + length + 1)` against `[src, src + length + 1)`, whose console code was not read either, so it refuses some copies whose wide equivalent proceeds. - [snwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: the handler installed with `set_constraint_handler_s` is not invoked, and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [swprintf_s and vswprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follow C11 K.3.9.1.4 with `RSIZE_MAX` assumed to be `(SIZE_MAX >> 1) / sizeof(char16_t)`: the complete output, including the terminating null, must fit in the buffer, otherwise it is a runtime-constraint violation that sets `buffer[0]` to zero and returns a negative value, and the buffer is not partially written. Argument and encoding checks match `snwprintf_s` -- [strerror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - follows the `libc.prx` that CUSA04865 and CUSA12435 ship, loaded on a PS4 Pro (firmware 10.71): 0 gives "No error", 1 to 94 the FreeBSD texts, any other number "error_code=0x%08x", and errno is left alone. The console's system libc, which homebrew without a bundled `libc.prx` uses, answers differently ("No error information" for 0 and unknown numbers, 95 and 96 named). `strerror_r` and `perror` keep "No error: 0" and "Unknown error: %d"; they are not measured. +- [strerror, strerror_r and perror](../../../core/libs/prx/libc/src/Errors.cpp) (libc) - a null `strerror_r` buffer returns `ERANGE` (34) without writing; what the console does with one is not measured. Otherwise they match the system `libSceLibcInternal` of PS5 firmware 13.60 and the `libc.prx` that CUSA04865 and CUSA12435 ship, loaded on a PS4 Pro (firmware 10.71): 0 gives "No error", 1 to 94 the FreeBSD texts and any other number "error_code=0x%08x"; `strerror_r` returns 0 for every number, and 34 without writing anything when the buffer is shorter than the text and its terminator; `perror` prints the same texts; errno is left alone. - [strerror_s and strerrorlen_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc) follow [C11 K.3.7.4.2 and K.3.7.4.3](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), using the existing FreeBSD error messages and unknown-error formatting. Truncation writes an ellipsis when capacity exceeds 3; constraint failures leave the destination untouched and invoke the registered guest-ABI handler. The console's `RSIZE_MAX` (`SIZE_MAX >> 1` here), nonzero returns (`EINVAL` 22 for constraints, `ERANGE` 34 for truncation), handler arguments and default abort behavior are unverified. Messages remain in the C locale, and guest errno is preserved. - [getw and putw](../../../core/libs/prx/libc/src/BinaryWordIo.cpp) (libc) follow FreeBSD 11.4 [getw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/getw.c) and [putw.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/putw.c): one 32-bit little-endian word, `getw` returns -1 for EOF or an error, and `putw` returns 0 on success or -1 on error while holding the stream lock. They use the existing host-backed streams, so buffering and error timing follow the host CRT. Null streams throw and closed streams return -1 with `EBADF`, as existing byte I/O does. Console behavior is unverified. A payload process on a PS5 (firmware 13.60) gave the same results: two words written little-endian, -1 with end of file after them and for a 2-byte file, and -1 with the error flag for `putw` on a read-only stream - [vsnwprintf_s](../../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - `va_list` counterpart of `snwprintf_s` (C11 K.3.9.1.3): truncates to the buffer and returns the full length, with the same constraints and argument checks