From f154e132bcfae671a45926918fdde073ee7b341e Mon Sep 17 00:00:00 2001 From: Savvanis Spyros Date: Tue, 20 Oct 2026 02:31:22 +0300 Subject: [PATCH] fix(relinker): take pointers into data between PS4 unwind functions as data The code analysis of the Windows and macOS outputs followed every relocation and lea target inside the code range as code. PS4 images keep read-only data between unwind functions, and strings and tables there decoded into garbage, failing the conversion. Outside the unwind functions such a target is now code only when it is 16-byte aligned, starts less than 256 bytes after an unwind function and decodes to a jump, return or trap; lea targets go through the same rule instead of the graph's own. Co-Authored-By: Claude Opus 5.5 --- .../src/analysis/CodeInstructionCollector.cpp | 55 +++++++++++++++++-- .../relinker/tests/test_sce_code_extent.py | 12 +++- docs/dev/debt/relinker.md | 2 + 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index e7a89396b1..9fab805034 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -96,8 +96,11 @@ std::set CodeInstructionCollector::Collect(const std::ve range(offset, size); return offset; }; + std::vector> sceFunctions; if (sceBase) { const auto unwindFunctions = UnusedNidFilter::ReadExceptionFunctions(bytes, headers, {}, {}); + for (const auto& function : unwindFunctions) sceFunctions.emplace_back(function.Begin, function.End); + std::sort(sceFunctions.begin(), sceFunctions.end()); for (auto& header : codeHeaders) { if (header.Type != 1 || (header.Flags & 1) == 0) continue; range(header.Offset, header.FileSize); @@ -118,10 +121,54 @@ std::set CodeInstructionCollector::Collect(const std::ve std::map tables; const auto inTable = [&](std::uint64_t address) { const auto after = tables.upper_bound(address); return after != tables.begin() && address < std::prev(after)->second; }; const auto addRoot = [&](std::uint64_t address) { if (isCode(address) && !inTable(address)) roots.insert(address); }; + const auto insideSceFunction = [&](std::uint64_t address) { + const auto after = std::upper_bound(sceFunctions.begin(), sceFunctions.end(), address, [](std::uint64_t value, const std::pair& function) { return value < function.first; }); + return after != sceFunctions.begin() && address < std::prev(after)->second; + }; + const auto nearSceFunctionEnd = [&](std::uint64_t address) { + const auto after = std::upper_bound(sceFunctions.begin(), sceFunctions.end(), address, [](std::uint64_t value, const std::pair& function) { return value < function.first; }); + if (after == sceFunctions.begin()) return false; + const auto end = std::prev(after)->second; + return address >= end && address - end < 256; + }; + const auto codeAt = [&](std::uint64_t address) { + for (const auto& header : codeHeaders) + if (header.Type == 1 && (header.Flags & 1) != 0 && address >= header.MappedAddress && address - header.MappedAddress < header.FileSize) + return std::span(bytes.data() + header.Offset + (address - header.MappedAddress), header.FileSize - (address - header.MappedAddress)); + return std::span(); + }; + const auto decodesToTransfer = [&](std::uint64_t address) { + const Codegen::X64InstructionDecoder probe; + for (std::size_t count = 0; count < 256; ++count) { + const auto code = codeAt(address); + if (code.empty()) return false; + Codegen::DecodedInstructionInfo info; + try { + info = probe.DecodeInstruction(code.data(), code.size()); + } catch (const Codegen::CodegenException&) { + return false; + } + if (info.Length == 0 || info.Length > code.size()) return false; + switch (info.FlowKind) { + case Codegen::ControlFlowKind::UnconditionalJump: + case Codegen::ControlFlowKind::Return: + case Codegen::ControlFlowKind::IndirectJump: + case Codegen::ControlFlowKind::Trap: + return true; + default: + break; + } + address += info.Length; + } + return false; + }; + const auto addPointerRoot = [&](std::uint64_t address) { + if (sceFunctions.empty() || insideSceFunction(address) || (address % 16 == 0 && nearSceFunctionEnd(address) && decodesToTransfer(address))) addRoot(address); + }; const auto addAddressTakenRoot = [&](std::uint64_t address, const std::uint8_t* code, const Codegen::DecodedInstructionInfo& info) { if (!info.HasRipRelativeDisp || code[info.OpcodeOffset] != 0x8d || !(info.RexPrefix & 8)) return; const auto displacement = std::bit_cast(Io::ReadU32(bytes, fileOffset(address, info.Length) + info.RipRelativeDispOffset)); - addRoot(address + info.Length + static_cast(displacement)); + addPointerRoot(address + info.Length + static_cast(displacement)); }; const auto addFunction = [&](std::uint64_t begin, std::uint64_t size, bool symbolAlias) { if (size == 0) return; @@ -226,7 +273,7 @@ std::set CodeInstructionCollector::Collect(const std::ve && addend <= static_cast(std::numeric_limits::max()) && Io::ReadU64(bytes, symbol + 8) == addend; if ((index != 0 && !zeroSectionSymbol && !matchingDefinedSymbol) || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot); - addRoot(addend); + addPointerRoot(addend); } else if (type == 1 || type == 6 || type == 7) { if (index != 0 && index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot); const auto symbol = symbols + index * 24; @@ -234,7 +281,7 @@ std::set CodeInstructionCollector::Collect(const std::ve else { const auto target = Io::ReadU64(bytes, symbol + 8) + addend; if (!pointers.Values.emplace(slot, target).second) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot); - addRoot(target); + addPointerRoot(target); } } } @@ -405,7 +452,7 @@ std::set CodeInstructionCollector::Collect(const std::ve for (const auto root : roots) if (root >= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root); if (entries.empty()) continue; const std::span text(bytes.data() + header.Offset, header.FileSize); - const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, true, sectionReachable[i], [&](std::uint64_t count) { report(6000 + 2500 * count / std::max(scanBytes, 1)); }, inTable); + const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, sceFunctions.empty(), sectionReachable[i], [&](std::uint64_t count) { report(6000 + 2500 * count / std::max(scanBytes, 1)); }, inTable); for (const auto address : graph->ReachableVaddrs()) { if (!decodedInstructions.insert(address).second) continue; instructions.insert(address); diff --git a/core/relinker/relinker/tests/test_sce_code_extent.py b/core/relinker/relinker/tests/test_sce_code_extent.py index 5bc8a2321c..e3a50f8296 100644 --- a/core/relinker/relinker/tests/test_sce_code_extent.py +++ b/core/relinker/relinker/tests/test_sce_code_extent.py @@ -22,10 +22,11 @@ POINTER = 0xB00 INTERPRETER = b"/libexec/ld-elf.so.1\0" OVERLAPPING = bytes.fromhex("b8 64 c3 90 90 72 fa c3") UNSUPPORTED_TLS = bytes.fromhex("64 2d 65 6c 66 2e c3") +INVALID = bytes.fromhex("41 41 41 41 41 41 66 66 66 66 2e 0f 1f 84 00 00 00 00 00") def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False, data_pointer=False, gap_entry=False, - plt_at=None, early_function=False, function_tls=False): + plt_at=None, early_function=False, function_tls=False, pointer_target=None, gap_bytes=None): plt = FUNCTION + FUNCTION_SIZE + gap if plt_at is None else plt_at entry = FUNCTION + FUNCTION_SIZE if gap_entry else FUNCTION data = plt + 0x20 if plt > FUNCTION else FUNCTION + FUNCTION_SIZE @@ -40,7 +41,7 @@ def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False, (1, 1), (0x61000035, 0x600), (0x61000037, 16), (0x61000039, 0x620), (0x6100003B, 24), (0x6100003F, 48), - (0x6100002F, 0x700), (0x61000031, 24 if interpreter_pointer else 0), (0x61000033, 24), + (0x6100002F, 0x700), (0x61000031, 24 if interpreter_pointer or pointer_target is not None else 0), (0x61000033, 24), (0x61000027, GOT), (0x61000029, 0x720), (0x6100002B, 7), (0x6100002D, 24), (0, 0), ] @@ -82,6 +83,10 @@ def fixture(sce_block=True, plt_push_offset=8, gap=0, interpreter_pointer=False, image[FUNCTION:FUNCTION + 3] = b"\x48\x8d\x05" struct.pack_into("