Merge pull request #1830 from tv7/fix/relinker-needed-debug-prx

fix(relinker): match needed module names that differ only in the extension
This commit is contained in:
Dean 2026-10-10 17:41:41 +13:00 committed by GitHub
commit fdd2d26077
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 94 additions and 10 deletions

View File

@ -8,7 +8,12 @@
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace Relinker {
@ -29,6 +34,19 @@ std::vector<std::string> ReadNeededNames(const Domain::SysVDynamicSection& dynam
return names;
}
std::string FoldFilename(std::string name) {
for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
return name;
}
std::string ModuleStem(std::string name, const bool windows) {
if (windows) name = FoldFilename(std::move(name));
for (const std::string_view suffix : {".debug_prx", ".sprx", ".prx"}) {
if (name.size() > suffix.size() && name.ends_with(suffix)) return name.substr(0, name.size() - suffix.size());
}
return {};
}
}
std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool macos, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set<std::string>& excludedModules) const {
@ -68,22 +86,45 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
}
const auto neededNames = ReadNeededNames(dynamic);
std::set<std::string> missingNeeded;
std::map<std::string, std::filesystem::path> neededAliases;
for (const auto& name : neededNames) {
if (excludedModules.contains(name)) continue;
if (std::none_of(paths.begin(), paths.end(), [&](const auto& path) { return path.filename().string() == name; })) missingNeeded.insert(name);
if (std::any_of(paths.begin(), paths.end(), [&](const auto& path) { return path.filename().string() == name || (windows && FoldFilename(path.filename().string()) == FoldFilename(name)); })) continue;
const auto stem = ModuleStem(name, windows);
const auto alias = stem.empty() ? paths.end() : std::find_if(paths.begin(), paths.end(), [&](const auto& path) { return ModuleStem(path.filename().string(), windows) == stem; });
if (alias != paths.end()) neededAliases.emplace(name, *alias);
else missingNeeded.insert(name);
}
if (!missingNeeded.empty()) {
std::map<std::string, std::filesystem::path> found;
std::map<std::string, std::filesystem::path> foundByStem;
const auto record = [](std::map<std::string, std::filesystem::path>& matches, const std::string& name, const std::filesystem::path& path) {
if (!matches.emplace(name, path).second) throw Domain::RelinkerException("Ambiguous needed module: " + matches.at(name).string() + " and " + path.string());
};
for (auto it = std::filesystem::recursive_directory_iterator(root); it != std::filesystem::recursive_directory_iterator(); ++it) {
if (it->is_directory() && std::find(directories.begin(), directories.end(), it->path()) != directories.end()) {
it.disable_recursion_pending();
continue;
}
if (!it->is_regular_file()) continue;
const auto name = it->path().filename().string();
if (!it->is_regular_file() || !missingNeeded.contains(name) || !isElf(it->path())) continue;
if (!found.emplace(name, it->path()).second) throw Domain::RelinkerException("Ambiguous needed module: " + found.at(name).string() + " and " + it->path().string());
const auto stem = ModuleStem(name, windows);
std::vector<std::string> stemMatches;
if (!stem.empty()) {
for (const auto& needed : missingNeeded) if (needed != name && ModuleStem(needed, windows) == stem) stemMatches.push_back(needed);
}
if ((!missingNeeded.contains(name) && stemMatches.empty()) || !isElf(it->path())) continue;
if (missingNeeded.contains(name)) record(found, name, it->path());
for (const auto& needed : stemMatches) record(foundByStem, needed, it->path());
}
for (const auto& name : missingNeeded) {
const auto exact = found.find(name);
const auto byStem = foundByStem.find(name);
if (exact == found.end() && byStem == foundByStem.end()) continue;
const auto& path = exact != found.end() ? exact->second : byStem->second;
if (exact == found.end()) neededAliases.emplace(name, path);
if (std::find(paths.begin(), paths.end(), path) == paths.end()) paths.push_back(path);
}
for (const auto& [name, path] : found) paths.push_back(path);
}
Io::FileReader reader;
std::map<std::filesystem::path, GuestImage> discovered;
@ -177,12 +218,20 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
const auto [found, inserted] = guestNames.emplace(name, index);
if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name);
}
if (windows) windowsGuestFiles.emplace(foldFilename(images[index].SourcePath.filename().string()), index);
if (windows) windowsGuestFiles.emplace(FoldFilename(images[index].SourcePath.filename().string()), index);
}
for (const auto& [name, path] : neededAliases) {
if (guestNames.contains(name)) continue;
const auto image = std::find_if(images.begin(), images.end(), [&](const auto& candidate) { return candidate.SourcePath == path; });
if (image == images.end()) throw Domain::RelinkerException("Needed module alias has no guest image: " + name);
const auto index = static_cast<std::size_t>(image - images.begin());
const auto [found, inserted] = guestNames.emplace(name, index);
if (!inserted && found->second != index) throw Domain::RelinkerException("Ambiguous guest dependency name: " + name);
}
const auto findGuest = [&](const std::string& name) {
const auto exact = guestNames.find(name);
if (exact != guestNames.end() || !windows) return exact;
const auto file = windowsGuestFiles.find(foldFilename(name));
const auto file = windowsGuestFiles.find(FoldFilename(name));
return file == windowsGuestFiles.end() ? guestNames.end() : guestNames.emplace(name, file->second).first;
};
const auto resolveIdentity = [&](const std::string& name) {

View File

@ -12,9 +12,9 @@ from test_guest_module_directories import module_with_symbol, needed_libraries
NEEDED = b"needed.prx"
def executable_with_needed():
def executable_with_needed(needed=NEEDED):
image = main_fixture()
strings = b"\0" + NEEDED + b"\0"
strings = b"\0" + needed + b"\0"
image[0x4800:0x4800 + len(strings)] = strings
tags = []
for position in range(0x4600, 0x4600 + 0x200, 16):
@ -35,10 +35,10 @@ def main():
with tempfile.TemporaryDirectory(prefix="anyps5-needed-modules-") as directory:
work = Path(directory)
def convert(case, windows):
def convert(case, windows, needed=NEEDED):
(case / "sce_module").mkdir(parents=True, exist_ok=True)
source = case / "input.elf"
source.write_bytes(executable_with_needed())
source.write_bytes(executable_with_needed(needed))
output = case / ("output.exe" if windows else "output.elf")
result = subprocess.run([str(relinker), *(["--windows"] if windows else []), str(source), str(output)],
capture_output=True, text=True, timeout=30)
@ -63,6 +63,41 @@ def main():
needed = needed_libraries(output.read_bytes())
assert needed == ["$ORIGIN/app0/Media/Modules/needed.prx.guest.prx"], needed
for directory in ("Media/Modules", "sce_module"):
case = work / f"{windows}-debug-name-{directory.replace('/', '-')}"
(case / directory).mkdir(parents=True)
(case / directory / "needed.prx").write_bytes(module_with_symbol(True))
result, output = convert(case, windows, b"needed.debug_prx")
assert result.returncode == 0, (result.stdout, result.stderr)
artifact = case / "app0" / directory / "needed.prx.guest.prx"
assert list((case / "app0").rglob("*.guest.prx")) == [artifact], list((case / "app0").rglob("*"))
if windows and os.name == "nt":
run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
assert run.returncode == 42, (run.returncode, run.stdout, run.stderr)
if not windows:
needed = needed_libraries(output.read_bytes())
assert needed == [f"$ORIGIN/app0/{directory}/needed.prx.guest.prx"], needed
if windows:
case = work / "debug-name-case"
(case / "prx").mkdir(parents=True)
(case / "prx" / "foo-bar.prx").write_bytes(module_with_symbol(True))
result, output = convert(case, windows, b"Foo-Bar.debug_prx")
assert result.returncode == 0, (result.stdout, result.stderr)
artifact = case / "app0" / "prx" / "foo-bar.prx.guest.prx"
assert list((case / "app0").rglob("*.guest.prx")) == [artifact], list((case / "app0").rglob("*"))
if os.name == "nt":
run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
assert run.returncode == 42, (run.returncode, run.stdout, run.stderr)
case = work / f"{windows}-debug-name-ambiguous"
(case / "first").mkdir(parents=True)
(case / "first" / "needed.prx").write_bytes(module_with_symbol(True))
(case / "first" / "needed.sprx").write_bytes(module_with_symbol(True))
result, output = convert(case, windows, b"needed.debug_prx")
assert result.returncode == 2 and "Ambiguous needed module" in result.stderr, result.stderr
assert not output.exists(), output
case = work / f"{windows}-ambiguous"
for name in ("first", "second"):
(case / name).mkdir(parents=True)