From 1bee5f35b7ccf8fea236da95d179e998e069ca53 Mon Sep 17 00:00:00 2001 From: wail-arch <116732676+wail-arch@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:23:08 +0000 Subject: [PATCH 1/2] fix(relinker): bind guest function exports at address 0 on Linux glibc's loader skips a defined symbol whose value is 0, and the ps5-payload-dev SDK links a shared object's code at address 0, so the first function of every SDK module could not be imported. Export such a function through a jmp in the module's added segment. --- core/relinker/CMakeLists.txt | 4 + .../src/linux/LinuxGuestModuleWriter.cpp | 11 +++ .../test_linux_guest_zero_address_exports.py | 90 +++++++++++++++++++ docs/dev/debt/relinker.md | 1 + 4 files changed, 106 insertions(+) create mode 100644 core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 41bbcc59f0..4be4fc4f17 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -630,6 +630,10 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $) add_test(NAME guest_module_identity COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_identity.py $) + add_test(NAME linux_guest_zero_address_exports COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_zero_address_exports.py + $ $) + 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 $ $) diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp index 76b91f97dc..4e8c2fcfdb 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -62,6 +62,17 @@ std::vector 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::numeric_limits::max()) - 5) + throw Domain::RelinkerException("Guest export at address 0 exceeds relative branch range"); + bytes.push_back(0xe9); + Io::AppendU32(bytes, static_cast(-static_cast(thunk + 5))); + Io::WriteU64(symbols, index * 24 + 8, thunk); + } std::vector needed; for (const auto& dependency : dependencies) needed.push_back(addString(dependency)); if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2")); diff --git a/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py b/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py new file mode 100644 index 0000000000..55ee15765e --- /dev/null +++ b/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py @@ -0,0 +1,90 @@ +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_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, info=0x12): + struct.pack_into(" Date: Sat, 24 Oct 2026 13:38:00 +0000 Subject: [PATCH 2/2] test(relinker): cover each export kind at address 0 on Linux Test weak, protected and aliased function exports at address 0, which each get their own jmp, next to internal, hidden and object exports, which stay at 0. Record that aliases get separate addresses, that the jmp must lie within 2 GiB of address 0 and that a data import at 0 is reported only by the host loader. Label the test and give it a timeout like its neighbours. --- core/relinker/CMakeLists.txt | 2 + .../test_linux_guest_zero_address_exports.py | 45 +++++++++++++++---- docs/dev/debt/relinker.md | 2 +- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4be4fc4f17..e309d58eb8 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -633,6 +633,8 @@ if(BUILD_TESTING) add_test(NAME linux_guest_zero_address_exports COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_zero_address_exports.py $ $) + 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 diff --git a/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py b/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py index 55ee15765e..c6ca0bcbde 100644 --- a/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py +++ b/core/relinker/relinker/tests/test_linux_guest_zero_address_exports.py @@ -8,13 +8,13 @@ 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, info=0x12): - struct.pack_into("