#include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include #include extern "C" { int APS5_VABI sceKernelDlsym(std::int32_t, const char*, void**); void* APS5_VABI dlopen_nid_postfix(const char*, int); void* APS5_VABI dlsym_nid_postfix(void*, const char*); 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*); } static void Require(bool value, const std::source_location& location = std::source_location::current()) { if (value) return; std::fprintf(stderr, "guest_dynamic_loader: failed at %s:%u\n", location.file_name(), location.line()); std::abort(); } extern "C" int APS5_VABI GuestDefaultScopeOnly(int a, int b) { return a * b; } template static bool ThrowsRuntimeError(TFunction function) { try { function(); } catch (const std::runtime_error&) { return true; } return false; } #ifdef _WIN32 static void CheckGlobalLookupOrder(const char* path) { const std::filesystem::path other = "anyps5-global-order-for-test.guest.prx"; std::filesystem::copy_file(path, other, std::filesystem::copy_options::overwrite_existing); void* local = dlopen_nid_postfix(path, 2); void* earlierGlobal = dlopen_nid_postfix(other.string().c_str(), 2 | 0x100); void* promoted = dlopen_nid_postfix(path, 2 | 0x100); Require(local && earlierGlobal && promoted); void* firstSymbol = dlsym_nid_postfix(local, "GuestModuleAdd"); void* earlierSymbol = dlsym_nid_postfix(earlierGlobal, "GuestModuleAdd"); Require(firstSymbol && earlierSymbol && firstSymbol != earlierSymbol); const std::array scopes{nullptr, reinterpret_cast(-2)}; for (void* scope : scopes) Require(dlsym_nid_postfix(scope, "GuestModuleAdd") == earlierSymbol); Require(dlclose_nid_postfix(promoted) == 0); for (void* scope : scopes) Require(dlsym_nid_postfix(scope, "GuestModuleAdd") == earlierSymbol); Require(dlclose_nid_postfix(local) == 0); Require(dlclose_nid_postfix(earlierGlobal) == 0); std::filesystem::remove(other); } static bool CheckGlobalVisibility(const char* path, bool globalFirst) { using Add = int (APS5_VABI *)(int, int); const std::array scopes{nullptr, reinterpret_cast(-2)}; const auto requireHidden = [&] { for (void* scope : scopes) { Require(dlsym_nid_postfix(scope, "GuestModuleAdd") == nullptr); Require(dlerror_nid_postfix() != nullptr); } }; requireHidden(); void* first = dlopen_nid_postfix(path, 2 | (globalFirst ? 0x100 : 0)); Require(first != nullptr); if (!globalFirst) requireHidden(); void* second = dlopen_nid_postfix(path, 2 | (globalFirst ? 0 : 0x100)); Require(second != nullptr && second != first); auto add = reinterpret_cast(dlsym_nid_postfix(first, "GuestModuleAdd")); Require(add && add(19, 23) == 42); Require(dlsym_nid_postfix(second, "GuestModuleAdd") == reinterpret_cast(add)); for (void* scope : scopes) Require(dlsym_nid_postfix(scope, "GuestModuleAdd") == reinterpret_cast(add)); void* global = globalFirst ? first : second; void* local = globalFirst ? second : first; Require(dlclose_nid_postfix(global) == 0); Require(dlsym_nid_postfix(local, "GuestModuleAdd") == reinterpret_cast(add)); Require(add(20, 22) == 42); bool visible = true; for (void* scope : scopes) { const bool found = dlsym_nid_postfix(scope, "GuestModuleAdd") == reinterpret_cast(add); if (!found) { std::fprintf(stderr, "RTLD_GLOBAL lost after closing its handle: global_first=%d scope=%s; surviving handle returns %d\n", globalFirst, scope == nullptr ? "NULL" : "-2", add(20, 22)); dlerror_nid_postfix(); visible = false; } } Require(dlclose_nid_postfix(local) == 0); requireHidden(); void* reloaded = dlopen_nid_postfix(path, 2); Require(reloaded != nullptr); auto reloadedAdd = reinterpret_cast(dlsym_nid_postfix(reloaded, "GuestModuleAdd")); Require(reloadedAdd && reloadedAdd(21, 21) == 42); requireHidden(); Require(dlclose_nid_postfix(reloaded) == 0); return visible; } #endif int main(int argc, char** argv) { Require(argc == 2); Require(dlerror_nid_postfix() == nullptr); Require(dlopen_nid_postfix(argv[1], 0x2000) == nullptr); Require(dlerror_nid_postfix() != nullptr); Require(dlerror_nid_postfix() == nullptr); Require(dlopen_nid_postfix("anyps5-missing-module-for-test.prx", 2) == nullptr); Require(dlerror_nid_postfix() != nullptr); void* mainSymbol = nullptr; const int mainResult = sceKernelDlsym(0, "GuestDefaultScopeOnly", &mainSymbol); std::fprintf(stderr, "sceKernelDlsym(0, GuestDefaultScopeOnly): result=0x%08x, symbol=%p\n", static_cast(mainResult), mainSymbol); Require(mainResult == 0 && mainSymbol != nullptr); using MainFunction = int (APS5_VABI *)(int, int); Require(reinterpret_cast(mainSymbol)(6, 7) == 42); Require(sceKernelDlsym(0, "anyps5_missing_main_symbol", &mainSymbol) == static_cast(0x80020003u)); dlerror_nid_postfix(); void* executable = dlopen_nid_postfix(nullptr, 2); Require(executable != nullptr && dlclose_nid_postfix(executable) == 0); #ifdef _WIN32 CheckGlobalLookupOrder(argv[1]); const bool localFirstVisible = CheckGlobalVisibility(argv[1], false); const bool globalFirstVisible = CheckGlobalVisibility(argv[1], true); Require(localFirstVisible && globalFirstVisible); #endif void* module = dlopen_nid_postfix(argv[1], 2 | 0x100); Require(module != nullptr); using Add = int (APS5_VABI *)(int, int); 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); auto sub = reinterpret_cast(dlsym_nid_postfix(module, "GuestModuleSub")); Require(sub && sub(50, 8) == 42); #endif Require(dlsym_nid_postfix(module, "missing_symbol") == nullptr); std::thread other([] { Require(dlerror_nid_postfix() == nullptr); }); other.join(); Require(dlerror_nid_postfix() != nullptr); Require(dlerror_nid_postfix() == nullptr); void* second = dlopen_nid_postfix(argv[1], 1); Require(second && second != module); Require(dlclose_nid_postfix(module) == 0); Require(dlsym_nid_postfix(module, "GuestModuleAdd") == nullptr); add = reinterpret_cast(dlsym_nid_postfix(second, "GuestModuleAdd")); Require(add && add(2, 3) == 5); Require(dlclose_nid_postfix(second) == 0); Require(dlclose_nid_postfix(second) == -1); const std::filesystem::path guest = "anyps5-relinked-module-for-test.prx"; auto relinked = guest; relinked += ".guest.prx"; { std::ofstream elf(guest, std::ios::binary); elf.write("\x7f" "ELF", 4); } std::filesystem::copy_file(argv[1], relinked, std::filesystem::copy_options::overwrite_existing); void* redirected = dlopen_nid_postfix(guest.string().c_str(), 2); Require(redirected != nullptr); add = reinterpret_cast(dlsym_nid_postfix(redirected, "GuestModuleAdd")); Require(add && add(40, 2) == 42); Require(dlclose_nid_postfix(redirected) == 0); std::filesystem::remove(guest); std::filesystem::remove(relinked); void* pinned = dlopen_nid_postfix(argv[1], 2); add = reinterpret_cast(dlsym_nid_postfix(pinned, "GuestModuleAdd")); Require(add && _sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast(add)) == 0); Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast(add)) == 0); Require(dlclose_nid_postfix(pinned) == 0); Require(add(20, 22) == 42); Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(add)) == 0); Require(add(40, 2) == 42); Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(add)) == 0); Require(_sceKernelRtldThreadAtexitIncrement_nid_postfix(reinterpret_cast(&ThrowsRuntimeError)) == 0); Require(_sceKernelRtldThreadAtexitDecrement_nid_postfix(reinterpret_cast(&ThrowsRuntimeError)) == 0); 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); void* hostMul = ::dlsym(host, "GuestModuleMul#guest"); void* hostSub = ::dlsym(host, "BOyBJaKwOa8#guest"); Require(hostMul != nullptr && hostSub != nullptr); for (void* scope : std::array{nullptr, reinterpret_cast(-2)}) { auto defaultMul = reinterpret_cast(dlsym_nid_postfix(scope, "GuestModuleMul")); auto defaultSub = reinterpret_cast(dlsym_nid_postfix(scope, "GuestModuleSub")); Require(reinterpret_cast(defaultMul) == hostMul); Require(reinterpret_cast(defaultSub) == hostSub); Require(defaultMul(6, 7) == 42 && defaultSub(50, 8) == 42); } Require(dlsym_nid_postfix(nullptr, "malloc") == nullptr); Require(::dlclose(host) == 0); #endif std::puts("guest_dynamic_loader: all checks passed"); }