Merge pull request #1529 from johnmaia/fix/relinker-declared-host-imports

fix(relinker): keep Linux imports declared from host modules off guest exports
This commit is contained in:
Adrià 2026-10-11 00:25:46 +02:00 committed by GitHub
commit 496536cb9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 12 deletions

View File

@ -206,7 +206,17 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
Io::WriteU32(symbols, index * 24, static_cast<std::uint32_t>(strings.size()));
Io::AppendString(strings, name + GuestSymbolSuffix);
};
const auto declaresHost = [&](const std::string& library) { return !windows && !library.empty() && findGuest(library) == guestNames.end(); };
if (dynamic.DynSymData.size() % 24 != 0) throw Domain::RelinkerException("Invalid executable symbol table");
std::vector<std::string> executableModules(dynamic.DynSymData.size() / 24);
for (const auto* table : {&dynamic.RelaData, &dynamic.RelaPltData}) {
if (table->size() % 24 != 0) throw Domain::RelinkerException("Invalid executable relocation table");
for (std::size_t offset = 0; offset < table->size(); offset += 24) {
const auto symbol = Io::ReadU64(*table, offset + 8) >> 32;
const auto module = dynamic.ImportModules.find(Io::ReadU64(*table, offset));
if (symbol != 0 && symbol < executableModules.size() && module != dynamic.ImportModules.end()) executableModules[symbol] = module->second;
}
}
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);
@ -215,6 +225,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
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);
if (declaresHost(executableModules[offset / 24])) continue;
rejectSharedImport(name.substr(0, name.find('#')), Io::Utf8Path(inputPath));
if (!windows && exports.contains(name)) rename(dynamic.DynSymData, dynamic.DynStrData, offset / 24, name);
}
@ -232,7 +243,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
}
for (std::size_t symbolIndex = 0; symbolIndex < images[index].Symbols.size(); ++symbolIndex) {
const auto& symbol = images[index].Symbols[symbolIndex];
if (symbol.Section != 0 || symbol.Name.empty()) continue;
if (symbol.Section != 0 || symbol.Name.empty() || declaresHost(symbol.Library)) continue;
rejectSharedImport(symbol.Name, Io::Utf8Path(images[index].SourcePath));
const auto found = exports.find(symbol.Name);
std::vector<std::size_t> providers;
@ -257,7 +268,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
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);
if (((symbol.Section == 0 && !declaresHost(symbol.Library)) || exported) && exports.contains(symbol.Name)) rename(image.Dynamic.DynSymData, image.Dynamic.DynStrData, index, symbol.Name);
}
}
}

View File

@ -5,6 +5,7 @@ import sys
import tempfile
from test_guest_intel_trampolines import PLAIN_SITE, elf_loads, guest_fixture, main_fixture
from test_windows_import_modules import consumer, provider
DYNAMIC = 0x4800
STRINGS = 0x4A00
@ -36,15 +37,18 @@ def guest_module(symbols):
return image
def importing_executable(names):
def importing_executable(names, modules=()):
image = main_fixture()
strings, offsets = string_table(names)
needed = [module + ".prx" for module in modules]
strings, offsets = string_table([*names, *needed, *modules])
image[STRINGS:STRINGS + len(strings)] = strings
for index, offset in enumerate(offsets, 1):
for index, offset in enumerate(offsets[:len(names)], 1):
struct.pack_into("<IBBHQQ", image, SYMBOLS + index * 24, offset, 0x12, 0, 0, 0, 0)
struct.pack_into("<QQq", image, RELOCATIONS + (index - 1) * 24, GOT + (index - 1) * 8, (index << 32) | 6, 0)
tags = [(5, STRINGS), (10, len(strings)), (6, SYMBOLS), (11, 24),
(7, RELOCATIONS), (8, len(names) * 24), (9, 24), (0, 0)]
tags = [(1, offset) for offset in offsets[len(names):len(names) + len(needed)]]
tags += [(0x61000045, (index << 48) | offset) for index, offset in enumerate(offsets[len(names) + len(needed):], 1)]
tags += [(5, STRINGS), (10, len(strings)), (6, SYMBOLS), (11, 24),
(7, RELOCATIONS), (8, len(names) * 24), (9, 24), (0, 0)]
for index, tag in enumerate(tags):
struct.pack_into("<qQ", image, DYNAMIC + index * 16, *tag)
struct.pack_into("<QQQQQ", image, 120 + 8, DYNAMIC, DYNAMIC, DYNAMIC, len(tags) * 16, len(tags) * 16)
@ -120,19 +124,25 @@ def imported_symbols(image):
return names
def convert(relinker, case):
def relink(relinker, case, executable, modules):
(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)]))
source.write_bytes(executable)
for name, image in modules.items():
(case / "sce_module" / name).write_bytes(image)
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 convert(relinker, case):
return relink(relinker, case, importing_executable(["AAAAAAAAAAA#A#A", "DDDDDDDDDDD#D#D"]), {
"libc.prx": guest_module([("AAAAAAAAAAA#A#A", True), ("BBBBBBBBBBB#B#B", False), ("CCCCCCCCCCC#C#C", False), ("EEEEEEEEEEE", True)]),
"other.prx": guest_module([("BBBBBBBBBBB#B#B", True), ("EEEEEEEEEEE", True)]),
})
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-guest-symbol-names-") as directory:
@ -148,6 +158,21 @@ def main():
check_hash_table((modules / "other.prx.guest.prx").read_bytes())
imports = imported_symbols(output.read_bytes())
assert imports == {"AAAAAAAAAAA#guest", "DDDDDDDDDDD"}, imports
declared = relink(relinker, work / "declared",
importing_executable(["AAAAAAAAAAA#B#B", "EEEEEEEEEEE#C#C"], ["libc", "libSceLibcInternal"]),
{"libc.prx": guest_module([("AAAAAAAAAAA", True), ("EEEEEEEEEEE", True)])})
imports = imported_symbols(declared.read_bytes())
assert imports == {"AAAAAAAAAAA#guest", "EEEEEEEEEEE"}, imports
guests = relink(relinker, work / "guest-declared", importing_executable([]), {
"b.prx": provider(7),
"c.prx": consumer("libSceLibcInternal.prx", module_name="libSceLibcInternal"),
}).parent / "app0" / "sce_module"
exporter = module_symbols((guests / "b.prx.guest.prx").read_bytes())
assert exporter == ["shared#guest"], exporter
importer = module_symbols((guests / "c.prx.guest.prx").read_bytes())
assert "shared" in importer and "shared#guest" not in importer, importer
print("Guest symbol name tests passed")