diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index eebb504ca..cef7ff2cf 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -49,6 +49,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible"); std::vector images; std::map exports; + std::map> sharedExports; std::set outputNames; Io::FileReader reader; for (const auto& path : paths) { @@ -65,7 +66,11 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path for (const auto& symbol : image.Symbols) { if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue; const auto [existing, inserted] = exports.emplace(symbol.Name, images.size()); - if (!inserted) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + images.at(existing->second).SourcePath.string() + " and " + path.string()); + if (inserted) continue; + if (windows || existing->second == images.size()) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + images.at(existing->second).SourcePath.string() + " and " + path.string()); + auto& providers = sharedExports[symbol.Name]; + providers.insert(existing->second); + providers.insert(images.size()); } std::vector codeHeaders; for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header); @@ -88,6 +93,24 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name); } } + const auto rejectSharedImport = [&](const std::string& name, const std::string& importer) { + const auto shared = sharedExports.find(name); + if (shared == sharedExports.end()) return; + std::string providers; + for (const auto provider : shared->second) providers += " " + images[provider].SourcePath.string(); + throw Domain::RelinkerException("Ambiguous guest import " + name + " in " + importer + ": exported by" + providers); + }; + 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; + const auto nameOffset = Io::ReadU32(dynamic.DynSymData, offset); + if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid executable symbol name offset"); + const auto start = dynamic.DynStrData.begin() + nameOffset; + const auto end = std::find(start, dynamic.DynStrData.end(), 0); + 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()); + } std::vector> dependencies(images.size()); for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs"); for (std::size_t index = 0; index < images.size(); ++index) { @@ -97,6 +120,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } for (const auto& symbol : images[index].Symbols) { if (symbol.Section != 0 || symbol.Name.empty()) continue; + rejectSharedImport(symbol.Name, images[index].SourcePath.string()); const auto found = exports.find(symbol.Name); if (found != exports.end()) { const auto& provider = images[found->second]; diff --git a/core/relinker/relinker/tests/test_guest_module_directories.py b/core/relinker/relinker/tests/test_guest_module_directories.py index 5d0389cb2..992f89abf 100644 --- a/core/relinker/relinker/tests/test_guest_module_directories.py +++ b/core/relinker/relinker/tests/test_guest_module_directories.py @@ -122,7 +122,21 @@ def main(): (case / name).mkdir(parents=True) (case / name / f"{name}.prx").write_bytes(module_with_symbol(True)) result, output = convert(case, windows) - assert result.returncode == 2 and "Duplicate guest export" in result.stderr, result.stderr + if windows: + assert result.returncode == 2 and "Duplicate guest export" in result.stderr, result.stderr + assert not output.exists() and not (case / "app0").exists(), output + else: + assert result.returncode == 0 and output.exists(), result.stderr + assert {path.name for path in (case / "app0").rglob("*.guest.prx")} == {"sce_module.prx.guest.prx", "prx.prx.guest.prx"} + + case = work / f"{windows}-ambiguous-import" + for name in ("sce_module", "prx"): + (case / name).mkdir(parents=True) + (case / name / f"{name}.prx").write_bytes(module_with_symbol(True)) + (case / "prx" / "consumer.prx").write_bytes(module_with_symbol(False)) + result, output = convert(case, windows) + expected = "Duplicate guest export" if windows else "Ambiguous guest import shared" + assert result.returncode == 2 and expected in result.stderr, result.stderr assert not output.exists() and not (case / "app0").exists(), output print("Guest module directory integration tests passed")