Merge pull request #2330 from simonbbby/fix/relinker-init-array-roots
fix(relinker): collect init and fini array roots through the containing load segment
This commit is contained in:
commit
9db3480a65
|
|
@ -293,6 +293,8 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
add_test(NAME entry_point_bounds COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_entry_point_bounds.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME init_array_roots COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_init_array_roots.py $<TARGET_FILE:relinker>)
|
||||
else()
|
||||
message(STATUS "Python3 interpreter not found; skipping Python relinker tests")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ inline constexpr std::int64_t DT_INIT_ARRAYSZ = 27;
|
|||
inline constexpr std::int64_t DT_FINI_ARRAYSZ = 28;
|
||||
inline constexpr std::int64_t DT_RUNPATH = 29;
|
||||
inline constexpr std::int64_t DT_FLAGS = 30;
|
||||
inline constexpr std::int64_t DT_PREINIT_ARRAY = 32;
|
||||
inline constexpr std::int64_t DT_PREINIT_ARRAYSZ = 33;
|
||||
inline constexpr std::uint64_t DF_BIND_NOW = 0x8;
|
||||
|
||||
inline constexpr std::int64_t DT_OS_INIT = 0x6000000c;
|
||||
|
|
@ -105,6 +107,8 @@ inline constexpr std::int64_t DT_OS_INIT_ARRAY = 0x60000019;
|
|||
inline constexpr std::int64_t DT_OS_FINI_ARRAY = 0x6000001a;
|
||||
inline constexpr std::int64_t DT_OS_INIT_ARRAYSZ = 0x6000001b;
|
||||
inline constexpr std::int64_t DT_OS_FINI_ARRAYSZ = 0x6000001c;
|
||||
inline constexpr std::int64_t DT_OS_PREINIT_ARRAY = 0x60000020;
|
||||
inline constexpr std::int64_t DT_OS_PREINIT_ARRAYSZ = 0x60000021;
|
||||
inline constexpr std::int64_t DT_OS_PLTGOT = 0x61000027;
|
||||
inline constexpr std::int64_t DT_OS_JMPREL = 0x61000029;
|
||||
inline constexpr std::int64_t DT_OS_PLTREL = 0x6100002b;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define RELINKER_ANALYSIS_UNUSED_NID_FILTER_IENTRYPOINTCOLLECTOR_HPP
|
||||
|
||||
#include <relinker/domain/Types.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/IRelativeRelocationIndex.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -13,7 +14,8 @@ public:
|
|||
virtual std::vector<VirtualAddress> Collect(
|
||||
const std::vector<std::uint8_t>& elfBytes,
|
||||
VirtualAddress textVaddr,
|
||||
std::size_t textSize
|
||||
std::size_t textSize,
|
||||
const IRelativeRelocationIndex& relativeRelocations
|
||||
) const = 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@ public:
|
|||
) override {
|
||||
if (textSection.empty()) throw RelinkerException("Cannot filter NIDs: text section is empty");
|
||||
|
||||
auto relativeRelocations = UnusedNidFilter::BuildRelativeRelocationIndex(elfBytes);
|
||||
auto collector = UnusedNidFilter::MakeEntryPointCollector();
|
||||
auto entries = collector->Collect(elfBytes, textVAddr, textSection.size());
|
||||
auto entries = collector->Collect(elfBytes, textVAddr, textSection.size(), *relativeRelocations);
|
||||
|
||||
if (entries.empty()) throw RelinkerException("Cannot filter NIDs: no entry points found");
|
||||
|
||||
VirtualAddress primary = entries[0];
|
||||
std::vector<VirtualAddress> extra(entries.begin() + 1, entries.end());
|
||||
|
||||
auto relativeRelocations = UnusedNidFilter::BuildRelativeRelocationIndex(elfBytes);
|
||||
auto cfg = UnusedNidFilter::BuildControlFlowGraph(textSection, textVAddr, primary, extra, *relativeRelocations);
|
||||
auto index = UnusedNidFilter::BuildGotAccessIndex(*cfg, textSection, textVAddr);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/IEntryPointCollector.hpp>
|
||||
#include <elfpatcher/general/ElfConstants.hpp>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
|
|
@ -30,6 +31,12 @@ bool fitsAt(std::uint64_t base, std::uint64_t offset, std::uint64_t length, std:
|
|||
return base <= size && offset <= size - base && length <= size - base - offset;
|
||||
}
|
||||
|
||||
struct LoadSegment {
|
||||
VirtualAddress vaddr;
|
||||
std::uint64_t fileOffset;
|
||||
std::uint64_t fileSize;
|
||||
};
|
||||
|
||||
bool inText(VirtualAddress va, VirtualAddress textVaddr, std::size_t textSize) {
|
||||
return va >= textVaddr && va < textVaddr + static_cast<VirtualAddress>(textSize);
|
||||
}
|
||||
|
|
@ -41,7 +48,8 @@ public:
|
|||
std::vector<VirtualAddress> Collect(
|
||||
const std::vector<std::uint8_t>& elfBytes,
|
||||
VirtualAddress textVaddr,
|
||||
std::size_t textSize
|
||||
std::size_t textSize,
|
||||
const IRelativeRelocationIndex& relativeRelocations
|
||||
) const override {
|
||||
if (elfBytes.size() < 64) throw RelinkerException("ELF too small for header");
|
||||
if (elfBytes[0] != 0x7f || elfBytes[1] != 'E' ||
|
||||
|
|
@ -66,6 +74,14 @@ public:
|
|||
|
||||
if (phEntSize < 56) throw RelinkerException("ELF program header entry too small");
|
||||
|
||||
std::vector<LoadSegment> loads;
|
||||
for (std::uint16_t i = 0; i < phCount; ++i) {
|
||||
std::size_t phPos = static_cast<std::size_t>(phOff) + i * phEntSize;
|
||||
if (phPos + 56 > elfBytes.size()) throw RelinkerException("Program header out of bounds");
|
||||
if (read32(elfBytes, phPos) != PT_LOAD) continue;
|
||||
loads.push_back({read64(elfBytes, phPos + 16), read64(elfBytes, phPos + 8), read64(elfBytes, phPos + 32)});
|
||||
}
|
||||
|
||||
for (std::uint16_t i = 0; i < phCount; ++i) {
|
||||
const std::uint64_t phIndexOff = static_cast<std::uint64_t>(i) * phEntSize;
|
||||
if (!fitsAt(phOff, phIndexOff, 56, elfBytes.size())) throw RelinkerException("Program header out of bounds");
|
||||
|
|
@ -84,6 +100,8 @@ public:
|
|||
std::uint64_t initArraySz = 0;
|
||||
VirtualAddress finiArrayVa = 0;
|
||||
std::uint64_t finiArraySz = 0;
|
||||
VirtualAddress preinitArrayVa = 0;
|
||||
std::uint64_t preinitArraySz = 0;
|
||||
|
||||
for (std::uint64_t off = 0; off + 16 <= segSz; off += 16) {
|
||||
std::size_t pos = static_cast<std::size_t>(segOff + off);
|
||||
|
|
@ -97,24 +115,37 @@ public:
|
|||
if (tag == DT_INIT_ARRAYSZ || tag == DT_OS_INIT_ARRAYSZ) initArraySz = val;
|
||||
if (tag == DT_FINI_ARRAY || tag == DT_OS_FINI_ARRAY) finiArrayVa = val;
|
||||
if (tag == DT_FINI_ARRAYSZ || tag == DT_OS_FINI_ARRAYSZ) finiArraySz = val;
|
||||
if (tag == DT_PREINIT_ARRAY || tag == DT_OS_PREINIT_ARRAY) preinitArrayVa = val;
|
||||
if (tag == DT_PREINIT_ARRAYSZ || tag == DT_OS_PREINIT_ARRAYSZ) preinitArraySz = val;
|
||||
}
|
||||
|
||||
auto collectArray = [&](VirtualAddress arrayVa, std::uint64_t arraySz) {
|
||||
if (arrayVa == 0 || arraySz == 0) return;
|
||||
std::uint64_t phFileOff = read64(elfBytes, phPos + 8);
|
||||
std::uint64_t phVaddr = read64(elfBytes, phPos + 16);
|
||||
if (arrayVa < phVaddr || !fitsAt(phFileOff, arrayVa - phVaddr, 0, elfBytes.size())) return;
|
||||
std::uint64_t arrayFileOff = phFileOff + (arrayVa - phVaddr);
|
||||
std::uint64_t count = arraySz / 8;
|
||||
for (std::uint64_t k = 0; k < count; ++k) {
|
||||
if (!fitsAt(arrayFileOff, k * 8, 8, elfBytes.size())) break;
|
||||
std::size_t entPos = static_cast<std::size_t>(arrayFileOff + k * 8);
|
||||
addIfInText(read64(elfBytes, entPos));
|
||||
if (arraySz % 8 != 0) throw RelinkerException("Init/fini array size is not a multiple of 8", arrayVa);
|
||||
const LoadSegment* container = nullptr;
|
||||
for (const auto& load : loads) {
|
||||
if (arrayVa >= load.vaddr && arrayVa - load.vaddr <= load.fileSize &&
|
||||
arraySz <= load.fileSize - (arrayVa - load.vaddr)) {
|
||||
container = &load;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!container) throw RelinkerException("Init/fini array is not file-backed", arrayVa);
|
||||
std::uint64_t arrayFileOff = container->fileOffset + (arrayVa - container->vaddr);
|
||||
if (arrayFileOff > elfBytes.size() || arraySz > elfBytes.size() - arrayFileOff)
|
||||
throw RelinkerException("Init/fini array is out of bounds", arrayVa);
|
||||
for (std::uint64_t k = 0; k < arraySz / 8; ++k) {
|
||||
VirtualAddress slot = arrayVa + k * 8;
|
||||
auto relocated = relativeRelocations.TargetOfSlot(slot);
|
||||
std::uint64_t target = relocated ? *relocated : read64(elfBytes, static_cast<std::size_t>(arrayFileOff + k * 8));
|
||||
if (target == 0 || target == std::numeric_limits<std::uint64_t>::max()) continue;
|
||||
addIfInText(target);
|
||||
}
|
||||
};
|
||||
|
||||
collectArray(initArrayVa, initArraySz);
|
||||
collectArray(finiArrayVa, finiArraySz);
|
||||
collectArray(preinitArrayVa, preinitArraySz);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from test_jmprel_table_bounds import fixture, DT_RELA, DT_RELASZ
|
||||
|
||||
|
||||
DT_INIT_ARRAY = 25
|
||||
DT_FINI_ARRAY = 26
|
||||
DT_INIT_ARRAYSZ = 27
|
||||
DT_FINI_ARRAYSZ = 28
|
||||
DT_PREINIT_ARRAY = 32
|
||||
DT_PREINIT_ARRAYSZ = 33
|
||||
DT_OS_INIT_ARRAY = 0x60000019
|
||||
DT_OS_INIT_ARRAYSZ = 0x6000001B
|
||||
DT_OS_PREINIT_ARRAY = 0x60000020
|
||||
DT_OS_PREINIT_ARRAYSZ = 0x60000021
|
||||
R_X86_64_GLOB_DAT = 6
|
||||
R_X86_64_RELATIVE = 8
|
||||
DYNAMIC = 0x400
|
||||
RELA = 0x7A0
|
||||
GOT_SLOT = 0x308
|
||||
FUNCTION = 0x280
|
||||
BEFORE_DYNAMIC = 0x380
|
||||
AFTER_DYNAMIC = 0x540
|
||||
LOAD_END = 0x800
|
||||
KEPT = "CFG/GOT filtering: 1 -> 1; filtered=0"
|
||||
DROPPED = "CFG/GOT filtering: 1 -> 0; filtered=1"
|
||||
|
||||
|
||||
def image(array_tag=None, array_size_tag=None, array_va=0, slots=(), relative=(), load_size=None):
|
||||
elf = fixture()
|
||||
for index in range(16):
|
||||
tag, _ = struct.unpack_from("<qQ", elf, DYNAMIC + index * 16)
|
||||
if tag == DT_RELASZ:
|
||||
struct.pack_into("<qQ", elf, DYNAMIC + index * 16, DT_RELASZ, 24 * (1 + len(relative)))
|
||||
elf[0x200] = 0xC3
|
||||
elf[FUNCTION:FUNCTION + 8] = b"\x48\x8b\x05" + struct.pack("<i", GOT_SLOT - (FUNCTION + 7)) + b"\xc3"
|
||||
if array_tag is not None:
|
||||
tags = [(array_tag, array_va), (array_size_tag, 8 * len(slots)), (0, 0)]
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", elf, DYNAMIC + 13 * 16 + index * 16, *tag)
|
||||
struct.pack_into("<Q", elf, 120 + 32, 16 * 16)
|
||||
struct.pack_into("<Q", elf, 120 + 40, 16 * 16)
|
||||
for index, word in enumerate(slots):
|
||||
struct.pack_into("<Q", elf, array_va + index * 8, word)
|
||||
struct.pack_into("<QQq", elf, RELA, GOT_SLOT, (1 << 32) | R_X86_64_GLOB_DAT, 0)
|
||||
for index, (slot, addend) in enumerate(relative, 1):
|
||||
struct.pack_into("<QQq", elf, RELA + index * 24, slot, R_X86_64_RELATIVE, addend)
|
||||
if load_size is not None:
|
||||
struct.pack_into("<Q", elf, 64 + 32, load_size)
|
||||
struct.pack_into("<Q", elf, 64 + 40, load_size)
|
||||
return elf
|
||||
|
||||
|
||||
def run(relinker, work, name, elf):
|
||||
source = work / (name + ".elf")
|
||||
output = work / (name + ".exe")
|
||||
source.write_bytes(elf)
|
||||
return subprocess.run([str(relinker), "--skip-sce-module", "--windows", "unused-filter=1", str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=20), output
|
||||
|
||||
|
||||
def filtered(relinker, work, name, elf):
|
||||
result, output = run(relinker, work, name, elf)
|
||||
assert result.returncode == 0 and output.exists(), (name, result.stdout, result.stderr)
|
||||
lines = [line for line in result.stdout.splitlines() if line.startswith("CFG/GOT filtering:")]
|
||||
assert len(lines) == 1, (name, result.stdout)
|
||||
return lines[0]
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-init-array-") as directory:
|
||||
work = Path(directory)
|
||||
cases = [
|
||||
("no-array", image(), DROPPED),
|
||||
("init-before-dynamic", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION]), KEPT),
|
||||
("init-after-dynamic", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, AFTER_DYNAMIC, [FUNCTION]), KEPT),
|
||||
("init-os-tags", image(DT_OS_INIT_ARRAY, DT_OS_INIT_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION]), KEPT),
|
||||
("init-second-slot", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [0, FUNCTION]), KEPT),
|
||||
("init-terminators", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [(1 << 64) - 1, 0]), DROPPED),
|
||||
("init-relative-addend", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [0],
|
||||
[(BEFORE_DYNAMIC, FUNCTION)]), KEPT),
|
||||
("init-relative-overrides-word", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [0x200],
|
||||
[(BEFORE_DYNAMIC, FUNCTION)]), KEPT),
|
||||
("init-relative-terminator", image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION],
|
||||
[(BEFORE_DYNAMIC, 0)]), DROPPED),
|
||||
("fini-before-dynamic", image(DT_FINI_ARRAY, DT_FINI_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION]), KEPT),
|
||||
("fini-relative-addend", image(DT_FINI_ARRAY, DT_FINI_ARRAYSZ, AFTER_DYNAMIC, [0],
|
||||
[(AFTER_DYNAMIC, FUNCTION)]), KEPT),
|
||||
("preinit-before-dynamic", image(DT_PREINIT_ARRAY, DT_PREINIT_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION]), KEPT),
|
||||
("preinit-os-tags", image(DT_OS_PREINIT_ARRAY, DT_OS_PREINIT_ARRAYSZ, BEFORE_DYNAMIC, [FUNCTION]), KEPT),
|
||||
]
|
||||
for name, elf, expected in cases:
|
||||
line = filtered(relinker, work, name, elf)
|
||||
assert line == expected, (name, line, expected)
|
||||
|
||||
result, output = run(relinker, work, "unmapped-array",
|
||||
image(DT_INIT_ARRAY, DT_INIT_ARRAYSZ, LOAD_END + 0x100, [FUNCTION], load_size=LOAD_END))
|
||||
assert result.returncode == 2 and "array is not file-backed" in result.stderr and not output.exists(), (
|
||||
result.returncode, result.stdout, result.stderr)
|
||||
print("Init array root tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue