From 34616c425b66af703ce0542b7583397ca9d6e64d Mon Sep 17 00:00:00 2001 From: jeffrey12cali <27741664+jeffrey12cali@users.noreply.github.com> Date: Wed, 7 Oct 2026 19:25:05 +0200 Subject: [PATCH 1/2] fix(module): resolve the default dlsym scope against guest exports only Titles pass handle 0 (RTLD_DEFAULT) for a process-wide lookup, and the loader rejected it: the handle is not in the module map, so every such lookup failed even when a loaded module exports the symbol. Handle 0 and -2 now take the default scope, searching the global modules by plain name, NID and `#guest` form, and then the process-wide scope. The process-wide scope of the real loader also contains glibc and libstdc++, so an unfiltered fallback would answer `malloc`, `dlopen` and `pthread_create` where PS5 reports the symbol as missing: handle 0 is the main program, whose lookup sees the program's exports, not the host C++ runtime. A result from the process scope is therefore accepted only when `dladdr` attributes it to a converted or replacement module (a `.prx` library or the executable), which keeps the fallback for guest symbols and removes the host leak. `guest_dynamic_loader` asserts the three host names are not found; disabling the filter makes that test fail. The scripting heap that an earlier revision of this branch added has been dropped: the title reaches the same boot milestone with the lookup failing honestly, so the export was an inference rather than a requirement, and prosper's bring-up notes report `ESRCH` for that name on hardware. --- core/libs/prx/libkernel/CMakeLists.txt | 3 ++ .../libkernel/Module/src/DynamicLoader.cpp | 43 ++++++++++++++++++- core/libs/tests/GuestDynamicLoader.cpp | 22 ++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 6055d0de5..2520ab477 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -104,6 +104,9 @@ if(BUILD_TESTING) target_link_libraries(guest_dynamic_loader_tests PRIVATE libkernel libc) configure_windows_unwind(guest_dynamic_loader_tests) add_dependencies(guest_dynamic_loader_tests guest_module_fixture) + if(NOT WIN32) + set_target_properties(guest_dynamic_loader_tests PROPERTIES ENABLE_EXPORTS ON) + endif() add_test(NAME guest_dynamic_loader COMMAND guest_dynamic_loader_tests $) set_tests_properties(guest_dynamic_loader PROPERTIES WORKING_DIRECTORY "$") diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index d2604c94d..509194a50 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -15,6 +15,7 @@ #include #else #include +#include #endif namespace { @@ -49,6 +50,39 @@ void* Symbol(Module& module, const char* name) { return ::dlsym(module.native, name); #endif } +#ifndef _WIN32 +std::uintptr_t MainProgramBase() { + static const std::uintptr_t base = [] { + struct Scan { + std::uintptr_t value = 0; + }; + Scan scan; + ::dl_iterate_phdr( + [](dl_phdr_info* info, size_t, void* data) { + auto* found = static_cast(data); + if (found->value == 0 && info->dlpi_name != nullptr && info->dlpi_name[0] == '\0') found->value = info->dlpi_addr; + return found->value == 0 ? 0 : 1; + }, + &scan); + return scan.value; + }(); + return base; +} + +bool FromGuestModule(void* address) { + Dl_info info{}; + if (address == nullptr || !::dladdr(address, &info) || info.dli_fname == nullptr) return false; + if (reinterpret_cast(info.dli_fbase) == MainProgramBase()) return true; + const std::string path(info.dli_fname); + return path.ends_with(".prx"); +} + +void* DefaultScopeSymbol(const char* symbolName) { + auto* result = ::dlsym(RTLD_DEFAULT, symbolName); + return FromGuestModule(result) ? result : nullptr; +} +#endif + void* FindSymbol(Module& module, const char* name) { if (auto* symbol = Symbol(module, name)) return symbol; const auto nid = Nid::ComputeNid(name, ""); @@ -116,7 +150,7 @@ void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) { std::vector> search; { std::lock_guard lock(modulesMutex); - if (handle == reinterpret_cast(static_cast(-2))) { + if (handle == nullptr || handle == reinterpret_cast(static_cast(-2))) { for (const auto& [key, module] : modules) if (module->global) search.push_back(module); } else { auto found = modules.find(reinterpret_cast(handle)); @@ -125,6 +159,13 @@ void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) { } } for (const auto& module : search) if (auto* result = FindSymbol(*module, name)) return result; +#ifndef _WIN32 + if (handle == nullptr || handle == reinterpret_cast(static_cast(-2))) { + if (auto* result = DefaultScopeSymbol(name)) return result; + const auto nid = Nid::ComputeNid(name, ""); + if (auto* result = DefaultScopeSymbol(nid.c_str())) return result; + } +#endif Error("dlsym: symbol not found in supported module scope"); return nullptr; } catch (const std::exception& error) { Error(error.what()); return nullptr; } diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp index 1b3172e67..93e68580d 100644 --- a/core/libs/tests/GuestDynamicLoader.cpp +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -2,6 +2,9 @@ #include #include #include +#ifndef _WIN32 +#include +#endif #include #include extern "C" { @@ -13,6 +16,9 @@ int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(const void*); int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(const void*); } static void Require(bool value) { if (!value) std::abort(); } +#ifndef _WIN32 +extern "C" int APS5_VABI GuestDefaultScopeOnly(int a, int b) { return a * b; } +#endif template static bool ThrowsRuntimeError(TFunction function) { try { @@ -38,6 +44,7 @@ int main(int argc, char** argv) { auto add = reinterpret_cast(dlsym_nid_postfix(module, "GuestModuleAdd")); Require(add && add(17, 25) == 42); Require(dlsym_nid_postfix(reinterpret_cast(-2), "GuestModuleAdd") == reinterpret_cast(add)); + Require(dlsym_nid_postfix(nullptr, "GuestModuleAdd") == reinterpret_cast(add)); #ifndef _WIN32 auto mul = reinterpret_cast(dlsym_nid_postfix(module, "GuestModuleMul")); Require(mul && mul(6, 7) == 42); @@ -88,4 +95,19 @@ int main(int argc, char** argv) { Require(ThrowsRuntimeError([] { _sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(&Require)); })); int local = 0; Require(ThrowsRuntimeError([&] { _sceKernelRtldThreadAtexitIncrement_nid_postfix(&local); })); +#ifndef _WIN32 + Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr); + Require(dlsym_nid_postfix(nullptr, "dlopen") == nullptr); + Require(dlsym_nid_postfix(nullptr, "pthread_create") == nullptr); Require(dlsym_nid_postfix(nullptr, "GuestDefaultScopeOnly") == reinterpret_cast(&GuestDefaultScopeOnly)); + Require(dlsym_nid_postfix(reinterpret_cast(-2), "GuestDefaultScopeOnly") == reinterpret_cast(&GuestDefaultScopeOnly)); + const auto hostLoaded = std::filesystem::absolute("anyps5-host-loaded-module-for-test.prx"); + std::filesystem::copy_file(argv[1], hostLoaded, std::filesystem::copy_options::overwrite_existing); + void* host = ::dlopen(hostLoaded.c_str(), RTLD_NOW | RTLD_GLOBAL); + Require(host != nullptr); + void* hostAdd = ::dlsym(host, "3qcG+yBRS+s"); + Require(hostAdd != nullptr); + Require(dlsym_nid_postfix(nullptr, "GuestModuleAdd") == hostAdd); + Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr); + Require(::dlclose(host) == 0); +#endif } From 0ecd75fd5413c5a42844bb85b0a613ce7d3c4d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 8 Oct 2026 14:30:59 +0200 Subject: [PATCH 2/2] test(libkernel): put the GuestDefaultScopeOnly assertion on its own line Co-Authored-By: Claude --- core/libs/tests/GuestDynamicLoader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp index 93e68580d..74223730f 100644 --- a/core/libs/tests/GuestDynamicLoader.cpp +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -98,7 +98,8 @@ int main(int argc, char** argv) { #ifndef _WIN32 Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr); Require(dlsym_nid_postfix(nullptr, "dlopen") == nullptr); - Require(dlsym_nid_postfix(nullptr, "pthread_create") == nullptr); Require(dlsym_nid_postfix(nullptr, "GuestDefaultScopeOnly") == reinterpret_cast(&GuestDefaultScopeOnly)); + Require(dlsym_nid_postfix(nullptr, "pthread_create") == nullptr); + Require(dlsym_nid_postfix(nullptr, "GuestDefaultScopeOnly") == reinterpret_cast(&GuestDefaultScopeOnly)); Require(dlsym_nid_postfix(reinterpret_cast(-2), "GuestDefaultScopeOnly") == reinterpret_cast(&GuestDefaultScopeOnly)); const auto hostLoaded = std::filesystem::absolute("anyps5-host-loaded-module-for-test.prx"); std::filesystem::copy_file(argv[1], hostLoaded, std::filesystem::copy_options::overwrite_existing);