feat(libc): implement syslog and vsyslog
ps5-opengl (PPSA99005) and ProsperoRadio (PPSA99001) import syslog from libSceLibcInternal. Messages go to stderr with the priority, and the format follows FreeBSD's vsyslog: %m expansion, masking of unknown priority bits, LOG_USER as the default facility, errno preserved. Assisted by Claude Code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d724cad38c
commit
bcd1b0c2a0
|
|
@ -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:$<TARGET_FILE_DIR:libc>")
|
||||
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:$<TARGET_FILE_DIR:libc>")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#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<std::va_list*>(args));
|
||||
const int size = std::vsnprintf(nullptr, 0, format, copy);
|
||||
va_end(copy);
|
||||
if (size <= 0) return {};
|
||||
std::string buffer(static_cast<std::size_t>(size) + 1, '\0');
|
||||
va_copy(copy, *reinterpret_cast<std::va_list*>(args));
|
||||
std::vsnprintf(buffer.data(), buffer.size(), format, copy);
|
||||
va_end(copy);
|
||||
buffer.resize(static_cast<std::size_t>(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<VaList*>(args));
|
||||
#ifdef _WIN32
|
||||
__builtin_sysv_va_end(args);
|
||||
#else
|
||||
va_end(args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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);
|
||||
}
|
||||
|
|
@ -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:<priority>] <message>`, 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue