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
2 changes: 2 additions & 0 deletions core/relinker/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $<TARGET_FILE:relinker>)
add_test(NAME guest_absolute_symbols COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $<TARGET_FILE:relinker>)
add_test(NAME guest_module_identity COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_identity.py $<TARGET_FILE:relinker>)
add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_import_modules.py $<TARGET_FILE:relinker>)
add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE}
Expand Down
4 changes: 3 additions & 1 deletion core/relinker/domain/include/domain/ImportModule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Domain {

inline std::string ImportModuleName(std::string name) {
if (name.ends_with(".prx")) name.resize(name.size() - 4);
else if (name.ends_with(".sprx")) name.resize(name.size() - 5);
else if (name.ends_with(".suprx")) name.resize(name.size() - 6);
if (name.ends_with("-module")) name.resize(name.size() - 7);
std::replace(name.begin(), name.end(), '.', '_');
return name;
Expand All @@ -36,7 +38,7 @@ inline std::string ImportModule(const std::string& symbol, const std::map<std::u
auto name = module->second;
if (name.empty() || name.find_first_of("/\\:$\r\n") != std::string::npos)
throw RelinkerException("Invalid import module name: " + name);
if (!name.ends_with(".prx")) name += ".prx";
if (!name.ends_with(".prx") && !name.ends_with(".sprx") && !name.ends_with(".suprx")) name += ".prx";
if (std::find(dependencies.begin(), dependencies.end(), name) != dependencies.end()) return name;
std::string matched;
for (const auto& dependency : dependencies) {
Expand Down
2 changes: 2 additions & 0 deletions core/relinker/relinker/include/relinker/guest/GuestImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct GuestImage {
std::filesystem::path SourcePath;
std::string OutputName;
std::string Soname;
std::vector<std::string> ModuleNames;
std::vector<std::uint8_t> Bytes;
std::vector<Codegen::TrampolineSite> Trampolines;
std::vector<Domain::ProgramHeader> Headers;
Expand All @@ -44,6 +45,7 @@ struct GuestImage {

class GuestImageReader {
public:
std::vector<std::string> ReadModuleNames(const std::vector<std::uint8_t>& bytes) const;
GuestImage Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const;
};

Expand Down
85 changes: 85 additions & 0 deletions core/relinker/relinker/src/guest/GuestImageReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,82 @@

namespace Relinker {

std::vector<std::string> GuestImageReader::ReadModuleNames(const std::vector<std::uint8_t>& bytes) const {
if (bytes.size() < 64 || Io::ReadU32(bytes, 0) != 0x464c457f || bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1 || Io::ReadU16(bytes, 18) != 62)
throw Domain::RelinkerException("Expected little-endian ELF64 x86-64 module metadata");
const auto type = Io::ReadU16(bytes, 16);
if (type != 3 && type != 0xfe18) return {};
if (Io::ReadU16(bytes, 52) != 64 || Io::ReadU16(bytes, 54) != 56 || Io::ReadU16(bytes, 56) == 0)
throw Domain::RelinkerException("Invalid module metadata program headers");
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
if (offset > bytes.size() || size > bytes.size() - offset) throw Domain::RelinkerException("Module metadata exceeds file");
};
range(Io::ReadU64(bytes, 32), static_cast<std::uint64_t>(Io::ReadU16(bytes, 56)) * 56);
ElfReader reader(bytes);
const auto headers = reader.ReadProgramHeaders();
const Domain::ProgramHeader* dynamic = nullptr;
const Domain::ProgramHeader* dynlib = nullptr;
for (const auto& header : headers) {
if (header.Type == 2) {
if (dynamic) throw Domain::RelinkerException("Duplicate module metadata dynamic segment");
dynamic = &header;
}
if (header.Type == 0x61000000) {
if (dynlib) throw Domain::RelinkerException("Duplicate module metadata dynamic data");
dynlib = &header;
}
}
if (!dynamic) throw Domain::RelinkerException("Missing module metadata dynamic segment");
std::map<std::uint64_t, std::uint64_t> strings;
std::vector<std::uint64_t> modules;
for (const auto& tag : reader.ReadDynamicTags(*dynamic)) {
if (tag.Tag == 0x6100000d || tag.Tag == 0x61000043) modules.push_back(tag.Value);
else if (tag.Tag == 5 || tag.Tag == 10 || tag.Tag == 0x61000035 || tag.Tag == 0x61000037) {
if (!strings.emplace(tag.Tag, tag.Value).second) throw Domain::RelinkerException("Duplicate module metadata string tag");
}
}
if (modules.empty()) return {};
const auto get = [&](std::uint64_t standard, std::uint64_t sce) {
if (strings.contains(standard) == strings.contains(sce)) throw Domain::RelinkerException("Missing or ambiguous module metadata string tag");
return strings.at(strings.contains(standard) ? standard : sce);
};
const auto size = get(10, 0x61000037);
const auto address = get(5, 0x61000035);
std::uint64_t offset = 0;
if (strings.contains(5)) {
bool found = false;
for (const auto& header : headers) {
if (header.Type != 1 || address < header.MappedAddress || address - header.MappedAddress > header.FileSize || size > header.FileSize - (address - header.MappedAddress)) continue;
range(header.Offset, header.FileSize);
if (found) throw Domain::RelinkerException("Ambiguous module metadata string mapping");
offset = header.Offset + address - header.MappedAddress;
found = true;
}
if (!found) throw Domain::RelinkerException("Unmapped module metadata strings");
} else {
if (!dynlib || address > dynlib->FileSize || size > dynlib->FileSize - address) throw Domain::RelinkerException("Module metadata strings exceed dynamic data");
range(dynlib->Offset, dynlib->FileSize);
offset = dynlib->Offset + address;
}
range(offset, size);
if (size == 0 || bytes[offset] != 0) throw Domain::RelinkerException("Invalid module metadata strings");
std::vector<std::string> names;
std::set<std::uint64_t> ids;
for (const auto value : modules) {
const auto nameOffset = value & 0xffffffffu;
if (nameOffset >= size) throw Domain::RelinkerException("Module metadata string offset out of bounds");
const auto start = bytes.begin() + static_cast<std::ptrdiff_t>(offset + nameOffset);
const auto end = bytes.begin() + static_cast<std::ptrdiff_t>(offset + size);
const auto zero = std::find(start, end, 0);
if (zero == end) throw Domain::RelinkerException("Unterminated module metadata string");
const std::string name(start, zero);
if (name.empty() || name.find_first_of("/\\:$\r\n") != std::string::npos) throw Domain::RelinkerException("Invalid module metadata name");
if (!ids.insert(value >> 48).second) throw Domain::RelinkerException("Duplicate module metadata export ID");
names.push_back(name);
}
return names;
}

GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const {
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); };
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
Expand Down Expand Up @@ -63,13 +139,15 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
std::map<std::uint64_t, std::uint64_t> tags;
std::vector<std::uint64_t> needed;
std::vector<std::uint64_t> moduleImports;
std::vector<std::uint64_t> moduleExports;
bool terminated = false;
for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) {
const auto tag = Io::ReadU64(bytes, offset);
const auto value = Io::ReadU64(bytes, offset + 8);
if (tag == 0) { terminated = true; break; }
if (tag == 1) needed.push_back(value);
else if (tag == 0x61000045) moduleImports.push_back(value);
else if (tag == 0x6100000d || tag == 0x61000043) moduleExports.push_back(value);
else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) {
if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag));
}
Expand Down Expand Up @@ -159,6 +237,13 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
image.Soname = string(tags.at(14));
if (image.Soname.empty() || image.Soname.find_first_of("\\:$\r\n") != std::string::npos) fail("Invalid guest SONAME: " + image.Soname);
}
std::set<std::uint64_t> moduleIds;
for (const auto value : moduleExports) {
const auto name = string(value & 0xffffffffu);
if (name.empty() || name.find_first_of("/\\:$\r\n") != std::string::npos) fail("Invalid guest module name: " + name);
if (!moduleIds.insert(value >> 48).second) fail("Duplicate export module ID");
image.ModuleNames.push_back(name);
}
std::set<std::string> dependencies;
for (const auto offset : needed) {
const auto name = string(offset);
Expand Down
64 changes: 58 additions & 6 deletions core/relinker/relinker/src/guest/GuestModuleBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,60 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
}
for (const auto& [name, path] : found) paths.push_back(path);
}
Io::FileReader reader;
std::map<std::filesystem::path, GuestImage> discovered;
const auto matchesIdentity = [](const std::string& name, const std::vector<std::string>& identities) {
return std::any_of(identities.begin(), identities.end(), [&](const auto& identity) {
return name == identity || name == identity + ".prx" || name == identity + ".sprx" || name == identity + ".suprx";
});
};
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 (const auto& path : paths) discovered.emplace(path, GuestImageReader().Read(path, reader.Read(path.string())));
std::set<std::string> unresolved;
for (const auto& name : missingNeeded) {
if (std::none_of(discovered.begin(), discovered.end(), [&](const auto& entry) {
const auto& [path, image] = entry;
return path.filename().string() == name || image.Soname == name ||
(windows && foldFilename(path.filename().string()) == foldFilename(name)) || matchesIdentity(name, image.ModuleNames);
})) unresolved.insert(name);
}
if (!unresolved.empty()) {
std::map<std::string, std::filesystem::path> identities;
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;
}
const auto& path = it->path();
const auto extension = path.extension().string();
if (!it->is_regular_file() || (extension != ".prx" && extension != ".sprx" && extension != ".suprx") ||
path.filename().string().ends_with(GuestModuleSuffix) || excludedModules.contains(path.filename().string()) || discovered.contains(path) || !isElf(path)) continue;
const auto names = GuestImageReader().ReadModuleNames(reader.Read(path.string()));
for (const auto& name : unresolved) {
if (!matchesIdentity(name, names)) continue;
if (!identities.emplace(name, path).second) throw Domain::RelinkerException("Ambiguous needed module identity: " + name);
}
}
for (const auto& [name, path] : identities) {
if (discovered.contains(path)) continue;
discovered.emplace(path, GuestImageReader().Read(path, reader.Read(path.string())));
paths.push_back(path);
}
}
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
std::sort(paths.begin(), paths.end());
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
if (paths.empty()) return {};
if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible");
std::vector<GuestImage> images;
std::map<std::string, std::vector<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) {
auto image = GuestImageReader().Read(path, reader.Read(path.string()));
auto image = std::move(discovered.at(path));
if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName);
std::string folded = image.OutputName;
if (windows) {
Expand Down Expand Up @@ -128,10 +171,6 @@ 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;
Expand All @@ -146,6 +185,19 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
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) {
if (findGuest(name) != guestNames.end()) return;
std::size_t match = images.size();
for (std::size_t index = 0; index < images.size(); ++index) {
const auto& identities = images[index].ModuleNames;
if (!matchesIdentity(name, identities)) continue;
if (match != images.size()) throw Domain::RelinkerException("Ambiguous guest module identity: " + name);
match = index;
}
if (match != images.size()) guestNames.emplace(name, match);
};
for (const auto& name : missingNeeded) resolveIdentity(name);
for (const auto& image : images) for (const auto& name : image.Dependencies) resolveIdentity(name);
const auto rejectSharedImport = [&](const std::string& name, const std::string& importer) {
const auto shared = sharedExports.find(name);
if (shared == sharedExports.end()) return;
Expand Down
Loading
Loading