Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions core/relinker/relinker/src/guest/GuestModuleBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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 toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set<std::string>& excludedModules) const {
Expand Down Expand Up @@ -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);
}
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
std::sort(paths.begin(), paths.end());
Expand Down Expand Up @@ -128,22 +169,26 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
}
std::map<std::string, std::size_t> guestNames;
std::map<std::string, std::size_t> windowsGuestFiles;
const auto foldFilename = [](std::string name) {
for (auto& character : name) if (character >= 'A' && character <= 'Z') character = static_cast<char>(character + ('a' - 'A'));
return name;
};
for (std::size_t index = 0; index < images.size(); ++index) {
for (const auto& name : {images[index].SourcePath.filename().string(), images[index].Soname}) {
if (name.empty()) continue;
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 rejectSharedImport = [&](const std::string& name, const std::string& importer) {
Expand Down
43 changes: 39 additions & 4 deletions core/relinker/relinker/tests/test_guest_needed_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
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):
Expand All @@ -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)
Expand All @@ -63,6 +63,41 @@ def convert(case, windows):
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)
Expand Down
Loading