mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
Merge pull request #10495 from wail-arch/fix/linux-guest-zero-address-exports
fix(relinker): bind guest function exports at address 0 on Linux
This commit is contained in:
4 files changed
+137
No files matched your search
@@ -807,6 +807,12 @@ if(BUILD_TESTING)
|
||||
${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 linux_guest_zero_address_exports COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_zero_address_exports.py
|
||||
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_module_args_host>)
|
||||
set_property(TEST linux_guest_zero_address_exports APPEND PROPERTY LABELS relinker)
|
||||
set_tests_properties(linux_guest_zero_address_exports PROPERTIES TIMEOUT 30)
|
||||
add_asan_test_options(linux_guest_zero_address_exports verify_asan_link_order=0)
|
||||
add_test(NAME guest_unused_import_dependencies COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_unused_import_dependencies.py
|
||||
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_module_args_host>)
|
||||
|
||||
@@ -64,6 +64,17 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
|
||||
needsTlsResolver = true;
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
|
||||
const auto& symbol = image.Symbols[index];
|
||||
if (symbol.Value != 0 || symbol.Section == 0 || symbol.Section >= 0xff00 || (symbol.Info >> 4) == 0 ||
|
||||
(symbol.Info & 15) != 2 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
|
||||
const auto thunk = address();
|
||||
if (thunk > static_cast<std::uint64_t>(std::numeric_limits<std::int32_t>::max()) - 5)
|
||||
throw Domain::RelinkerException("Guest export at address 0 exceeds relative branch range");
|
||||
bytes.push_back(0xe9);
|
||||
Io::AppendU32(bytes, static_cast<std::uint32_t>(-static_cast<std::int32_t>(thunk + 5)));
|
||||
Io::WriteU64(symbols, index * 24 + 8, thunk);
|
||||
}
|
||||
std::vector<std::uint64_t> needed;
|
||||
for (const auto& dependency : dependencies) needed.push_back(addString(dependency));
|
||||
if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2"));
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_guest_deferred_dependencies import page_aligned
|
||||
from test_guest_export_lookup import guest_symbols
|
||||
from test_guest_intel_trampolines import main_fixture
|
||||
from test_guest_symbol_names import dynamic_tags, file_offset, symbol_name
|
||||
from test_windows_import_modules import consumer, provider
|
||||
|
||||
|
||||
def at_address_zero(image):
|
||||
struct.pack_into("<Q", image, 0x898 + 8, 0)
|
||||
image = page_aligned(image)
|
||||
struct.pack_into("<Q", image, 24, 0)
|
||||
struct.pack_into("<QQ", image, 64 + 16, 0, 0)
|
||||
return image
|
||||
|
||||
|
||||
def zero_address_module(symbols):
|
||||
image = guest_symbols([(name, info, other, 1) for name, info, other in symbols])
|
||||
position = 0x600
|
||||
while True:
|
||||
tag, value = struct.unpack_from("<qQ", image, position)
|
||||
if tag == 0:
|
||||
break
|
||||
if tag == 6:
|
||||
table = value - 0x1a00
|
||||
position += 16
|
||||
for index in range(1, len(symbols) + 1):
|
||||
struct.pack_into("<Q", image, table + index * 24 + 8, 0)
|
||||
image = page_aligned(image)
|
||||
struct.pack_into("<Q", image, 24, 0)
|
||||
struct.pack_into("<QQ", image, 64 + 16, 0, 0)
|
||||
return image
|
||||
|
||||
|
||||
def jump_target(image, value):
|
||||
offset = file_offset(image, value)
|
||||
assert image[offset] == 0xe9, image[offset:offset + 5].hex()
|
||||
displacement, = struct.unpack_from("<i", image, offset + 1)
|
||||
return value + 5 + displacement
|
||||
|
||||
|
||||
def exports(image):
|
||||
tags = dynamic_tags(image)
|
||||
count, = struct.unpack_from("<I", image, file_offset(image, tags[4]) + 4)
|
||||
table = file_offset(image, tags[6])
|
||||
result = {}
|
||||
for index in range(1, count):
|
||||
_, info, _, section, value, _ = struct.unpack_from("<IBBHQQ", image, table + index * 24)
|
||||
if section:
|
||||
result[symbol_name(image, tags, index)] = (info, value)
|
||||
return result
|
||||
|
||||
|
||||
def relink(relinker, case, modules):
|
||||
(case / "sce_module").mkdir(parents=True)
|
||||
for name, image in modules.items():
|
||||
(case / "sce_module" / name).write_bytes(image)
|
||||
image = main_fixture()
|
||||
code = bytes.fromhex("b83c000000bf2a0000000f05")
|
||||
image[0x4000:0x4000 + len(code)] = code
|
||||
source = case / "input.elf"
|
||||
source.write_bytes(image)
|
||||
output = case / "output.elf"
|
||||
result = subprocess.run([str(relinker), "--skip-syscall-check", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
host = Path(sys.argv[2]).resolve()
|
||||
runnable = sys.platform.startswith("linux") and platform.machine() in ("x86_64", "AMD64")
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-linux-guest-zero-address-exports-") as directory:
|
||||
work = Path(directory)
|
||||
|
||||
output = relink(relinker, work / "function", {
|
||||
"provider.prx": at_address_zero(provider(22)),
|
||||
"consumer.prx": page_aligned(consumer("provider.prx")),
|
||||
})
|
||||
image = (output.parent / "app0" / "sce_module" / "provider.prx.guest.prx").read_bytes()
|
||||
info, value = exports(image)["shared#guest"]
|
||||
assert info == 0x12 and value != 0, (info, value)
|
||||
assert jump_target(image, value) == 0, value
|
||||
|
||||
output = relink(relinker, work / "kinds", {"provider.prx": zero_address_module([
|
||||
("global", 0x12, 0), ("weak", 0x22, 0), ("protected", 0x12, 3), ("alias", 0x12, 0),
|
||||
("internal", 0x12, 1), ("hidden", 0x12, 2), ("object", 0x11, 0)])})
|
||||
image = (output.parent / "app0" / "sce_module" / "provider.prx.guest.prx").read_bytes()
|
||||
symbols = exports(image)
|
||||
jumps = [symbols[name + "#guest"][1] for name in ("global", "weak", "protected", "alias")]
|
||||
assert all(value != 0 and jump_target(image, value) == 0 for value in jumps), symbols
|
||||
assert len(set(jumps)) == len(jumps), symbols
|
||||
assert symbols["internal"] == (0x12, 0) and symbols["hidden"] == (0x12, 0), symbols
|
||||
assert symbols["object#guest"] == (0x11, 0), symbols
|
||||
|
||||
if runnable:
|
||||
libraries = work / "function" / "libs"
|
||||
libraries.mkdir()
|
||||
for name in ("libc.prx", "libkernel.prx"):
|
||||
shutil.copyfile(host, libraries / name)
|
||||
output = work / "function" / "output.elf"
|
||||
output.chmod(0o755)
|
||||
result = subprocess.run([str(output)], capture_output=True, text=True, timeout=30,
|
||||
env={**os.environ, "ANYPS5_GUEST_MODULE_ARGS": str(work / "events.txt")})
|
||||
assert result.returncode == 42, (result.returncode, result.stdout, result.stderr)
|
||||
print("Linux guest zero-address export tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -9,6 +9,7 @@
|
||||
- The [syscall scanner](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) discovers routine bounds from unwind ranges, direct branch targets, and address-taken `lea` targets ([#7547](https://github.com/boykopovar/AnyPS5/issues/7547)) so undecodable bytes in an inter-routine gap are not reported as an illegal instruction. Each unwind range is decoded from its start through its end, and direct branch and `lea` targets reached from it are followed until the next return, unconditional jump, indirect jump, or trap. Inside discovered routines, all syscalls are rejected, including dynamic RAX loads. In inter-routine gaps outside all discovered routines, embedded data does not trigger decode failures, and raw syscall bytes without a constant number are skipped and counted; stubs with constant syscall numbers in gaps remain detected. With no unwind ranges, discovery is not performed and every syscall byte pattern is rejected.
|
||||
|
||||
- [Executable guest imports](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - on Linux and macOS, a relocated executable import resolved to one guest provider requires that module and its dependencies at startup, even when it was discovered through `--module-dir` without a DT_NEEDED entry. Unreferenced symbols and no-op relocations do not require an otherwise deferred module. Console module-start ordering is unmeasured.
|
||||
- [Guest exports at address 0 on Linux](../../../core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp) - the host loader skips a defined symbol whose value is 0 (`check_match` in glibc's `elf/dl-lookup.c`), and the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) links a shared object's code at address 0, so its first function is exported at 0, as in the SDK's `hello_so` sample. Such a function export is written as a `jmp` to it in the segment the module gains, so a pointer to it taken through the symbol differs from one the module computes itself, and two names for it get two `jmp`s at different addresses. A module whose `jmp` would lie 2 GiB or more above address 0 fails to convert. A data or untyped export at address 0 stays unresolvable on Linux, and an import of one is not reported at conversion: the host loader stops on it as an undefined symbol.
|
||||
|
||||
- [Runtime-loaded modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) can be mapped early for bindings. Linux defers their initialization to an explicit load of each module; standard SDK folders keep their mapping initializer, including through the browser conversion API. Windows defers such a module too, unless a module outside these directories or in a standard SDK folder needs it, directly or through other modules; then it starts at startup without start arguments. On both, loading a module does not start the deferred modules it needs: each starts when the title loads it, and one the title never loads gets no `module_start` and no initializers. On macOS, a module another module needs is initialized at startup without start arguments; console dependency-start ordering is unmeasured.
|
||||
- [Runtime-loaded modules the executable needs](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) are mapped at startup on Windows and started when the title loads them with `sceKernelLoadStartModule`, with their start arguments, as a Unity player loads `mono-ps4.sprx`. A title that calls such a module before loading it finds its initializers not run; whether the console starts it earlier is not measured. A module the executable only imports from and the title never loads itself gets no `module_start` and no initializers at all under this deferral. The [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) prints `Deferred module <path>` at startup for each such module that an import of the executable names, whether or not the title loads it later; an import without a module name is not reported. macOS still starts it at startup.
|
||||
|
||||
Reference in new issue
Block a user