fix(relinker): preserve executable exports and dynamic lookup tags

This commit is contained in:
tototomate123 2026-10-10 16:57:31 +02:00
parent 7ea423f78b
commit ded8b4df23
5 changed files with 91 additions and 0 deletions

View File

@ -243,6 +243,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_runpath.py $<TARGET_FILE:relinker>)
add_test(NAME executable_segments COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_executable_segments.py $<TARGET_FILE:relinker>)
add_test(NAME linux_executable_exports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_executable_exports.py $<TARGET_FILE:relinker>)
add_test(NAME linux_entry_argv COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $<TARGET_FILE:relinker>)
add_test(NAME linux_program_header_order COMMAND ${Python3_EXECUTABLE}

View File

@ -76,10 +76,12 @@ inline constexpr std::uint32_t PT_OS_RELRO = 0x61000010;
inline constexpr std::uint32_t PT_LOOS = 0x61000000;
inline constexpr std::uint32_t PT_HIOS = 0x6fffffff;
inline constexpr std::int64_t DT_DEBUG = 21;
inline constexpr std::int64_t DT_NULL = 0;
inline constexpr std::int64_t DT_NEEDED = 1;
inline constexpr std::int64_t DT_PLTRELSZ = 2;
inline constexpr std::int64_t DT_PLTGOT = 3;
inline constexpr std::int64_t DT_HASH = 4;
inline constexpr std::int64_t DT_STRTAB = 5;
inline constexpr std::int64_t DT_SYMTAB = 6;
inline constexpr std::int64_t DT_RELA = 7;

View File

@ -4,6 +4,7 @@
#include <elfpatcher/general/SectionHeaderTableRequest.hpp>
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
#include <codegen/x86/StubBodyBuilder.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <limits>
#include <span>
@ -112,6 +113,15 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
buf.push_back(b);
alignBuf(buf, kDynSymAlignment);
const auto hashOff = static_cast<std::uint64_t>(buf.size());
const auto symbolCount = static_cast<std::uint32_t>(dynSection.DynSymData.size() / kSymEntrySize);
Io::AppendU32(buf, 1);
Io::AppendU32(buf, symbolCount);
Io::AppendU32(buf, symbolCount > 1 ? 1 : 0);
for (std::uint32_t index = 0; index < symbolCount; ++index)
Io::AppendU32(buf, index != 0 && index + 1 < symbolCount ? index + 1 : 0);
alignBuf(buf, kDynSymAlignment);
const auto relaOff = static_cast<std::uint64_t>(buf.size());
for (std::uint8_t b : dynSection.RelaData)
buf.push_back(b);
@ -138,6 +148,8 @@ std::vector<std::uint8_t> LinuxElfPatcher::Patch(
_appendDynEntry(dynSegBuf, DT_STRSZ, dynStrSize);
_appendDynEntry(dynSegBuf, DT_SYMTAB, vaddrOfExtraBlockOffset(dynSymOff));
_appendDynEntry(dynSegBuf, DT_SYMENT, kSymEntrySize);
_appendDynEntry(dynSegBuf, DT_HASH, vaddrOfExtraBlockOffset(hashOff));
_appendDynEntry(dynSegBuf, DT_DEBUG, 0);
if (!dynSection.RelaData.empty()) {
_appendDynEntry(dynSegBuf, DT_RELA, vaddrOfExtraBlockOffset(relaOff));
_appendDynEntry(dynSegBuf, DT_RELASZ, dynSection.RelaData.size());

View File

@ -294,6 +294,28 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
}
auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(dynamicRefs, neededLibraries, dynJmpRelOffset, pltCount);
if (hasTag(DT_OS_SYMTABSZ)) {
const auto symbolBytes = getTagValue(DT_OS_SYMTABSZ);
if (symbolBytes % symEntSize != 0 || dynSymTabOffset > raw.size() || symbolBytes > raw.size() - dynSymTabOffset)
throw RelinkerException("Invalid executable dynamic symbol table size");
for (ByteCount off = symEntSize; off < symbolBytes; off += symEntSize) {
const auto* symbol = raw.data() + dynSymTabOffset + off;
std::uint16_t section = 0;
std::memcpy(&section, symbol + 6, sizeof(section));
if (section == 0 || (symbol[4] >> 4) == 0 || (symbol[5] & 3) == 1 || (symbol[5] & 3) == 2) continue;
std::uint32_t sourceName = 0;
std::memcpy(&sourceName, symbol, sizeof(sourceName));
auto name = readCStr(sourceName);
name.resize(name.find('#') == std::string::npos ? name.size() : name.find('#'));
const auto nameOffset = static_cast<std::uint32_t>(dynSection.DynStrData.size());
dynSection.DynStrData.insert(dynSection.DynStrData.end(), name.begin(), name.end());
dynSection.DynStrData.push_back(0);
const auto position = dynSection.DynSymData.size();
dynSection.DynSymData.insert(dynSection.DynSymData.end(), symbol, symbol + symEntSize);
std::memcpy(dynSection.DynSymData.data() + position, &nameOffset, sizeof(nameOffset));
}
}
auto appendRela = [&](std::vector<std::uint8_t>& buf, std::uint64_t offset, std::uint64_t info, std::int64_t addend) {
std::size_t pos = buf.size();
buf.resize(pos + 24);

View File

@ -0,0 +1,53 @@
import os
from pathlib import Path
import platform
import struct
import subprocess
import sys
import tempfile
from test_linux_load_alignment import fixture
def main():
if sys.platform != "linux" or platform.machine() != "x86_64":
return
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-main-export-") as directory:
work = Path(directory)
probe = work / "probe.c"
probe.write_text(
"#include <dlfcn.h>\n"
"#include <unistd.h>\n"
"void check(void) {\n"
" int (*function)(void) = dlsym(RTLD_DEFAULT, \"FixtureExport\");\n"
" _exit(function && function() == 42 ? 42 : 1);\n"
"}\n")
library = work / "probe.so"
subprocess.run([os.environ.get("CC", "cc"), "-shared", "-fPIC", str(probe), "-o", str(library), "-ldl", "-Wl,-init,check"], check=True)
image = fixture()
strings = b"\0FixtureExport#A#B\0".ljust(32, b"\0") + str(library).encode() + b"\0"
image[0x4600:0x4600 + len(strings)] = strings
image[0x4210:0x4216] = bytes.fromhex("b82a000000c3")
struct.pack_into("<IBBHQQ", image, 0x4900, 0, 0, 0, 0, 0, 0)
struct.pack_into("<IBBHQQ", image, 0x4918, 1, 0x12, 0, 1, 0x210, 6)
tags = [(5, 0x600), (10, len(strings)), (6, 0x900), (11, 24), (7, 0x700), (8, 0), (9, 24), (0x6100003f, 48), (1, 32), (0, 0)]
struct.pack_into("<IIQQQQQQ", image, 120, 2, 6, 0x4800, 0x800, 0x800, len(tags) * 16, len(tags) * 16, 8)
for index, tag in enumerate(tags):
struct.pack_into("<qQ", image, 0x4800 + index * 16, *tag)
source = work / "input.elf"
source.write_bytes(image)
output = work / "output.elf"
result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)], capture_output=True, text=True)
assert result.returncode == 0, (result.stdout, result.stderr)
output.chmod(0o755)
executed = subprocess.run([str(output)], capture_output=True)
assert executed.returncode == 42, (executed.returncode, executed.stderr)
struct.pack_into("<Q", image, 0x4800 + 7 * 16 + 8, 47)
source.write_bytes(image)
invalid = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(work / "invalid.elf")], capture_output=True, text=True)
assert invalid.returncode == 2 and "dynamic symbol table size" in invalid.stderr, invalid.stderr
print("Linux executable exports test passed")
if __name__ == "__main__":
main()