diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index f3deb658df35..cd49316e3420 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -306,14 +306,19 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo } } std::string length; - if (*format && std::strchr("hljztL", *format)) { + if (*format && std::strchr("hljztLq", *format)) { length += *format++; if ((length == "h" && *format == 'h') || (length == "l" && *format == 'l')) length += *format++; } - const char conversion = *format; - if (!conversion) throw std::invalid_argument("Incomplete format conversion"); + if (length == "q") length = "ll"; + char conversion = *format; + if (!conversion) break; ++format; + if (conversion == 'D' || conversion == 'O' || conversion == 'U') { + if (length != "j" && length != "z" && length != "t" && length != "ll") length = "l"; + conversion = static_cast(conversion + 'a' - 'A'); + } const bool integerLength = length.empty() || length == "h" || length == "hh" || length == "l" || length == "ll" || length == "j" || length == "z" || length == "t"; if (conversion == 'd' || conversion == 'i') { @@ -391,6 +396,8 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo else if (length == "h") *static_cast(pointer) = static_cast(count); else if (length.empty()) *static_cast(pointer) = count; else *static_cast(pointer) = count; + } else if (!std::strchr("diouxXaAeEfFgGcCsSpn", conversion)) { + output.Value(spec + 'c', static_cast(static_cast(conversion))); } else { throw std::invalid_argument("Unsupported format conversion '" + spec + length + conversion + "' in \"" + std::string(formatStart).substr(0, 160) + "\""); } diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 8b2260536d2b..8e30381a1bbb 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -191,6 +191,16 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { address(0x1a), 6, address(0xf), -6, address(0xf)) == 34 && std::strcmp(pointers, "[0x1a][0x1a][0x1a][ 0xf][0xf ]") == 0); Require(snprintf_nid_postfix(pointers, 6, "%p", address(0xabcdef)) == 8 && std::strcmp(pointers, "0xabc") == 0); + char freebsd[64]; + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%qu|%qx|%qo", -5000000000LL, 5000000000ULL, 255ULL, 8ULL) == 28 && + std::strcmp(freebsd, "-5000000000|5000000000|ff|10") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%D|%hD|%O|%U|%jD", -5000000000L, 5000000000L, 8L, 5000000000UL, -6000000000LL) == 48 && + std::strcmp(freebsd, "-5000000000|5000000000|10|5000000000|-6000000000") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "[%5%][%-5%][%y][%5y][%-3y]") == 29 && + std::strcmp(freebsd, "[ %][% ][y][ y][y ]") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "100%") == 3 && std::strcmp(freebsd, "100") == 0); + Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%d done 5% ", 7) == 8 && std::strcmp(freebsd, "7 done 5") == 0); + Require(snprintf_nid_postfix(freebsd, 4, "ab%dcd%", 12) == 6 && std::strcmp(freebsd, "ab1") == 0); #endif snprintf_nid_postfix(buffer, sizeof(buffer), "%.*s", -1, "unlimited"); Require(std::strcmp(buffer, "unlimited") == 0); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 124436d2ad17..170399b3171c 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -66,3 +66,4 @@ - [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified. - [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libSceAmpr) follows Dinkumware's `thread0.cpp` as shipped with [Visual C++ 2013](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/thread0.cpp) and in [microsoft/STL](https://github.com/microsoft/STL/blob/da52dc0fcadbd83690cfd1ed32408d2847439493/stl/src/thread0.cpp), without console verification. `_Cpp_error` codes 0 to 6 throw a guest `std::system_error` whose `error_code` holds the FreeBSD errno of the `errc` value (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whose `what()` is the table message, `": "` and the generic category's message for that errno, selected by the first matching errno entry in VC12's [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/syserror.cpp); another code throws `std::out_of_range` on the host instead of reading past the table. The guest `system_error` is taken to be [VC12's](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/system_error): the 16-byte `runtime_error` followed by `error_code {int, const error_category*}`, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12's `error_category` virtual order (destructors, `name`, `message`, `default_error_condition`, the two `equivalent`), not the console's `_Error_objects::_Generic_object`, and the new `generic_category()` export returns that object; a title's inline category object in a bundled libc can still have a different identity. Its `message` and direct message export construct the existing [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) model: a reserved eight-byte word, 16-byte inline buffer, length and capacity, with guest `operator new` storage above 15 bytes. This 40-byte string layout and virtual return ABI have not been verified against a console library. The deleting destructor still throws. `_Syserror_map` covers the 72 distinct Dinkumware `errc` values present in FreeBSD 11.4 [errno.h](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h), returning null for other values; category messages use `unknown error` for those. First-match aliases are `EAGAIN`/`EWOULDBLOCK` -> `operation would block` and `ENOTSUP`/`EOPNOTSUPP` -> `not supported`. STREAMS-only errno assignments absent from that header (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are not inferred - [Call tracing](../../../core/libs/prx/libc/src/specifics/windows/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) exists on Windows only and records the entry of a call, not its arguments or its result. One process-wide ring keeps the last 65536 calls, so a thread that calls often pushes the others out. Calls are also counted per traced target since start-up; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; arguments in wider vector registers pass only as long as the recording code stays free of AVX instructions, which is not tested +- [printf family conversions](../../../core/libs/prx/libc/include/WindowsFormatting.hpp) (libc) - on Windows the `q` length modifier, the `%D`, `%O` and `%U` conversions, an unknown conversion character (printed as itself with the width and `-` flag, as `%c` would) and a `%` that ends the format (output stops there) follow FreeBSD 11.4 [vfprintf.c](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/stdio/vfprintf.c). The console's `libc.prx` is assumed to take them from the same code, and has not been checked on a title. The `0` flag on an unknown conversion is handled by the host `%c`, which pads with blanks where FreeBSD pads with zeroes. Linux formats these through glibc, which prints `%D`, `%O`, `%U` and unknown conversions as written.