diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 099467fe17..1f5bf03850 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -510,6 +510,17 @@ if(BUILD_TESTING) PASS_REGULAR_EXPRESSION "C\\+\\+ exception St9bad_alloc raised on thread [0-9]+ cannot be delivered: 0x[0-9a-f]+ undeliverable_host_exception_tests\\.exe\\+0x[0-9a-f]+ has no unwind data") endif() + add_test_executable(guest_kernel_sys_uuid_tests tests/GuestKernelSysUuid.cpp) + target_include_directories(guest_kernel_sys_uuid_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_uuid_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_uuid_tests) + add_test(NAME guest_kernel_sys_uuid COMMAND guest_kernel_sys_uuid_tests) + set_tests_properties(guest_kernel_sys_uuid PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_uuid PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(crash_text_tests tests/CrashText.cpp) target_include_directories(crash_text_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) if(MINGW) diff --git a/core/libs/prx/libkernel_sys/CMakeLists.txt b/core/libs/prx/libkernel_sys/CMakeLists.txt index 70631309ca..4464c335f4 100644 --- a/core/libs/prx/libkernel_sys/CMakeLists.txt +++ b/core/libs/prx/libkernel_sys/CMakeLists.txt @@ -1,3 +1,5 @@ add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp Aio.cpp ThreadQueries.cpp) target_include_directories(${LIB_NAME} PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(${LIB_NAME}) + +target_sources(${LIB_NAME} PRIVATE Uuid.cpp) diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 7a1880f16c..91a67b15ac 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -2355,11 +2355,6 @@ int APS5_VABI utrace_nid_postfix(void) { return 0; } -int APS5_VABI uuidgen_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI vfork_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libkernel_sys/Uuid.cpp b/core/libs/prx/libkernel_sys/Uuid.cpp new file mode 100644 index 0000000000..917aec58fc --- /dev/null +++ b/core/libs/prx/libkernel_sys/Uuid.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostEntropy.hpp" + +extern "C" int* APS5_VABI __error_nid_postfix(); +extern "C" int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); + +namespace { + +struct GuestUuid { + std::uint32_t timeLow; + std::uint16_t timeMid; + std::uint16_t timeHiAndVersion; + std::uint8_t clockSeqHiAndReserved; + std::uint8_t clockSeqLow; + std::array node; +}; +static_assert(sizeof(GuestUuid) == 16); + +constexpr int GuestEfault = 14; +constexpr int GuestEinval = 22; +constexpr int GuestClockRealtime = 0; +constexpr int UuidgenMaxCount = 2048; +constexpr std::uint64_t UuidTimeMask = (std::uint64_t{1} << 60) - 1; +constexpr std::uint64_t UuidGregorianOffset = 0x01B21DD213814000ULL; + +std::mutex uuidMutex; +std::array uuidNode{}; +bool uuidNodeReady = false; +std::uint64_t uuidLastTime = 0; +std::uint16_t uuidLastSequence = 0; + +int Fail(int error) { + *__error_nid_postfix() = error; + return -1; +} + +std::uint64_t UuidTime() { + KernelTimespec now{}; + if (clock_gettime_nid_postfix(GuestClockRealtime, &now) != 0) throw std::runtime_error("uuidgen: clock_gettime(CLOCK_REALTIME) failed"); + const auto ticks = UuidGregorianOffset + static_cast(now.tv_sec) * 10000000ULL + static_cast(now.tv_nsec) / 100; + return ticks & UuidTimeMask; +} + +} + +extern "C" { + +int APS5_VABI uuidgen_nid_postfix(GuestUuid* store, int count) { + if (count < 1 || count > UuidgenMaxCount) return Fail(GuestEinval); + std::uint64_t time = 0; + std::uint16_t sequence = 0; + std::array node{}; + { + std::lock_guard lock(uuidMutex); + if (!uuidNodeReady) { + HostEntropy_nid_no_patch(uuidNode.data(), uuidNode.size()); + uuidNode[0] |= 0x01; + uuidNodeReady = true; + } + node = uuidNode; + time = UuidTime(); + if (uuidLastTime == 0) { + HostEntropy_nid_no_patch(&sequence, sizeof(sequence)); + sequence &= 0x3fff; + } else if (uuidLastTime >= time) { + sequence = static_cast((uuidLastSequence + 1) & 0x3fff); + } else { + sequence = uuidLastSequence; + } + uuidLastSequence = sequence; + uuidLastTime = (time + static_cast(count) - 1) & UuidTimeMask; + } + if (store == nullptr) return Fail(GuestEfault); + const auto variantSequence = static_cast(sequence | 0x8000); + for (int index = 0; index < count; ++index, ++time) { + store[index].timeLow = static_cast(time); + store[index].timeMid = static_cast(time >> 32); + store[index].timeHiAndVersion = static_cast(((time >> 48) & 0x0fff) | 0x1000); + store[index].clockSeqHiAndReserved = static_cast(variantSequence >> 8); + store[index].clockSeqLow = static_cast(variantSequence); + store[index].node = node; + } + return 0; +} + +} diff --git a/core/libs/tests/GuestKernelSysUuid.cpp b/core/libs/tests/GuestKernelSysUuid.cpp new file mode 100644 index 0000000000..d27b2edfb8 --- /dev/null +++ b/core/libs/tests/GuestKernelSysUuid.cpp @@ -0,0 +1,72 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI uuidgen_nid_postfix(void*, int); +int* APS5_VABI __error_nid_postfix(); +} + +namespace { + +struct Uuid { + std::uint32_t timeLow; + std::uint16_t timeMid; + std::uint16_t timeHiAndVersion; + std::uint8_t clockSeqHiAndReserved; + std::uint8_t clockSeqLow; + std::uint8_t node[6]; +}; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "uuidgen check failed at line %u (guest errno %d)\n", location.line(), *__error_nid_postfix()); + std::abort(); + } +} + +int Error() { + return *__error_nid_postfix(); +} + +std::uint64_t Timestamp(const Uuid& uuid) { + return (static_cast(uuid.timeHiAndVersion & 0x0fff) << 48) | (static_cast(uuid.timeMid) << 32) | uuid.timeLow; +} + +std::uint16_t Sequence(const Uuid& uuid) { + return static_cast(((uuid.clockSeqHiAndReserved & 0x3f) << 8) | uuid.clockSeqLow); +} + +} + +int main() { + std::array first{}; + std::array second{}; + Require(uuidgen_nid_postfix(first.data(), 0) == -1 && Error() == 22); + Require(uuidgen_nid_postfix(first.data(), 2049) == -1 && Error() == 22); + Require(uuidgen_nid_postfix(nullptr, 1) == -1 && Error() == 14); + Require(uuidgen_nid_postfix(first.data(), static_cast(first.size())) == 0); + Require(uuidgen_nid_postfix(second.data(), static_cast(second.size())) == 0); + for (const auto& uuid : first) { + Require((uuid.timeHiAndVersion >> 12) == 1); + Require((uuid.clockSeqHiAndReserved & 0xc0) == 0x80); + Require((uuid.node[0] & 0x01) != 0); + Require(std::memcmp(uuid.node, first[0].node, sizeof(uuid.node)) == 0); + Require(Sequence(uuid) == Sequence(first[0])); + } + Require(Timestamp(first[1]) == Timestamp(first[0]) + 1 && Timestamp(first[2]) == Timestamp(first[0]) + 2); + Require(std::memcmp(second[0].node, first[0].node, sizeof(first[0].node)) == 0); + const bool later = Timestamp(second[0]) > Timestamp(first[2]) && Sequence(second[0]) == Sequence(first[0]); + const bool bumped = Timestamp(second[0]) <= Timestamp(first[2]) && Sequence(second[0]) == ((Sequence(first[0]) + 1) & 0x3fff); + Require(later || bumped); + const auto unixTicks = static_cast(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count() / 100); + const auto gregorianTicks = unixTicks + 0x01B21DD213814000ULL; + const auto stamp = Timestamp(first[0]); + Require(stamp + 600000000ULL > gregorianTicks && stamp < gregorianTicks + 600000000ULL); +} diff --git a/docs/dev/debt/libkernel_sys.md b/docs/dev/debt/libkernel_sys.md index 3088794aaa..0c0d76b359 100644 --- a/docs/dev/debt/libkernel_sys.md +++ b/docs/dev/debt/libkernel_sys.md @@ -23,6 +23,7 @@ - [link](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) on Windows resolves a symbolic link source through the host, which follows longer chains than FreeBSD: a chain of 40 links is linked, where FreeBSD 11.4 returns `ELOOP` after 32 (`MAXSYMLINKS`). A loop returns `ELOOP` as on FreeBSD - [pthread_attr_setcreatesuspend_np, pthread_resume_np, pthread_resume_all_np, pthread_multi_np, scePthreadAttrSetcreatesuspend, scePthreadResume, scePthreadResumeAll and scePthreadMulti](../../../core/libs/prx/libkernel_sys/Export.Discovery.cpp) (libkernel_sys) - when a thread created suspended stops relative to its signal mask is unmeasured on a console. Return values and errors follow FreeBSD 11.4 libthr ([`thr_attr.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_attr.c#L369-L381), [`thr_resume_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_resume_np.c#L47-L97), [`thr_multi_np.c`](https://github.com/freebsd/freebsd-src/blob/8c639945/lib/libthr/thread/thr_multi_np.c#L40-L51)) and the code of firmware 13.42's `libkernel_sys.sprx` ([#9573](https://github.com/boykopovar/AnyPS5/pull/9573#issuecomment-6393877843)), where the sce forms add `0x80020000` to a non-zero errno - [fstatat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `stat`, or to `lstat` with `AT_SYMLINK_NOFOLLOW` (0x200). Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory, and flags other than `AT_SYMLINK_NOFOLLOW` return `EINVAL`. PPSA74126 imports it. Console behaviour is unverified +- [uuidgen](../../../core/libs/prx/libkernel_sys/Uuid.cpp) (libkernel_sys) - the node is 48 random bits with the multicast bit set, chosen once per process, where the console's kernel uses the address of its first Ethernet interface; that the PS5 kernel registers one is assumed, not measured. Otherwise it follows FreeBSD 11's [`kern_uuidgen` and `sys_uuidgen`](https://github.com/freebsd/freebsd-src/blob/53c4471833e8e5e57b4d1b501b1a52b343bb0451/sys/kern/kern_uuid.c#L129-L198): version 1 UUIDs with consecutive timestamps from the guest `CLOCK_REALTIME`, a random 14-bit clock sequence that is incremented when the clock has not passed the last batch, and `EINVAL` for a count outside 1 to 2048. A null store returns `EFAULT` after the state advances, as `copyout` fails there; other invalid addresses are not detected - [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rmdir` (with `AT_REMOVEDIR`, 0x800) or `unlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Removing a directory without `AT_REMOVEDIR` fails with `EPERM`, removing `.` or `..` with `AT_REMOVEDIR` fails with `EINVAL`, and flags other than `AT_REMOVEDIR` fail with `EINVAL`. Console behaviour is unverified - [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified - [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask