From 65dba8a893588f48f4e7f6a0efd565ee0923ae15 Mon Sep 17 00:00:00 2001 From: roboterspinne <229981445+roboterspinne@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:27:02 +0100 Subject: [PATCH] fix(relinker): bind imports declared from a guest module only to that module's exports on Linux and macOS An import that declares a bundled guest module which does not export its NID was still renamed to the guest suffix on Linux and macOS when another bundled module exports the NID, so the dynamic linker bound it to that module. For guest-module imports the builder already chooses the provider only among the declared module's exports and records such an import as a system import of that module; the guest-module rename now follows that decision, and the executable rename applies the same rule, as Windows does. Such an import keeps its plain NID, and an executable import no longer makes an unrelated module from --module-dir start with the executable; undeclared and host-declared imports and Windows outputs are unchanged. --- .../relinker/src/guest/GuestModuleBuilder.cpp | 8 +++++--- .../relinker/tests/test_guest_symbol_names.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index d234bbe4db79..ef1fda52c085 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -244,7 +244,8 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } if (needed != 1) rejectSharedImport(base, Io::Utf8Path(inputPath)); const auto found = exports.find(name); - if (!windows && found != exports.end()) { + const auto declared = windows || executableModules[offset / 24].empty() ? guestNames.end() : findGuest(executableModules[offset / 24]); + if (!windows && found != exports.end() && (declared == guestNames.end() || std::any_of(found->second.begin(), found->second.end(), [&](const auto& candidate) { return candidate.Provider == declared->second; }))) { rename(dynamic.DynSymData, dynamic.DynStrData, offset / 24, name); if (executableReferences[offset / 24] && found->second.size() == 1) executableProviders.insert(found->second.front().Provider); } @@ -327,11 +328,12 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path }; if (!windows) { const auto executableExports = macos ? std::set{} : ExportedNames(dynamic); - for (auto& image : images) { + for (std::size_t module = 0; module < images.size(); ++module) { + auto& image = images[module]; 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 && !declaresHost(symbol.Library)) || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name); + if (((symbol.Section == 0 && !declaresHost(symbol.Library) && !systemImports[module].contains(index)) || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name); } if (!macos) BindNamedImportsByNid(image, executableExports); } diff --git a/core/relinker/relinker/tests/test_guest_symbol_names.py b/core/relinker/relinker/tests/test_guest_symbol_names.py index e41660d23701..9e9b593a8607 100644 --- a/core/relinker/relinker/tests/test_guest_symbol_names.py +++ b/core/relinker/relinker/tests/test_guest_symbol_names.py @@ -261,6 +261,21 @@ def main(): }).parent / "app0" / "sce_module" importer = module_symbols((guests / "c.prx.guest.prx").read_bytes()) assert "shared#guest" in importer, importer + + for exported, expected, unexpected in (("entry", "entry#guest", "entry"), ("other", "entry", "entry#guest")): + guests = relink(relinker, work / f"guest-declared-missing-{exported}", importing_executable([]), { + "provider.prx": guest_module([(exported, True)]), + "third.prx": guest_module([("entry", True)]), + "consumer.prx": needed_module_import("ZZZZZZZZZZZ", "entry#A#B", "provider.prx", (0x61000045,)), + }).parent / "app0" / "sce_module" + importer = module_symbols((guests / "consumer.prx.guest.prx").read_bytes()) + assert expected in importer and unexpected not in importer, (exported, importer) + output = relink(relinker, work / f"executable-declared-missing-{exported}", importing_executable(["entry#B#B"], ["provider"]), { + "provider.prx": guest_module([(exported, True)]), + "third.prx": guest_module([("entry", True)]), + }) + imports = imported_symbols(output.read_bytes()) + assert imports == {expected}, (exported, imports) print("Guest symbol name tests passed")