From 5fab03e4e51356d97f6d9bb55c4a0de91d40cbd1 Mon Sep 17 00:00:00 2001 From: Nahuel Genchi <65604505+NahuelGenchi@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:58:01 -0300 Subject: [PATCH] fix(relinker): keep host prx references out of guest modules on Linux On Linux every module resolves its references through one global scope, and the guest modules are listed ahead of the host prx. When a title bundles its own libc.prx, the host libc and the other host prx bound their references to their own NIDs to the guest libc, so the host libc built its standard streams on top of the guest _Stdout. The exports of guest modules, and the guest and executable imports that a guest module satisfies, now carry a #guest suffix. The title's imports still reach the guest modules first, and a host prx only sees host definitions, as on Windows. sceKernelDlsym also looks for the suffixed name. --- .../libkernel/Module/src/DynamicLoader.cpp | 8 ++ core/libs/tests/GuestDynamicLoader.cpp | 6 + core/libs/tests/GuestModuleFixture.cpp | 11 ++ core/relinker/CMakeLists.txt | 2 + .../include/relinker/guest/GuestImage.hpp | 1 + .../relinker/src/guest/GuestModuleBuilder.cpp | 16 +++ .../relinker/tests/test_guest_symbol_names.py | 132 ++++++++++++++++++ 7 files changed, 176 insertions(+) create mode 100644 core/relinker/relinker/tests/test_guest_symbol_names.py diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index e1091def8..8a53d9cc0 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #ifdef _WIN32 #define NOMINMAX @@ -47,7 +48,14 @@ void* Symbol(Module& module, const char* name) { void* FindSymbol(Module& module, const char* name) { if (auto* symbol = Symbol(module, name)) return symbol; const auto nid = Nid::ComputeNid(name, ""); +#ifdef _WIN32 return Symbol(module, nid.c_str()); +#else + if (auto* symbol = Symbol(module, nid.c_str())) return symbol; + constexpr char guestSuffix[] = "#guest"; + if (auto* symbol = Symbol(module, (std::string(name) + guestSuffix).c_str())) return symbol; + return Symbol(module, (nid + guestSuffix).c_str()); +#endif } } diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp index 2fa27e917..0259f64bd 100644 --- a/core/libs/tests/GuestDynamicLoader.cpp +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -24,6 +24,12 @@ 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)); +#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(); diff --git a/core/libs/tests/GuestModuleFixture.cpp b/core/libs/tests/GuestModuleFixture.cpp index e8f8521d6..05835ccb9 100644 --- a/core/libs/tests/GuestModuleFixture.cpp +++ b/core/libs/tests/GuestModuleFixture.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/general/ExportMacros.hpp" #ifdef _WIN32 #define MODULE_EXPORT __declspec(dllexport) #else @@ -7,3 +8,13 @@ extern "C" MODULE_EXPORT int APS5_VABI GuestModuleAdd_nid_postfix(int a, int b) { return a + b; } +#ifndef _WIN32 +extern "C" MODULE_EXPORT int APS5_VABI GuestModuleMul_nid_no_patch(int a, int b) { + return a * b; +} +APS5_EXPORT("GuestModuleMul#guest", GuestModuleMul_nid_no_patch); +extern "C" MODULE_EXPORT int APS5_VABI GuestModuleSub_nid_no_patch(int a, int b) { + return a - b; +} +APS5_EXPORT("BOyBJaKwOa8#guest", GuestModuleSub_nid_no_patch); +#endif diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 0dca9d1f2..26276046b 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -218,6 +218,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $) add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $) + add_test(NAME guest_symbol_names COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbol_names.py $) add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp index 8325e8208..7f50aba5b 100644 --- a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -10,6 +10,7 @@ namespace Relinker { +inline constexpr char GuestSymbolSuffix[] = "#guest"; inline constexpr char GuestModuleSuffix[] = ".guest.prx"; struct GuestSymbol { diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index cef7ff2cf..59e5b3289 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace Relinker { @@ -100,6 +101,11 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path for (const auto provider : shared->second) providers += " " + images[provider].SourcePath.string(); throw Domain::RelinkerException("Ambiguous guest import " + name + " in " + importer + ": exported by" + providers); }; + const auto rename = [](std::vector& symbols, std::vector& strings, std::size_t index, const std::string& name) { + if (strings.size() > std::numeric_limits::max()) throw Domain::RelinkerException("Guest string table too large"); + Io::WriteU32(symbols, index * 24, static_cast(strings.size())); + Io::AppendString(strings, name + GuestSymbolSuffix); + }; if (dynamic.DynSymData.size() % 24 != 0) throw Domain::RelinkerException("Invalid executable symbol table"); for (std::size_t offset = 0; offset < dynamic.DynSymData.size(); offset += 24) { if (Io::ReadU16(dynamic.DynSymData, offset + 6) != 0) continue; @@ -110,6 +116,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated executable symbol name"); const std::string name(start, end); rejectSharedImport(name.substr(0, name.find('#')), inputPath.string()); + if (!windows && exports.contains(name)) rename(dynamic.DynSymData, dynamic.DynStrData, offset / 24, name); } std::vector> dependencies(images.size()); for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs"); @@ -130,6 +137,15 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name); } } + if (!windows) { + for (auto& image : images) { + for (std::size_t index = 1; index < image.Symbols.size(); ++index) { + const auto& symbol = image.Symbols[index]; + const bool exported = symbol.Section != 0 && (symbol.Info >> 4) != 0 && symbol.Visibility != 1 && symbol.Visibility != 2; + if ((symbol.Section == 0 || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name); + } + } + } std::vector order; std::vector states(images.size()); const std::function visit = [&](std::size_t index) { diff --git a/core/relinker/relinker/tests/test_guest_symbol_names.py b/core/relinker/relinker/tests/test_guest_symbol_names.py new file mode 100644 index 000000000..35558254c --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_symbol_names.py @@ -0,0 +1,132 @@ +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import PLAIN_SITE, elf_loads, guest_fixture, main_fixture + +DYNAMIC = 0x4800 +STRINGS = 0x4A00 +SYMBOLS = 0x4B00 +RELOCATIONS = 0x4C00 +GOT = 0x4E00 + + +def string_table(names): + strings = b"\0" + offsets = [] + for name in names: + offsets.append(len(strings)) + strings += name.encode() + b"\0" + return strings, offsets + + +def guest_module(symbols): + image = guest_fixture(PLAIN_SITE) + strings, offsets = string_table(name for name, _ in symbols) + assert len(strings) <= 0x40 + image[0x800:0x800 + len(strings)] = strings + struct.pack_into("> 32: + names.add(symbol_name(image, tags, info >> 32)) + return names + + +def convert(relinker, case): + (case / "sce_module").mkdir(parents=True) + source = case / "input.elf" + source.write_bytes(importing_executable(["AAAAAAAAAAA#A#A", "DDDDDDDDDDD#D#D"])) + (case / "sce_module" / "libc.prx").write_bytes( + guest_module([("AAAAAAAAAAA#A#A", True), ("BBBBBBBBBBB#B#B", False), ("CCCCCCCCCCC#C#C", False), ("EEEEEEEEEEE", True)])) + (case / "sce_module" / "other.prx").write_bytes(guest_module([("BBBBBBBBBBB#B#B", True), ("EEEEEEEEEEE", True)])) + output = case / "output.elf" + result = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=30) + assert result.returncode == 0, (result.stdout, result.stderr) + return output + + +def main(): + relinker = Path(sys.argv[1]).resolve() + with tempfile.TemporaryDirectory(prefix="anyps5-guest-symbol-names-") as directory: + work = Path(directory) + + output = convert(relinker, work) + modules = output.parent / "app0" / "sce_module" + libc = module_symbols((modules / "libc.prx.guest.prx").read_bytes()) + assert libc == ["AAAAAAAAAAA#guest", "BBBBBBBBBBB#guest", "CCCCCCCCCCC", "EEEEEEEEEEE#guest"], libc + other = module_symbols((modules / "other.prx.guest.prx").read_bytes()) + assert other == ["BBBBBBBBBBB#guest", "EEEEEEEEEEE#guest"], other + imports = imported_symbols(output.read_bytes()) + assert imports == {"AAAAAAAAAAA#guest", "DDDDDDDDDDD"}, imports + print("Guest symbol name tests passed") + + +if __name__ == "__main__": + main()