fix(relinker): accept an identical duplicate relocation in Windows executables

A RELA entry repeated with the same target, type and addend writes the
same value twice, so applying it once is equivalent. The Windows
relocation builder refused it as overlapping and code analysis as an
invalid relative relocation; the Linux output already passed it through.
Forever Launcher's game-child.bin (PPSA99030) repeats one RELATIVE entry
at 0x2df70 and could not be converted. Distinct entries at one target
are still refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
p3nicillinandClaude Opus 5.5 committed 2026-10-26 11:38:33 +00:00
1 parent 4b1a9a3b2f
commit 9673868d5f
4 files changed
+67 -2

No files matched your search

+3
View File
@@ -538,6 +538,9 @@ if(BUILD_TESTING)
add_test(NAME overlapping_relocations COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_overlapping_relocations.py $<TARGET_FILE:relinker>)
set_tests_properties(overlapping_relocations PROPERTIES TIMEOUT 30)
add_test(NAME duplicate_relocations COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_duplicate_relocations.py $<TARGET_FILE:relinker>)
set_tests_properties(duplicate_relocations PROPERTIES TIMEOUT 30)
add_test(NAME guest_own_tls_relocations COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_own_tls_relocations.py $<TARGET_FILE:relinker>)
set_tests_properties(guest_own_tls_relocations PROPERTIES TIMEOUT 30)
@@ -6,6 +6,7 @@
#include <limits>
#include <map>
#include <string>
#include <utility>
namespace Elfpatcher::Windows {
@@ -15,6 +16,7 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom
throw Domain::RelinkerException("Invalid ELF dynamic symbol table size");
PeRelocations result;
std::map<std::uint64_t, std::uint64_t> ranges;
std::map<std::uint64_t, std::pair<std::uint64_t, std::uint64_t>> entries;
const auto applyTable = [&](const std::vector<std::uint8_t>& table, const bool plt) {
if (table.size() % 24 != 0)
throw Domain::RelinkerException("Invalid ELF RELA table size");
@@ -29,10 +31,13 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom
if (target > std::numeric_limits<std::uint64_t>::max() - 8)
throw Domain::RelinkerException("Relocation target address overflow", target);
const auto rva = image.GetRva(target, 8);
const auto applied = entries.find(target);
if (applied != entries.end() && applied->second == std::pair{info, addend}) continue;
const auto next = ranges.lower_bound(target);
if ((next != ranges.end() && next->first < target + 8) || (next != ranges.begin() && std::prev(next)->second > target))
throw Domain::RelinkerException("Overlapping ELF relocation targets", target);
ranges.emplace(target, target + 8);
entries.emplace(target, std::pair{info, addend});
if (type == 8) {
if (symbol != 0)
throw Domain::RelinkerException("RELATIVE relocation has a symbol index", target);
@@ -310,7 +310,8 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
const auto matchingDefinedSymbol = hasSymbol && Io::ReadU16(bytes, symbol + 6) != 0
&& addend <= static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::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);
const auto [recorded, inserted] = pointers.Values.emplace(slot, addend);
if ((index != 0 && !zeroSectionSymbol && !matchingDefinedSymbol) || (!inserted && recorded->second != addend)) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot);
addPointerRoot(addend);
} else if (type == 1 || type == 6 || type == 7) {
if (index != 0 && index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot);
@@ -318,7 +319,8 @@ std::set<Domain::VirtualAddress> CodeInstructionCollector::Collect(const std::ve
if (index == 0 || Io::ReadU16(bytes, symbol + 6) == 0) importSlots.insert(slot);
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);
const auto [recorded, inserted] = pointers.Values.emplace(slot, target);
if (!inserted && recorded->second != target) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot);
addPointerRoot(target);
}
}
@@ -0,0 +1,55 @@
import struct
import subprocess
import sys
import tempfile
from pathlib import Path
from test_guest_none_relocations import module
from test_overlapping_relocations import TARGETS, check_output, fixture
def with_second_entry(entry):
image, local, plt = fixture("disjoint", False, False)
struct.pack_into("<QQq", image, 0x700 + 24, *entry)
for offset in range(0x400, 0x500, 16):
tag, value = struct.unpack_from("<qQ", image, offset)
if tag == 8:
struct.pack_into("<qQ", image, offset, tag, value + 24)
return image, local, plt
raise AssertionError("no DT_RELASZ tag")
def main():
relinker = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="anyps5-relocation-duplicate-") as directory:
work = Path(directory)
def convert(name, target, options, image):
case = work / f"{name}-{target}"
modules = case / "sce_module"
modules.mkdir(parents=True)
(modules / "module.prx").write_bytes(module())
source = case / "input.elf"
source.write_bytes(image)
output = case / ("output.exe" if target == "windows" else "output.out")
result = subprocess.run([str(relinker), "--registry", *options, str(source), str(output)],
capture_output=True, text=True, timeout=10)
return result, output
_, local, _ = fixture("disjoint", False, False)
duplicate, _, plt = with_second_entry(local[0])
for target, options in TARGETS:
result, output = convert("duplicate", target, options, duplicate)
assert result.returncode == 0, (target, result.stdout, result.stderr)
if target != "linux":
check_output(output.read_bytes(), target, False, local, plt)
offset, info, addend = local[0]
conflicting, _, _ = with_second_entry((offset, info, addend + 8))
result, output = convert("conflicting", "windows", ["--windows"], conflicting)
assert result.returncode == 2 and "Overlapping ELF relocation targets" in result.stderr and not output.exists(), (
result.stdout, result.stderr)
print("Duplicate relocation tests passed")
if __name__ == "__main__":
main()