diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 30aaf71ba..49a57bbf9 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -33,6 +33,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Syslog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp ) @@ -233,6 +234,15 @@ if(BUILD_TESTING) set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp) + target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_syslog_tests PRIVATE libc) + configure_windows_unwind(guest_syslog_tests) + add_test(NAME guest_syslog COMMAND guest_syslog_tests) + if(WIN32) + set_tests_properties(guest_syslog PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(mspace_tests ${LIBS_INCLUDE_DIR}/tests/Mspace.cpp) target_include_directories(mspace_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(mspace_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/Syslog.cpp b/core/libs/prx/libc/src/Syslog.cpp new file mode 100644 index 000000000..1c4b8552a --- /dev/null +++ b/core/libs/prx/libc/src/Syslog.cpp @@ -0,0 +1,107 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include "prx/libc/include/WindowsFormatting.hpp" +#endif + +extern "C" { +int* APS5_VABI __error_nid_postfix(); +int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length); +} + +namespace { +constexpr int priorityMask = 0x07; +constexpr int facilityMask = 0x3f8; +constexpr int userFacility = 1 << 3; +constexpr int kernelFacility = 0; +constexpr int errorPriority = 3; + +std::mutex& OutputMutex() { + static std::mutex mutex; + return mutex; +} + +void Emit(int priority, const std::string& message) { + std::lock_guard lock(OutputMutex()); + std::fprintf(stderr, "[syslog:%d] %s", priority, message.c_str()); + if (message.empty() || message.back() != '\n') std::fputc('\n', stderr); + std::fflush(stderr); +} + +std::string ExpandErrorText(const char* format, int error) { + std::string expanded; + for (const char* cursor = format; *cursor; ++cursor) { + if (cursor[0] == '%' && cursor[1] == 'm') { + char text[128]; + strerror_r_nid_postfix(error, text, sizeof(text)); + for (const char* character = text; *character; ++character) { + expanded += *character; + if (*character == '%') expanded += '%'; + } + ++cursor; + } else if (cursor[0] == '%' && cursor[1] == '%') { + expanded += "%%"; + ++cursor; + } else { + expanded += *cursor; + } + } + return expanded; +} + +std::string Format(const char* format, VaList* args) { +#ifdef _WIN32 + std::string buffer; + LibcDetail::FormatWindows(nullptr, 0, format, args, &buffer); + return buffer; +#else + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int size = std::vsnprintf(nullptr, 0, format, copy); + va_end(copy); + if (size <= 0) return {}; + std::string buffer(static_cast(size) + 1, '\0'); + va_copy(copy, *reinterpret_cast(args)); + std::vsnprintf(buffer.data(), buffer.size(), format, copy); + va_end(copy); + buffer.resize(static_cast(size)); + return buffer; +#endif +} +} + +extern "C" { +void APS5_VABI vsyslog_nid_postfix(int priority, const char* format, VaList* args) { + const int saved = *__error_nid_postfix(); + if (priority & ~(priorityMask | facilityMask)) { + char complaint[64]; + std::snprintf(complaint, sizeof(complaint), "syslog: unknown facility/priority: %x", priority); + Emit(kernelFacility | errorPriority, complaint); + priority &= priorityMask | facilityMask; + } + if ((priority & facilityMask) == 0) priority |= userFacility; + Emit(priority, format ? Format(ExpandErrorText(format, saved).c_str(), args) : std::string()); + *__error_nid_postfix() = saved; +} + +void APS5_VABI syslog_nid_postfix(int priority, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + vsyslog_nid_postfix(priority, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif +} +} diff --git a/core/libs/tests/GuestSyslog.cpp b/core/libs/tests/GuestSyslog.cpp new file mode 100644 index 000000000..064ee785d --- /dev/null +++ b/core/libs/tests/GuestSyslog.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" { +void APS5_VABI syslog_nid_postfix(int, const char*, ...); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + auto* captured = std::tmpfile(); + Require(captured != nullptr); + std::fflush(stderr); +#ifdef _WIN32 + const int saved = ::_dup(::_fileno(stderr)); + Require(saved >= 0 && ::_dup2(::_fileno(captured), ::_fileno(stderr)) == 0); +#else + const int saved = ::dup(::fileno(stderr)); + Require(saved >= 0 && ::dup2(::fileno(captured), ::fileno(stderr)) == ::fileno(stderr)); +#endif + *__error_nid_postfix() = 2; + syslog_nid_postfix(6, "station %s at %d kbps", "radio", 128); + const int afterFirst = *__error_nid_postfix(); + syslog_nid_postfix(16 | 3, "open: %m, %%m kept, %d%%\n", 50); + syslog_nid_postfix(0x10000 | 4, "masked"); + syslog_nid_postfix(7, ""); + std::fflush(stderr); +#ifdef _WIN32 + Require(::_dup2(saved, ::_fileno(stderr)) == 0 && ::_close(saved) == 0); +#else + Require(::dup2(saved, ::fileno(stderr)) == ::fileno(stderr) && ::close(saved) == 0); +#endif + Require(afterFirst == 2 && *__error_nid_postfix() == 2); + std::rewind(captured); + char text[512]{}; + const auto length = std::fread(text, 1, sizeof(text) - 1, captured); + const char* expected = + "[syslog:14] station radio at 128 kbps\n" + "[syslog:19] open: No such file or directory, %m kept, 50%\n" + "[syslog:3] syslog: unknown facility/priority: 10004\n" + "[syslog:12] masked\n" + "[syslog:15] \n"; + Require(length == std::strlen(expected) && std::strcmp(text, expected) == 0); + Require(std::fclose(captured) == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 2a74a7879..55a4e9cb7 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -358,3 +358,4 @@ Throughout the project, every function at every stage either **does exactly what - [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require one call per ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and one later `s_setpc_b64` return reading that exact pair. Regions must nest or be disjoint; recursion, shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented. - [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Native RDNA2/PS5 execution and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation. - [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 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