mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
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:
1 parent
4b1a9a3b2f
commit
9673868d5f
4 files changed
+67
-2
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user