mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-08 13:50:20 +00:00
The move path of SeparateSharedLoadPages padded the output byte by byte to an offset congruent modulo max(p_align, 0x1000), so a p_align of 0x80000000 grew the output by 2 GiB and 0x8000000000000000 ended in std::bad_alloc. A moved segment now starts at the next offset congruent to its address modulo the page and its p_align becomes at most 0x1000, since Linux maps every PT_LOAD from p_offset - (p_vaddr % 0x1000) and reads p_align only to align the ET_DYN load bias. At a fixed base every input PT_LOAD p_align is checked against the base before any segment is moved, with the message RebaseProgramHeaders uses.
279 lines
13 KiB
C++
279 lines
13 KiB
C++
#include <elfpatcher/linux/LinuxElfPatcher.hpp>
|
|
#include <elfpatcher/general/EntryStubBuilder.hpp>
|
|
#include <elfpatcher/general/ProgramHeaderLayoutBuilder.hpp>
|
|
#include <elfpatcher/general/SectionHeaderTableBuilder.hpp>
|
|
#include <elfpatcher/general/SegmentFilter.hpp>
|
|
#include <io/ByteWriter.hpp>
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
using Bytes = std::vector<std::uint8_t>;
|
|
|
|
constexpr std::uint64_t kPage = 0x1000;
|
|
|
|
void Require(const bool ok, const std::string& what) {
|
|
if (!ok) throw std::runtime_error("linux load pages: " + what);
|
|
}
|
|
|
|
template <typename T>
|
|
T Read(const Bytes& bytes, const std::uint64_t offset) {
|
|
T value{};
|
|
std::memcpy(&value, bytes.data() + offset, sizeof(T));
|
|
return value;
|
|
}
|
|
|
|
template <typename T>
|
|
void Write(Bytes& bytes, const std::uint64_t offset, const T value) {
|
|
std::memcpy(bytes.data() + offset, &value, sizeof(T));
|
|
}
|
|
|
|
Bytes Fixture(const std::size_t size) {
|
|
Bytes bytes(size);
|
|
bytes[0] = 0x7F; bytes[1] = 'E'; bytes[2] = 'L'; bytes[3] = 'F';
|
|
bytes[4] = 2; bytes[5] = 1; bytes[6] = 1;
|
|
Write<std::uint16_t>(bytes, 16, 3);
|
|
Write<std::uint16_t>(bytes, 18, 62);
|
|
Write<std::uint64_t>(bytes, 24, 0x400);
|
|
Write<std::uint64_t>(bytes, 32, 64);
|
|
Write<std::uint16_t>(bytes, 54, 56);
|
|
Write<std::uint16_t>(bytes, 56, 1);
|
|
bytes[0x400] = 0xC3;
|
|
return bytes;
|
|
}
|
|
|
|
Domain::ProgramHeader Load(const std::uint64_t offset, const std::uint64_t vaddr, const std::uint64_t fileSize, const std::uint64_t memorySize, const std::uint32_t flags, const std::uint64_t alignment = kPage) {
|
|
return {1, flags, offset, vaddr, vaddr, fileSize, memorySize, alignment};
|
|
}
|
|
|
|
Elfpatcher::Linux::LinuxElfPatcher Patcher(const std::optional<std::uint64_t> fixedBase = std::nullopt) {
|
|
const auto byteWriter = std::make_shared<Io::ByteWriter>();
|
|
return Elfpatcher::Linux::LinuxElfPatcher(
|
|
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
|
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(std::make_shared<Elfpatcher::SegmentFilter>(), byteWriter),
|
|
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
|
|
byteWriter,
|
|
fixedBase);
|
|
}
|
|
|
|
std::vector<Domain::ProgramHeader> OutputLoads(const Bytes& output) {
|
|
std::vector<Domain::ProgramHeader> loads;
|
|
const auto table = Read<std::uint64_t>(output, 32);
|
|
const auto count = Read<std::uint16_t>(output, 56);
|
|
for (std::uint16_t index = 0; index < count; ++index) {
|
|
const auto header = table + index * 56u;
|
|
if (Read<std::uint32_t>(output, header) != 1) continue;
|
|
loads.push_back({1, Read<std::uint32_t>(output, header + 4), Read<std::uint64_t>(output, header + 8), Read<std::uint64_t>(output, header + 16),
|
|
Read<std::uint64_t>(output, header + 24), Read<std::uint64_t>(output, header + 32), Read<std::uint64_t>(output, header + 40), Read<std::uint64_t>(output, header + 48)});
|
|
}
|
|
return loads;
|
|
}
|
|
|
|
std::map<std::uint64_t, std::uint8_t> MapLikeLinux(const Bytes& output) {
|
|
std::map<std::uint64_t, std::uint8_t> memory;
|
|
for (const auto& load : OutputLoads(output)) {
|
|
const auto prefix = load.MappedAddress % kPage;
|
|
Require(load.Offset % kPage == prefix, "PT_LOAD offset and address differ within a page");
|
|
const auto fileEnd = load.MappedAddress + load.FileSize;
|
|
const auto filePageEnd = (fileEnd + kPage - 1) & ~(kPage - 1);
|
|
for (auto address = load.MappedAddress - prefix; address < filePageEnd; ++address) {
|
|
const auto offset = load.Offset - prefix + (address - (load.MappedAddress - prefix));
|
|
memory[address] = offset < output.size() ? output[offset] : 0;
|
|
}
|
|
if (load.MemorySize > load.FileSize) {
|
|
const auto memoryPageEnd = (load.MappedAddress + load.MemorySize + kPage - 1) & ~(kPage - 1);
|
|
for (auto address = fileEnd; address < memoryPageEnd; ++address)
|
|
memory[address] = 0;
|
|
}
|
|
}
|
|
return memory;
|
|
}
|
|
|
|
void RequireImage(const Bytes& output, const Bytes& source, const std::vector<Domain::ProgramHeader>& headers, const std::string& what) {
|
|
const auto memory = MapLikeLinux(output);
|
|
for (const auto& load : headers) {
|
|
for (std::uint64_t relative = 0; relative < load.MemorySize; ++relative) {
|
|
const auto address = load.MappedAddress + relative;
|
|
const auto found = memory.find(address);
|
|
Require(found != memory.end(), what + ": address not mapped");
|
|
const std::uint8_t expected = relative < load.FileSize ? source[load.Offset + relative] : 0;
|
|
if (found->second != expected) {
|
|
char text[96];
|
|
std::snprintf(text, sizeof(text), ": byte at 0x%llx reads 0x%02x, not 0x%02x", static_cast<unsigned long long>(address), found->second, expected);
|
|
Require(false, what + text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::uint64_t OutputOffset(const Bytes& output, const std::uint64_t vaddr) {
|
|
for (const auto& load : OutputLoads(output))
|
|
if (load.MappedAddress == vaddr) return load.Offset;
|
|
throw std::runtime_error("linux load pages: no output PT_LOAD at the segment's address");
|
|
}
|
|
|
|
std::uint64_t OutputAlignment(const Bytes& output, const std::uint64_t vaddr) {
|
|
for (const auto& load : OutputLoads(output))
|
|
if (load.MappedAddress == vaddr) return load.Alignment;
|
|
throw std::runtime_error("linux load pages: no output PT_LOAD at the segment's address");
|
|
}
|
|
|
|
void ZeroFillBelowTheNextSegment() {
|
|
auto source = Fixture(0x3000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA});
|
|
std::fill(source.begin() + 0x1100, source.begin() + 0x1c20, std::uint8_t{0x55});
|
|
std::fill(source.begin() + 0x1c20, source.begin() + 0x2000, std::uint8_t{0x77});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1000, 0x1000, 0x100, 0x1c20, 6),
|
|
Load(0x1c20, 0x2c20, 0x3e0, 0x3e0, 0),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "bss sharing a page with the next segment");
|
|
}
|
|
|
|
void DataAndZeroFillBelowTheNextSegment() {
|
|
auto source = Fixture(0x4000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x2400, std::uint8_t{0xAA});
|
|
std::fill(source.begin() + 0x2400, source.begin() + 0x2c20, std::uint8_t{0x55});
|
|
std::fill(source.begin() + 0x2c20, source.begin() + 0x3000, std::uint8_t{0x77});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1000, 0x1000, 0x1400, 0x1c20, 6),
|
|
Load(0x2c20, 0x2c20, 0x3e0, 0x3e0, 0),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "data and bss sharing a page with the next segment");
|
|
}
|
|
|
|
void ZeroFileBytesStayInPlace() {
|
|
auto source = Fixture(0x3000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA});
|
|
std::fill(source.begin() + 0x2c20, source.begin() + 0x3000, std::uint8_t{0x77});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1000, 0x1000, 0x100, 0x1c20, 6),
|
|
Load(0x2c20, 0x2c20, 0x3e0, 0x3e0, 0),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "zero bytes below the next segment");
|
|
Require(OutputOffset(output, 0x2c20) == 0x2c20, "a segment whose page already reads right was moved");
|
|
}
|
|
|
|
void IncongruentSegmentAtPageStart() {
|
|
auto source = Fixture(0x2000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1010, std::uint8_t{0x33});
|
|
std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1010, 0x1000, 0x100, 0x100, 6),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "segment at a page start with an offset inside a page");
|
|
}
|
|
|
|
void IncongruentSegmentInsideASharedPage() {
|
|
auto source = Fixture(0x2000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA});
|
|
std::fill(source.begin() + 0x1200, source.begin() + 0x1210, std::uint8_t{0x33});
|
|
std::fill(source.begin() + 0x1210, source.begin() + 0x1310, std::uint8_t{0x5A});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1000, 0x1000, 0x100, 0x220, 6),
|
|
Load(0x1210, 0x1220, 0x100, 0x100, 4),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "segment in a shared page with an offset at another place in its page");
|
|
}
|
|
|
|
void IncongruentOffsetBelowThePagePrefix() {
|
|
auto source = Fixture(0x1000);
|
|
std::fill(source.begin() + 0x100, source.begin() + 0x180, std::uint8_t{0xBB});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x100, 0x2300, 0x80, 0x80, 4),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
RequireImage(output, source, headers, "segment whose offset lies below its page prefix");
|
|
}
|
|
|
|
void LargeAlignmentMovesToThePage() {
|
|
for (const auto alignment : {std::uint64_t{0x80000000}, std::uint64_t{0x4000}, std::uint64_t{0x8000000000000000}}) {
|
|
auto source = Fixture(0x2000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1010, std::uint8_t{0x33});
|
|
std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1010, 0x1000, 0x100, 0x100, 6, alignment),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
Require(output.size() <= source.size() + 3 * kPage, "output grew to " + std::to_string(output.size()) + " bytes for alignment " + std::to_string(alignment));
|
|
RequireImage(output, source, headers, "incongruent segment with a large alignment");
|
|
Require(OutputAlignment(output, 0x1000) == kPage, "moved segment declares an alignment its offset does not honour");
|
|
}
|
|
}
|
|
|
|
void LargeAlignmentOnASharedPage() {
|
|
auto source = Fixture(0x3000);
|
|
std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA});
|
|
std::fill(source.begin() + 0x1100, source.begin() + 0x1c20, std::uint8_t{0x55});
|
|
std::fill(source.begin() + 0x1c20, source.begin() + 0x2000, std::uint8_t{0x77});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1000, 0x1000, 0x100, 0x1c20, 6),
|
|
Load(0x1c20, 0x2c20, 0x3e0, 0x3e0, 0, std::uint64_t{0x80000000}),
|
|
};
|
|
const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {});
|
|
Require(output.size() <= source.size() + 3 * kPage, "output grew to " + std::to_string(output.size()) + " bytes on the shared page path");
|
|
RequireImage(output, source, headers, "shared page with a large alignment");
|
|
Require(OutputAlignment(output, 0x2c20) == kPage, "moved segment declares an alignment its offset does not honour");
|
|
}
|
|
|
|
void LargeAlignmentAtAFixedBaseIsRefused() {
|
|
for (const auto alignment : {std::uint64_t{0x80000000}, std::uint64_t{0x8000000000000000}}) {
|
|
auto source = Fixture(0x2000);
|
|
std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA});
|
|
const std::vector<Domain::ProgramHeader> headers{
|
|
Load(0x400, 0x400, 0x10, 0x10, 5),
|
|
Load(0x1010, 0x1000, 0x100, 0x100, 6, alignment),
|
|
};
|
|
std::string message;
|
|
try {
|
|
Patcher(0x400000).Patch(source, headers, {}, 0, "$ORIGIN/libs", false, false, {});
|
|
} catch (const std::exception& error) {
|
|
message = error.what();
|
|
}
|
|
Require(message.find("is not a multiple of the PT_LOAD alignment") != std::string::npos, "fixed base accepted an alignment it is not a multiple of: " + message);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
ZeroFillBelowTheNextSegment();
|
|
DataAndZeroFillBelowTheNextSegment();
|
|
ZeroFileBytesStayInPlace();
|
|
IncongruentSegmentAtPageStart();
|
|
IncongruentSegmentInsideASharedPage();
|
|
IncongruentOffsetBelowThePagePrefix();
|
|
LargeAlignmentMovesToThePage();
|
|
LargeAlignmentOnASharedPage();
|
|
LargeAlignmentAtAFixedBaseIsRefused();
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|