fix(relinker): resolve SCE relocation tables relative to dynlibdata
This commit is contained in:
parent
ccf9ff9044
commit
cdfc11cf56
|
|
@ -59,15 +59,19 @@ constexpr std::size_t RelaEntSize = 24;
|
|||
void collectRelativeEntries(
|
||||
const std::vector<std::uint8_t>& elfBytes,
|
||||
const std::vector<LoadSegment>& loads,
|
||||
VirtualAddress tableVaddr,
|
||||
std::uint64_t tableAddress,
|
||||
std::uint64_t tableSize,
|
||||
bool sceTable,
|
||||
std::uint64_t sceTableBase,
|
||||
std::unordered_map<VirtualAddress, VirtualAddress>& out
|
||||
) {
|
||||
if (tableSize == 0) return;
|
||||
if (tableSize == 0 && !sceTable) return;
|
||||
if (tableSize % RelaEntSize != 0)
|
||||
throw RelinkerException("Invalid relocation table size", tableSize);
|
||||
|
||||
std::uint64_t tableFileOff = vaddrToFileOffset(loads, tableVaddr, tableSize);
|
||||
if (sceTable && tableAddress > std::numeric_limits<std::uint64_t>::max() - sceTableBase)
|
||||
throw RelinkerException("Relocation table file offset overflow", tableAddress);
|
||||
std::uint64_t tableFileOff = sceTable ? sceTableBase + tableAddress : vaddrToFileOffset(loads, tableAddress, tableSize);
|
||||
if (tableFileOff > elfBytes.size())
|
||||
throw RelinkerException("Relocation table out of bounds", tableFileOff);
|
||||
if (tableSize > elfBytes.size() - tableFileOff)
|
||||
|
|
@ -128,12 +132,21 @@ std::unique_ptr<IRelativeRelocationIndex> BuildRelativeRelocationIndex(
|
|||
std::uint16_t phCount = read16(elfBytes, 56);
|
||||
|
||||
if (phEntSize < 56) throw RelinkerException("ELF program header entry too small");
|
||||
if (phOff > elfBytes.size() || phCount > (elfBytes.size() - phOff) / phEntSize)
|
||||
throw RelinkerException("Program header out of bounds");
|
||||
|
||||
std::vector<LoadSegment> loads;
|
||||
std::uint64_t sceTableBase = 0;
|
||||
bool hasSceDynlibData = false;
|
||||
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");
|
||||
std::uint32_t type = read32(elfBytes, phPos);
|
||||
if (type == PT_SCE_DYNLIBDATA) {
|
||||
if (hasSceDynlibData) throw RelinkerException("Multiple PT_SCE_DYNLIBDATA segments", phPos);
|
||||
hasSceDynlibData = true;
|
||||
sceTableBase = read64(elfBytes, phPos + 8);
|
||||
}
|
||||
if (type != PT_LOAD) continue;
|
||||
LoadSegment seg;
|
||||
seg.fileOffset = read64(elfBytes, phPos + 8);
|
||||
|
|
@ -159,6 +172,8 @@ std::unique_ptr<IRelativeRelocationIndex> BuildRelativeRelocationIndex(
|
|||
std::uint64_t relaSz = 0;
|
||||
VirtualAddress jmprelVa = 0;
|
||||
std::uint64_t pltrelsz = 0;
|
||||
bool relaSce = false;
|
||||
bool jmprelSce = false;
|
||||
|
||||
for (std::uint64_t off = 0; off <= segSz && segSz - off >= 16; off += 16) {
|
||||
std::size_t pos = static_cast<std::size_t>(segOff + off);
|
||||
|
|
@ -166,14 +181,20 @@ std::unique_ptr<IRelativeRelocationIndex> BuildRelativeRelocationIndex(
|
|||
std::uint64_t val = read64(elfBytes, pos + 8);
|
||||
|
||||
if (tag == DT_NULL) break;
|
||||
if (tag == DT_RELA || tag == DT_OS_RELA) relaVa = val;
|
||||
if (tag == DT_RELA || tag == DT_OS_RELA) {
|
||||
relaVa = val;
|
||||
relaSce = tag == DT_OS_RELA;
|
||||
}
|
||||
if (tag == DT_RELASZ || tag == DT_OS_RELASZ) relaSz = val;
|
||||
if (tag == DT_JMPREL || tag == DT_OS_JMPREL) jmprelVa = val;
|
||||
if (tag == DT_JMPREL || tag == DT_OS_JMPREL) {
|
||||
jmprelVa = val;
|
||||
jmprelSce = tag == DT_OS_JMPREL;
|
||||
}
|
||||
if (tag == DT_PLTRELSZ || tag == DT_OS_PLTRELSZ) pltrelsz = val;
|
||||
}
|
||||
|
||||
collectRelativeEntries(elfBytes, loads, relaVa, relaSz, targets);
|
||||
collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, targets);
|
||||
collectRelativeEntries(elfBytes, loads, relaVa, relaSz, relaSce, sceTableBase, targets);
|
||||
collectRelativeEntries(elfBytes, loads, jmprelVa, pltrelsz, jmprelSce, sceTableBase, targets);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ public:
|
|||
requireRange(segment.Offset, segment.FileSize);
|
||||
if (segment.MemorySize > std::numeric_limits<VirtualAddress>::max() - segment.MappedAddress)
|
||||
throw RelinkerException("Strict filter: segment address overflow", segment.MappedAddress);
|
||||
if (segment.Type == 0x61000000) {
|
||||
if (hasSceDynlibData) throw RelinkerException("Strict filter: multiple PT_SCE_DYNLIBDATA segments", segment.Offset);
|
||||
hasSceDynlibData = true;
|
||||
sceTableBase = segment.Offset;
|
||||
}
|
||||
if (segment.Type == 1 && segment.FileSize > segment.MemorySize)
|
||||
throw RelinkerException("Strict filter: invalid load segment size", segment.Offset);
|
||||
if (segment.Type != 2) continue;
|
||||
|
|
@ -136,6 +141,8 @@ private:
|
|||
ElfReader reader;
|
||||
std::vector<ProgramHeader> headers;
|
||||
std::map<std::int64_t, std::uint64_t> tags;
|
||||
std::uint64_t sceTableBase = 0;
|
||||
bool hasSceDynlibData = false;
|
||||
|
||||
void requireRange(std::uint64_t offset, std::uint64_t size) const {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) throw RelinkerException("Strict filter: file range out of bounds", offset);
|
||||
|
|
@ -168,7 +175,10 @@ private:
|
|||
std::optional<std::uint64_t> table(std::int64_t standardTag, std::int64_t osTag) const {
|
||||
const auto address = value(standardTag, osTag);
|
||||
if (!address) return std::nullopt;
|
||||
return tags.contains(osTag) ? *address : fileOffset(*address, 0);
|
||||
if (!tags.contains(osTag)) return fileOffset(*address, 0);
|
||||
if (*address > std::numeric_limits<std::uint64_t>::max() - sceTableBase)
|
||||
throw RelinkerException("Strict filter: SCE table file offset overflow", *address);
|
||||
return sceTableBase + *address;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -465,6 +467,117 @@ void relativeRelocationTableBounds() {
|
|||
requireFailure([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflowDynamicOffset); }, "PT_DYNAMIC offset near UINT64_MAX was accepted");
|
||||
}
|
||||
|
||||
void sceDynlibDataOffsets() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0xC3});
|
||||
importThunk(input, 16, 0x2000);
|
||||
auto bytes = elfFixture(input);
|
||||
bytes.resize(0xA00);
|
||||
write<std::uint16_t>(bytes, 56, 4);
|
||||
write<std::uint32_t>(bytes, 176, 2);
|
||||
write<std::uint32_t>(bytes, 180, 6);
|
||||
write<std::uint64_t>(bytes, 184, 0x300);
|
||||
write<std::uint64_t>(bytes, 192, 0x2000);
|
||||
write<std::uint64_t>(bytes, 208, 0x40);
|
||||
write<std::uint64_t>(bytes, 216, 0x40);
|
||||
write<std::uint64_t>(bytes, 224, 8);
|
||||
write<std::uint32_t>(bytes, 232, 0x61000000);
|
||||
write<std::uint32_t>(bytes, 236, 4);
|
||||
write<std::uint64_t>(bytes, 240, 0x500);
|
||||
write<std::uint64_t>(bytes, 264, 0x500);
|
||||
write<std::uint64_t>(bytes, 272, 0x500);
|
||||
write<std::uint64_t>(bytes, 280, 0x10);
|
||||
write<std::int64_t>(bytes, 0x300, 0x6100002F);
|
||||
write<std::uint64_t>(bytes, 0x308, 0x400);
|
||||
write<std::int64_t>(bytes, 0x310, 0x61000031);
|
||||
write<std::uint64_t>(bytes, 0x318, 24);
|
||||
write<std::int64_t>(bytes, 0x320, 0x61000033);
|
||||
write<std::uint64_t>(bytes, 0x328, 24);
|
||||
write<std::uint64_t>(bytes, 0x900, 0x2040);
|
||||
write<std::uint64_t>(bytes, 0x908, 8);
|
||||
write<std::uint64_t>(bytes, 0x910, 0x1010);
|
||||
|
||||
std::string failures;
|
||||
try {
|
||||
const auto index = Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(bytes);
|
||||
if (index->TargetOfSlot(0x2040) != 0x1010) failures += "SCE relative relocation was not indexed; ";
|
||||
} catch (const std::exception& error) {
|
||||
failures += std::string("SCE relative relocation index: ") + error.what() + "; ";
|
||||
}
|
||||
try {
|
||||
const std::vector<Relinker::NidReference> references = {{"live", {}, 6, 0x900, 0x2000, 0}};
|
||||
const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr);
|
||||
if (filtered.size() != 1) failures += "SCE relocation target did not keep its import";
|
||||
} catch (const std::exception& error) {
|
||||
failures += std::string("SCE strict filter: ") + error.what();
|
||||
}
|
||||
require(failures.empty(), failures.c_str());
|
||||
|
||||
auto noSegment = bytes;
|
||||
write<std::uint32_t>(noSegment, 232, 0x6FFFFF01);
|
||||
write<std::uint64_t>(noSegment, 0x308, 0x900);
|
||||
require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(noSegment)->TargetOfSlot(0x2040) == 0x1010,
|
||||
"SCE relocation without PT_SCE_DYNLIBDATA was not read at its absolute file offset");
|
||||
const std::vector<Relinker::NidReference> references = {{"live", {}, 6, 0x900, 0x2000, 0}};
|
||||
require(Relinker::MakeStrictUnusedNidFilter()->Filter(references, noSegment, input.Text, input.TextVaddr).size() == 1,
|
||||
"Strict filter lost an SCE relocation without PT_SCE_DYNLIBDATA");
|
||||
|
||||
auto jmprel = bytes;
|
||||
write<std::uint64_t>(jmprel, 208, 0x60);
|
||||
write<std::uint64_t>(jmprel, 216, 0x60);
|
||||
write<std::uint64_t>(jmprel, 0x318, 0);
|
||||
write<std::int64_t>(jmprel, 0x330, 0x61000029);
|
||||
write<std::uint64_t>(jmprel, 0x338, 0x430);
|
||||
write<std::int64_t>(jmprel, 0x340, 0x6100002D);
|
||||
write<std::uint64_t>(jmprel, 0x348, 24);
|
||||
write<std::uint64_t>(jmprel, 0x930, 0x2050);
|
||||
write<std::uint64_t>(jmprel, 0x938, 8);
|
||||
write<std::uint64_t>(jmprel, 0x940, 0x1010);
|
||||
require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(jmprel)->TargetOfSlot(0x2050) == 0x1010,
|
||||
"SCE JMPREL table was not read at its segment-relative file offset");
|
||||
write<std::uint64_t>(jmprel, 0x338, 0x4F0);
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(jmprel); },
|
||||
"SCE JMPREL table past the end of the file was accepted");
|
||||
|
||||
auto emptyJmprel = jmprel;
|
||||
write<std::uint64_t>(emptyJmprel, 0x338, 0x430);
|
||||
write<std::uint64_t>(emptyJmprel, 0x348, 0);
|
||||
require(!Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel)->TargetOfSlot(0x2050),
|
||||
"Valid empty SCE JMPREL table was rejected or parsed");
|
||||
write<std::uint64_t>(emptyJmprel, 0x338, 0x501);
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel); },
|
||||
"Empty SCE JMPREL table past the file was accepted");
|
||||
write<std::uint64_t>(emptyJmprel, 0x338, std::numeric_limits<std::uint64_t>::max());
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel); },
|
||||
"Empty SCE JMPREL table with overflowing offset was accepted");
|
||||
|
||||
auto emptyRela = bytes;
|
||||
write<std::uint64_t>(emptyRela, 0x318, 0);
|
||||
require(!Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela)->TargetOfSlot(0x2040),
|
||||
"Valid empty SCE RELA table was rejected or parsed");
|
||||
write<std::uint64_t>(emptyRela, 0x308, 0x501);
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela); },
|
||||
"Empty SCE RELA table past the file was accepted");
|
||||
write<std::uint64_t>(emptyRela, 0x308, std::numeric_limits<std::uint64_t>::max());
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela); },
|
||||
"Empty SCE RELA table with overflowing offset was accepted");
|
||||
|
||||
auto overflow = bytes;
|
||||
write<std::uint64_t>(overflow, 0x308, std::numeric_limits<std::uint64_t>::max());
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflow); },
|
||||
"SCE relocation file offset overflow was accepted");
|
||||
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, overflow, input.Text, input.TextVaddr); },
|
||||
"Strict filter accepted an overflowing SCE table offset");
|
||||
|
||||
auto duplicate = bytes;
|
||||
write<std::uint16_t>(duplicate, 56, 5);
|
||||
write<std::uint32_t>(duplicate, 288, 0x61000000);
|
||||
requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(duplicate); },
|
||||
"Duplicate PT_SCE_DYNLIBDATA segments were accepted");
|
||||
requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter()->Filter(references, duplicate, input.Text, input.TextVaddr); },
|
||||
"Strict filter accepted duplicate PT_SCE_DYNLIBDATA segments");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -486,6 +599,7 @@ int main() {
|
|||
filterCallbackDataImports();
|
||||
filterAndPltCompaction();
|
||||
relativeRelocationTableBounds();
|
||||
sceDynlibDataOffsets();
|
||||
std::cout << "Strict NID filter tests passed\n";
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
|
|
|
|||
|
|
@ -466,7 +466,6 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Image loads](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) (shader recompiler) - an image_load reads a sampled binding, also of an image the shader stores to, so in a primitive-ordered pixel shader it misses an earlier overlapping primitive's image_store in the same draw.
|
||||
- [PA_SC_CONSERVATIVE_RASTERIZATION_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with `OVER_RAST_ENABLE` and `UNDER_RAST_ENABLE` clear is decoded as disabled even when `UNCERTAINTY_REGION_MODE` or the `OUTER_`/`INNER_UNCERTAINTY_EDGERULE_OVERRIDE` bits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured.
|
||||
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
|
||||
- The [relinker pipeline](../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp) reads `DT_SCE_*` table addresses as offsets into the `PT_SCE_DYNLIBDATA` segment, where PS4 executables (CUSA00900) keep them; without that segment they are still read as file offsets. It rejects multiple `PT_SCE_DYNLIBDATA` segments, matching the single segment in the [documented PS4 ELF layout](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/wiki/PS4-ELF-Specification). The `unused-filter=1` and `unused-filter=2` analyses do not: [RelativeRelocationIndex](../../core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp) reads `DT_SCE_RELA` and `DT_SCE_JMPREL` as virtual addresses and [StrictNidFilter](../../core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp) reads the SCE tables as file offsets, so they misread a PS4 executable's tables.
|
||||
- The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
|
||||
- [getifaddrs](../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if<index>` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask.
|
||||
- [gethostname](../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented.
|
||||
|
|
|
|||
Loading…
Reference in New Issue