Merge pull request #733 from alfalfa-star/fix/relinker-duplicate-guest-exports

fix(relinker): allow duplicate Linux guest exports that no module imports
This commit is contained in:
Dean 2026-10-06 10:34:07 +13:00 committed by GitHub
commit bc24783e5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 2 deletions

View File

@ -49,6 +49,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");
std::vector<GuestImage> images;
std::map<std::string, std::size_t> exports;
std::map<std::string, std::set<std::size_t>> sharedExports;
std::set<std::string> outputNames;
Io::FileReader reader;
for (const auto& path : paths) {
@ -65,7 +66,11 @@ std::vector<GuestArtifact> 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<Domain::ProgramHeader> 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<GuestArtifact> 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<std::set<std::size_t>> 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<GuestArtifact> 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];

View File

@ -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")