fix(relinker): discover required guest module identities
This commit is contained in:
parent
d70b899894
commit
98c47df9e5
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -63,6 +139,7 @@ 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);
|
||||
|
|
@ -70,6 +147,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_windows_import_modules import consumer, dynamic, executable, provider
|
||||
from test_guest_module_directories import needed_libraries
|
||||
|
||||
|
||||
def declared_provider(value, identities, soname=None, tag=0x61000043, symbol='shared'):
|
||||
image = provider(value)
|
||||
strings = b'\0' + symbol.encode() + b'#A#B\0'
|
||||
tags = [(5, 0x2200), (10, 0), (6, 0x2280), (11, 24),
|
||||
(4, 0x2240), (7, 0x2300), (8, 0), (9, 24)]
|
||||
if soname:
|
||||
tags.append((14, len(strings)))
|
||||
strings += soname.encode() + b'\0'
|
||||
for index, identity in enumerate(identities, 1):
|
||||
tags.append((tag, (index << 48) | (0x101 << 32) | len(strings)))
|
||||
strings += identity.encode() + b'\0'
|
||||
assert len(strings) <= 0x40
|
||||
tags[1] = (10, len(strings))
|
||||
image[0x800:0x800 + len(strings)] = strings
|
||||
dynamic(image, 0x600, 176, tags)
|
||||
return image
|
||||
|
||||
|
||||
def sce_strings(image):
|
||||
struct.pack_into('<H', image, 56, 4)
|
||||
struct.pack_into('<IIQQQQQQ', image, 232, 0x61000000, 4, 0x800, 0, 0, 0x40, 0x40, 8)
|
||||
struct.pack_into('<qQ', image, 0x600, 0x61000035, 0)
|
||||
struct.pack_into('<q', image, 0x610, 0x61000037)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix='anyps5-module-identity-') as directory:
|
||||
work = Path(directory)
|
||||
|
||||
def convert(label, windows, request, module_name='libGuest', providers=None, guest_request=None, symbol='shared#A#B', module_directory='sce_module', extra=None):
|
||||
case = work / label
|
||||
modules = case / module_directory
|
||||
modules.mkdir(parents=True)
|
||||
(case / 'sce_module').mkdir(exist_ok=True)
|
||||
for filename, image in (providers or {'renamed.prx': declared_provider(22, ('libGuest',))}).items():
|
||||
(modules / filename).write_bytes(image)
|
||||
for filename, image in (extra or {}).items():
|
||||
target = case / filename
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(image)
|
||||
if guest_request:
|
||||
(case / 'sce_module/consumer.prx').write_bytes(consumer(guest_request, symbol=symbol, module_name=module_name))
|
||||
source = case / 'input.elf'
|
||||
source.write_bytes(executable(request, symbol=symbol, module_name=module_name))
|
||||
output = case / ('output.exe' if windows else 'output.elf')
|
||||
result = subprocess.run([str(relinker), *(['--windows'] if windows else []),
|
||||
'--rpath', '$ORIGIN/custom-hosts', str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
return result, output
|
||||
|
||||
def run(output, expected=22, error=None):
|
||||
if os.name != 'nt':
|
||||
return
|
||||
result = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
|
||||
if error:
|
||||
assert result.returncode != 0 and error in result.stderr, (result.returncode, result.stdout, result.stderr)
|
||||
else:
|
||||
assert result.returncode == expected, (result.returncode, result.stdout, result.stderr)
|
||||
|
||||
for windows in (True, False):
|
||||
for directory in ('sce_module', '.', 'Media/Modules'):
|
||||
result, output = convert(f'sprx-request-{windows}-{directory.replace("/", "-")}', windows,
|
||||
'libGuest.sprx', module_directory=directory, guest_request='libGuest.sprx',
|
||||
providers={'renamed.sprx': declared_provider(22, ('libGuest',))})
|
||||
assert result.returncode == 0, result.stderr
|
||||
artifact = output.parent / 'app0' / directory / 'renamed.sprx.guest.prx'
|
||||
assert artifact.is_file(), list((output.parent / 'app0').rglob('*'))
|
||||
assert len(list((output.parent / 'app0').rglob('*.guest.prx'))) == 2
|
||||
if windows:
|
||||
run(output)
|
||||
else:
|
||||
assert needed_libraries(output.read_bytes()) == [
|
||||
'$ORIGIN/app0/' + directory + '/renamed.sprx.guest.prx',
|
||||
'$ORIGIN/app0/sce_module/consumer.prx.guest.prx']
|
||||
|
||||
for directory in ('.', 'Media/Modules'):
|
||||
for suffix in ('.prx', '.sprx', '.suprx'):
|
||||
relative = Path(directory) / ('renamed' + suffix)
|
||||
result, output = convert(f'outside-{windows}-{directory.replace("/", "-")}-{suffix}', windows,
|
||||
'libGuest.suprx', module_directory=directory, guest_request='libGuest.suprx',
|
||||
providers={relative.name: declared_provider(22, ('libGuest',))},
|
||||
extra={'unused.prx': declared_provider(77, ('libUnused',)), 'opaque.sprx': b'not an ELF'})
|
||||
assert result.returncode == 0, result.stderr
|
||||
artifact = output.parent / 'app0' / (relative.as_posix() + '.guest.prx')
|
||||
assert artifact.is_file(), list((output.parent / 'app0').rglob('*'))
|
||||
assert len(list((output.parent / 'app0').rglob('*.guest.prx'))) == 2
|
||||
if windows:
|
||||
run(output)
|
||||
else:
|
||||
assert needed_libraries(output.read_bytes()) == [
|
||||
'$ORIGIN/app0/' + directory + '/renamed' + suffix + '.guest.prx',
|
||||
'$ORIGIN/app0/sce_module/consumer.prx.guest.prx']
|
||||
|
||||
result, output = convert(f'outside-ambiguous-{windows}', windows, 'libGuest.suprx', module_directory='.',
|
||||
providers={'one.prx': declared_provider(22, ('libGuest',))},
|
||||
extra={'other/two.prx': declared_provider(33, ('libGuest',))})
|
||||
assert result.returncode == 2 and 'Ambiguous needed module identity' in result.stderr, result.stderr
|
||||
assert not output.exists()
|
||||
|
||||
result, output = convert(f'outside-sce-strings-{windows}', windows, 'libGuest.suprx', module_directory='.',
|
||||
providers={'renamed.prx': sce_strings(declared_provider(22, ('libGuest',)))})
|
||||
assert result.returncode == 0, result.stderr
|
||||
if windows:
|
||||
run(output)
|
||||
|
||||
for soname in (False, True):
|
||||
filename = 'physical.prx' if soname else 'libGuest.suprx'
|
||||
result, output = convert(f'known-before-outside-{windows}-{soname}', windows, 'libGuest.suprx',
|
||||
providers={filename: declared_provider(22, ('unrelated',), 'libGuest.suprx' if soname else None)},
|
||||
extra={'renamed.prx': declared_provider(11, ('libGuest',))})
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert len(list((output.parent / 'app0').rglob('*.guest.prx'))) == 1
|
||||
if windows:
|
||||
run(output)
|
||||
|
||||
malformed = declared_provider(22, ('libGuest',))
|
||||
struct.pack_into('<Q', malformed, 0x600 + 8 * 16 + 8, (1 << 48) | 0xffffffff)
|
||||
result, output = convert(f'outside-bad-name-{windows}', windows, 'libGuest.suprx',
|
||||
module_directory='.', providers={'renamed.prx': malformed})
|
||||
assert result.returncode == 2 and 'Module metadata string offset out of bounds' in result.stderr, result.stderr
|
||||
assert not output.exists()
|
||||
|
||||
malformed = sce_strings(declared_provider(22, ('libGuest',)))
|
||||
struct.pack_into('<Q', malformed, 0x618, 0x100)
|
||||
result, output = convert(f'outside-bad-sce-range-{windows}', windows, 'libGuest.suprx',
|
||||
module_directory='.', providers={'renamed.prx': malformed})
|
||||
assert result.returncode == 2 and 'Module metadata strings exceed dynamic data' in result.stderr, result.stderr
|
||||
assert not output.exists()
|
||||
|
||||
unrelated = declared_provider(77, ('libUnused',))
|
||||
struct.pack_into('<qQ', unrelated, 0x600 + 5 * 16, 0x70000000, 0)
|
||||
result, output = convert(f'outside-unrelated-unsupported-body-{windows}', windows, 'libGuest.suprx',
|
||||
module_directory='.', extra={'unused.prx': unrelated})
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert len(list((output.parent / 'app0').rglob('*.guest.prx'))) == 1
|
||||
if windows:
|
||||
run(output)
|
||||
|
||||
for windows in (True, False):
|
||||
for tag in (0x6100000d, 0x61000043):
|
||||
for suffix in ('.suprx', '.prx', '.sprx'):
|
||||
request = 'libGuest' + suffix
|
||||
result, output = convert(f'{windows}-{tag}-{suffix}', windows, request,
|
||||
providers={'renamed.prx': declared_provider(22, ('libGuest',), tag=tag)}, guest_request=request)
|
||||
assert result.returncode == 0, result.stderr
|
||||
if windows:
|
||||
run(output)
|
||||
else:
|
||||
needed = needed_libraries(output.read_bytes())
|
||||
assert needed == ['$ORIGIN/app0/sce_module/renamed.prx.guest.prx',
|
||||
'$ORIGIN/app0/sce_module/consumer.prx.guest.prx'], needed
|
||||
child = output.parent / 'app0/sce_module/consumer.prx.guest.prx'
|
||||
assert needed_libraries(child.read_bytes()) == ['$ORIGIN/renamed.prx.guest.prx']
|
||||
|
||||
providers = {'one.prx': declared_provider(22, ('libGuest',)),
|
||||
'two.prx': declared_provider(33, ('libGuest',), symbol='other')}
|
||||
result, output = convert(f'{windows}-ambiguous', windows, 'libGuest.suprx', providers=providers)
|
||||
assert result.returncode == 2 and 'Ambiguous guest module identity' in result.stderr, result.stderr
|
||||
assert not output.exists()
|
||||
|
||||
for request in ('LIBGUEST.suprx', 'libGuest.xyz', 'unavailable.suprx'):
|
||||
result, output = convert(f'{windows}-{request}', windows, request, module_name=request.rsplit('.', 1)[0])
|
||||
assert result.returncode == 0, result.stderr
|
||||
if windows:
|
||||
run(output, error='Failed to load module:')
|
||||
else:
|
||||
assert request in needed_libraries(output.read_bytes())
|
||||
|
||||
providers = {'wrong.prx': declared_provider(11, ('other',)),
|
||||
'right.prx': declared_provider(22, ('libGuest',))}
|
||||
result, output = convert('windows-owner', True, 'libGuest.suprx', providers=providers, guest_request='libGuest.suprx')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output)
|
||||
|
||||
providers = {'LIBGUEST.suprx': declared_provider(22, ('unrelated',)),
|
||||
'renamed.prx': declared_provider(11, ('libGuest',))}
|
||||
result, output = convert('windows-physical-before-identity', True, 'libGuest.suprx',
|
||||
providers=providers, guest_request='libGuest.suprx')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output)
|
||||
|
||||
providers = {'LIBGUEST.suprx': declared_provider(11, ('unrelated',)),
|
||||
'renamed.prx': declared_provider(22, ('libOther',), soname='libGuest.suprx')}
|
||||
result, output = convert('windows-soname-before-folded-file', True, 'libGuest.suprx',
|
||||
providers=providers, guest_request='libGuest.suprx')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output)
|
||||
|
||||
for exact_soname in (False, True):
|
||||
providers = {'other.prx' if exact_soname else 'libGuest.suprx':
|
||||
declared_provider(22, ('unrelated',), 'libGuest.suprx' if exact_soname else None),
|
||||
'alias.prx': declared_provider(11, ('libGuest',))}
|
||||
result, output = convert(f'exact-{exact_soname}', True, 'libGuest.suprx', providers=providers, guest_request='libGuest.suprx')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output)
|
||||
|
||||
result, output = convert('missing-symbol', True, 'libGuest.suprx', symbol='absent#A#B')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output, error='unresolved ELF import absent')
|
||||
providers = {'wrong.prx': declared_provider(11, ('other',)),
|
||||
'right.prx': declared_provider(22, ('libGuest',), symbol='other')}
|
||||
result, output = convert('wrong-provider', True, 'libGuest.suprx', providers=providers)
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output, error='unresolved ELF import shared')
|
||||
result, output = convert('multiple-identities', True, 'libGuest.suprx',
|
||||
providers={'renamed.prx': declared_provider(22, ('custom', 'libGuest'))}, guest_request='libGuest.suprx')
|
||||
assert result.returncode == 0, result.stderr
|
||||
run(output)
|
||||
print('Guest module identity dependency tests passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue