Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion core/libs/prx/libkernel/Module/src/Module.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
Expand Down Expand Up @@ -153,10 +155,22 @@ extern "C" {
void* APS5_VABI dlopen_nid_postfix(const char* path, int flags);
void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name);
int APS5_VABI dlclose_nid_postfix(void* handle);
const char* APS5_VABI sceKernelGetFsSandboxRandomWord();
}

namespace {
constexpr int kRtldNow = 2;

std::optional<std::string> SystemLibraryPath(std::string_view path) {
const std::string sandbox = std::string("/") + sceKernelGetFsSandboxRandomWord() + "/common/lib/";
for (const std::string_view prefix : {std::string_view("/system/common/lib/"), std::string_view(sandbox)}) {
if (!path.starts_with(prefix)) continue;
const auto name = path.substr(prefix.size());
if (name.size() <= 5 || !name.ends_with(".sprx") || name.find('/') != std::string_view::npos) return std::nullopt;
return "libs/" + std::string(name.substr(0, name.size() - 5)) + ".prx";
}
return std::nullopt;
}
}

extern "C" {
Expand Down Expand Up @@ -281,7 +295,8 @@ KernelModule APS5_VABI sceKernelLoadStartModule(const char* module_file_name, si
if (!module_file_name) return static_cast<KernelModule>(SCE_KERNEL_ERROR_EFAULT);
pendingModuleArgs = {args, argp};
pendingModuleInitResult = 0;
void* handle = dlopen_nid_postfix(module_file_name, kRtldNow);
const auto systemLibrary = SystemLibraryPath(module_file_name);
void* handle = dlopen_nid_postfix(systemLibrary ? systemLibrary->c_str() : module_file_name, kRtldNow);
const int started = pendingModuleInitResult;
pendingModuleArgs = {};
pendingModuleInitResult = 0;
Expand Down
21 changes: 21 additions & 0 deletions core/libs/tests/GuestDynamicLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ int APS5_VABI dlclose_nid_postfix(void*);
char* APS5_VABI dlerror_nid_postfix();
int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(const void*);
int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(const void*);
int APS5_VABI sceKernelLoadStartModule(const char*, std::size_t, const void*, unsigned, const void*, int*);
int APS5_VABI sceKernelStopUnloadModule(int, std::size_t, const void*, unsigned, const void*, int*);
}
static void Require(bool value, const std::source_location& location = std::source_location::current()) {
if (value) return;
Expand Down Expand Up @@ -166,6 +168,25 @@ int main(int argc, char** argv) {
std::filesystem::remove(guest);
std::filesystem::remove(relinked);

const bool createdLibs = std::filesystem::create_directories("libs");
std::filesystem::copy_file(argv[1], "libs/anyps5-system-module-for-test.prx", std::filesystem::copy_options::overwrite_existing);
for (const char* path : {"/gkbHtXc3Wq/common/lib/anyps5-system-module-for-test.sprx", "/system/common/lib/anyps5-system-module-for-test.sprx"}) {
int started = -1;
const int system = sceKernelLoadStartModule(path, 0, nullptr, 0, nullptr, &started);
Require(system > 0 && started == 0);
add = reinterpret_cast<Add>(dlsym_nid_postfix(reinterpret_cast<void*>(static_cast<std::intptr_t>(system)), "GuestModuleAdd"));
Require(add && add(30, 12) == 42);
Require(sceKernelStopUnloadModule(system, 0, nullptr, 0, nullptr, nullptr) == 0);
}
Require(sceKernelLoadStartModule("/gkbHtXc3Wq/common/lib/anyps5-missing-system-module.sprx", 0, nullptr, 0, nullptr, nullptr) == static_cast<int>(0x80020002u));
Require(sceKernelLoadStartModule("/gkbHtXc3Wq/common/lib/anyps5-system-module-for-test.prx", 0, nullptr, 0, nullptr, nullptr) == static_cast<int>(0x80020002u));
std::filesystem::copy_file(argv[1], "anyps5-escape-module-for-test.prx", std::filesystem::copy_options::overwrite_existing);
Require(sceKernelLoadStartModule("/gkbHtXc3Wq/common/lib/../anyps5-escape-module-for-test.sprx", 0, nullptr, 0, nullptr, nullptr) == static_cast<int>(0x80020002u));
std::filesystem::remove("anyps5-escape-module-for-test.prx");
std::filesystem::remove("libs/anyps5-system-module-for-test.prx");
std::error_code notEmpty;
if (createdLibs) std::filesystem::remove("libs", notEmpty);

void* pinned = dlopen_nid_postfix(argv[1], 2);
add = reinterpret_cast<Add>(dlsym_nid_postfix(pinned, "GuestModuleAdd"));
Require(add && _sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast<const void*>(add)) == 0);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libkernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
- [rtprio_thread, pthread_setconcurrency, pthread_getconcurrency](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - `rtprio_thread` follows FreeBSD 11.4's `rtprio_thread(2)`. Every guest thread here runs under `SCHED_FIFO` or `SCHED_RR` (priority 256-767), whose rtprio class is real-time; the console's mapping from that priority to the rtprio `prio` range (0-31) is unmeasured, so `RTP_LOOKUP` and `RTP_SET` throw for such a thread rather than report an impossible priority. A thread that is not real-time reports `RTP_PRIO_NORMAL` (3) with 0, and `RTP_SET` accepts only that value. Another function fails with `EINVAL`, a null `rtprio` with `EFAULT`, a thread other than the calling one throws, and an id that is not a live guest thread with `ESRCH`. `pthread_setconcurrency` stores 0 or more and returns `EINVAL` for a negative level (the error itself, as FreeBSD's libthr does, with errno untouched); `pthread_getconcurrency` returns the level, which is a hint and changes nothing about the host's concurrency. Morrowind (`mshivam019/morrowind-ps5`) imports all three at start-up
- [sceKernelVirtualQuery](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) returns [VirtualQueryInfo](../../../core/libs/SceTypes.hpp) with a one-byte flag allocation unit so Windows and SysV hosts return the same 72-byte representation, with the name at byte 33 and GPU mask at byte 65. This preserves the existing SysV representation; the console offsets beyond the address, protection and size fields observed in Astro Bot's AGC calls are not hardware-measured. Size and offset assertions plus a raw-byte named-mapping regression guard the host ABI.
- [app0.zar files](../../../core/libs/prx/libkernel/File/src/ArchivedFile.cpp) (libkernel) - `sceKernelOpen`, `open`, `sceKernelStat` and `sceKernelCheckReachability` resolve a path missing from `app0/` in `app0.zar`. An archived file is a read-only host null device whose reads, positioned and vectored reads, AIO reads, seeks, `fstat` and `FIONREAD` are served from the archive, with the position shared by `dup`, `dup2` and `F_DUPFD`; writes fail with `EBADF` as on a read-only descriptor, opening one for writing or truncation fails with `EROFS` and `O_CREAT | O_EXCL` with `EEXIST`. `fstat` and `sceKernelStat` report mode `0444` (`0555` for a directory), the node index as inode and the archive file's device and times. Opening an archived directory or a directory present both on disk and in the archive throws, as `getdents` would miss entries. On an archived descriptor `mmap`, `flock`, record locks, `fstatfs`, `ftruncate`, `fsync`, `fdatasync`, `getdirentries`, `fchmod` and `futimes` throw instead of acting on the null device; `mmap` is `noexcept`, so its refusal terminates the process. `lstat` and the other path calls on archived files are not implemented
- [sceKernelLoadStartModule](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) loads a system library named by its console path, `/system/common/lib/<name>.sprx` or the sandboxed `/<sandbox word>/common/lib/<name>.sprx` that `sceKernelGetFsSandboxRandomWord` names, from the host `libs/<name>.prx`, as libSceSysmodule loads modules by ID; a host library has no `module_start`, so `res` is 0. Other system directories, such as `/system/priv/lib`, are not mapped. The OpenOrbis piglet sample loads `libScePigletv2VSH.sprx` and `libScePrecompiledShaders.sprx` this way
- [param.sfo](../../../core/libs/prx/libkernel/AppMetadata/src/ParamSfoParser.cpp) (libkernel) is read when `param.json` is absent, for `TITLE`, `TITLE_ID`, `DOWNLOAD_DATA_SIZE` and `USER_DEFINED_PARAM_1` to `_4`, and mounts `/data` and `/download0` like `param.json`; the localized `TITLE_00` to `TITLE_30` are not selected by the console language.
- [sysctl MIBs for kern.smp.maxcpus, kern.stackprot, kern.sched.name and kern.timecounter.hardware](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) - on a PS5 (firmware 12.70), a payload process resolves these names to {1, 2147482816, 2147482814}, {1, 2147483151}, {1, 2147483182, 2147483181} and {1, 2147482838, 2147482834}; `sysctl` returns -1 with `ENOENT` for reads and size queries by those exact MIBs, leaving the buffer and length unchanged, including when either or both output pointers are null. Writes were not measured and throw. `sysctlnametomib` still throws for these four names; #7541 implements the mappings for nine of the other names.
- [sceKernelGetProcessAbiType](../../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) throws: no measured value, prototype or documented consumer of the process ABI type was found, and PS5_MihawkOS (`mihawk-99/PS5_MihawkOS`, PPSA99170) is the only title known to import it. Returning a guessed constant would let a title branch on a value the console never answered with, so the declaration only resolves the import until the console value is measured
Loading