Files
AnyPS5/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp
T
Ishmam Ahmed 78634fb0a9 feat(relinker): support --trace-imports for Linux output
The Linux entry stub now passes each traced import through libc.prx's
TraceImport_nid_no_patch before it calls the guest entry, and stores the
returned thunk in the import's slot. The same imports are traced as on
Windows: functions bound through a PLT slot or a GOT slot, without an
addend, and not weak. TraceImport_nid_no_patch is bound eagerly through
an added GLOB_DAT relocation, so a libc.prx without it fails at load.
Each slot's page is made writable with mprotect first, so slots inside a
PT_GNU_RELRO range can be replaced.

--trace-imports now requires libc.prx among the libraries on Linux too,
and is refused with --macos.
2026-10-19 22:56:03 +01:00

549 lines
28 KiB
C++

#include <elfpatcher/linux/LinuxElfPatcher.hpp>
#include <elfpatcher/general/ElfConstants.hpp>
#include <elfpatcher/general/ProgramHeaderLayoutRequest.hpp>
#include <elfpatcher/general/SectionHeaderTableRequest.hpp>
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
#include <codegen/x86/StubBodyBuilder.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <limits>
#include <map>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
namespace Elfpatcher::Linux {
namespace {
constexpr std::uint64_t kLinuxUserAddressEnd = 0x800000000000;
std::string Hex(const std::uint64_t value) {
std::ostringstream text;
text << "0x" << std::hex << value;
return text.str();
}
std::uint64_t Rebased(const std::uint64_t base, const std::uint64_t address, const std::string& what) {
if (base > kLinuxUserAddressEnd || address > kLinuxUserAddressEnd - base)
throw Domain::RelinkerException(what + " " + Hex(address) + " lies outside the Linux user address space at fixed base " + Hex(base));
return base + address;
}
void RequireWholeEntries(const std::vector<std::uint8_t>& table, const std::uint64_t entrySize, const std::string& what) {
if (table.size() % entrySize != 0)
throw Domain::RelinkerException(what + " is not a whole number of entries");
}
void RequireOnlyDependencies(const std::vector<std::uint8_t>& entries) {
RequireWholeEntries(entries, kDynEntrySize, "Executable dependency table");
for (std::size_t entry = 0; entry < entries.size(); entry += kDynEntrySize) {
const auto tag = static_cast<std::int64_t>(Io::ReadU64(entries, entry));
if (tag != DT_NEEDED)
throw Domain::RelinkerException("Dynamic tag " + Hex(static_cast<std::uint64_t>(tag)) + " cannot be placed at a fixed Linux base");
}
}
std::vector<std::uint8_t> RebasedSymbols(const std::vector<std::uint8_t>& table, const std::uint64_t base) {
RequireWholeEntries(table, kSymEntrySize, "Dynamic symbol table");
auto rebased = table;
for (std::size_t entry = 0; entry < rebased.size(); entry += kSymEntrySize) {
const auto section = Io::ReadU16(rebased, entry + 6);
if (section == SHN_UNDEF || section == SHN_ABS || (rebased[entry + 4] & 0xf) == STT_TLS)
continue;
if (section >= SHN_LORESERVE)
throw Domain::RelinkerException("Symbol section index " + Hex(section) + " cannot be placed at a fixed Linux base");
Io::WriteU64(rebased, entry + 8, Rebased(base, Io::ReadU64(rebased, entry + 8), "Symbol"));
}
return rebased;
}
std::vector<std::uint8_t> RebasedRelocations(const std::vector<std::uint8_t>& table, const std::uint64_t base) {
RequireWholeEntries(table, kRelaEntrySize, "Relocation table");
auto rebased = table;
for (std::size_t entry = 0; entry < rebased.size(); entry += kRelaEntrySize) {
const auto type = static_cast<std::uint32_t>(Io::ReadU64(rebased, entry + 8));
switch (type) {
case R_X86_64_NONE:
continue;
case R_X86_64_RELATIVE:
case R_X86_64_IRELATIVE: {
const auto addend = static_cast<std::int64_t>(Io::ReadU64(rebased, entry + 16));
if (addend < 0)
throw Domain::RelinkerException("Relocation target " + std::to_string(addend) + " lies below the image at a fixed Linux base");
Io::WriteU64(rebased, entry + 16, Rebased(base, static_cast<std::uint64_t>(addend), "Relocation target"));
break;
}
case R_X86_64_64:
case R_X86_64_PC32:
case R_X86_64_COPY:
case R_X86_64_GLOB_DAT:
case R_X86_64_JUMP_SLOT:
case R_X86_64_32:
case R_X86_64_32S:
case R_X86_64_DTPMOD64:
case R_X86_64_DTPOFF64:
case R_X86_64_TPOFF64:
break;
default:
throw Domain::RelinkerException("Relocation type " + std::to_string(type) + " cannot be placed at a fixed Linux base");
}
Io::WriteU64(rebased, entry, Rebased(base, Io::ReadU64(rebased, entry), "Relocation offset"));
}
return rebased;
}
void RequireFileRange(const std::vector<std::uint8_t>& buf, const Domain::ProgramHeader& load) {
if (load.Offset > buf.size() || load.FileSize > buf.size() - load.Offset)
throw Domain::RelinkerException("PT_LOAD at " + Hex(load.MappedAddress) + " has file bytes outside the input");
}
void SeparateSharedLoadPages(std::vector<std::uint8_t>& buf, std::vector<Domain::ProgramHeader>& headers) {
std::vector<std::size_t> loads;
for (std::size_t index = 0; index < headers.size(); ++index) {
if (headers[index].Type == PT_LOAD && headers[index].MemorySize != 0) {
RequireFileRange(buf, headers[index]);
loads.push_back(index);
}
}
std::stable_sort(loads.begin(), loads.end(), [&headers](const std::size_t left, const std::size_t right) {
return headers[left].MappedAddress < headers[right].MappedAddress;
});
for (std::size_t position = 0; position < loads.size(); ++position) {
auto& load = headers[loads[position]];
const auto prefix = load.MappedAddress % kDefaultLoadAlignment;
if (prefix == 0 || load.FileSize == 0 || load.Offset % kDefaultLoadAlignment != prefix)
continue;
const auto pageStart = load.MappedAddress - prefix;
std::vector<std::uint8_t> image(static_cast<std::size_t>(prefix), 0);
std::vector<bool> covered(static_cast<std::size_t>(prefix), false);
for (std::size_t below = 0; below < position; ++below) {
const auto& other = headers[loads[below]];
const auto end = std::min(other.MappedAddress + other.MemorySize, load.MappedAddress);
for (auto address = std::max(other.MappedAddress, pageStart); address < end; ++address) {
const auto relative = address - other.MappedAddress;
covered[static_cast<std::size_t>(address - pageStart)] = true;
image[static_cast<std::size_t>(address - pageStart)] = relative < other.FileSize ? buf[static_cast<std::size_t>(other.Offset + relative)] : 0;
}
}
const auto mapped = load.Offset - prefix;
bool differs = false;
for (std::size_t index = 0; index < image.size() && !differs; ++index)
differs = covered[index] && buf[static_cast<std::size_t>(mapped) + index] != image[index];
if (!differs)
continue;
const auto alignment = std::max<std::uint64_t>(load.Alignment, kDefaultLoadAlignment);
while ((buf.size() + prefix) % alignment != load.MappedAddress % alignment)
buf.push_back(0);
buf.insert(buf.end(), image.begin(), image.end());
const auto offset = static_cast<std::uint64_t>(buf.size());
buf.resize(static_cast<std::size_t>(offset + load.FileSize));
std::copy_n(buf.begin() + static_cast<std::ptrdiff_t>(load.Offset), static_cast<std::size_t>(load.FileSize), buf.begin() + static_cast<std::ptrdiff_t>(offset));
load.Offset = offset;
}
}
struct TracedImport {
std::uint64_t Slot;
std::string Name;
std::string Library;
};
std::string ReadDynamicString(const std::vector<std::uint8_t>& strings, const std::uint32_t offset) {
if (offset >= strings.size())
throw Domain::RelinkerException("Import symbol name lies outside the dynamic string table");
std::string value;
for (std::size_t index = offset; index < strings.size() && strings[index] != 0; ++index)
value.push_back(static_cast<char>(strings[index]));
return value;
}
std::vector<TracedImport> TracedImports(const Domain::SysVDynamicSection& dynSection) {
std::vector<TracedImport> imports;
const auto collect = [&](const std::vector<std::uint8_t>& table) {
RequireWholeEntries(table, kRelaEntrySize, "Relocation table");
for (std::size_t entry = 0; entry < table.size(); entry += kRelaEntrySize) {
const auto info = Io::ReadU64(table, entry + 8);
const auto type = static_cast<std::uint32_t>(info);
const auto symbol = info >> 32;
const auto target = Io::ReadU64(table, entry);
if ((type != R_X86_64_GLOB_DAT && type != R_X86_64_JUMP_SLOT) || symbol == 0 || Io::ReadU64(table, entry + 16) != 0)
continue;
if (type == R_X86_64_GLOB_DAT && !dynSection.FunctionImports.contains(target))
continue;
if (symbol >= dynSection.DynSymData.size() / kSymEntrySize)
throw Domain::RelinkerException("Invalid import symbol index", target);
const auto symbolOffset = static_cast<std::size_t>(symbol * kSymEntrySize);
if (Io::ReadU16(dynSection.DynSymData, symbolOffset + 6) != SHN_UNDEF || dynSection.DynSymData[symbolOffset + 4] >> 4 == STB_WEAK)
continue;
auto name = ReadDynamicString(dynSection.DynStrData, Io::ReadU32(dynSection.DynSymData, symbolOffset));
if (name.empty())
continue;
const auto library = dynSection.ImportModules.find(target);
imports.push_back({target, std::move(name), library == dynSection.ImportModules.end() ? std::string{} : library->second});
}
};
collect(dynSection.RelaData);
collect(dynSection.RelaPltData);
return imports;
}
std::vector<std::uint8_t> TracePrologue(const std::uint64_t vaddr, const std::uint64_t table, const std::uint64_t tableEnd, const std::uint64_t function) {
std::vector<std::uint8_t> code = {0x52, 0x53, 0x41, 0x54, 0x48, 0x83, 0xec, 0x08};
const auto rip = [&](std::initializer_list<std::uint8_t> opcode, const std::uint64_t target) {
code.insert(code.end(), opcode);
const auto displacement = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(vaddr + code.size() + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Import trace table exceeds rel32 range");
Io::AppendU32(code, static_cast<std::uint32_t>(displacement));
};
rip({0x48, 0x8d, 0x1d}, table);
rip({0x4c, 0x8d, 0x25}, tableEnd);
const auto loop = code.size();
code.insert(code.end(), {0x4c, 0x39, 0xe3, 0x73, 0x00});
const auto exit = code.size();
code.insert(code.end(), {0x48, 0x8b, 0x03, 0x48, 0x8b, 0x3c, 0x03, 0x48, 0x8b, 0x73, 0x08, 0x48, 0x01, 0xde, 0x48, 0x8b, 0x53, 0x10, 0x48, 0x01, 0xda});
rip({0xff, 0x15}, function);
code.insert(code.end(), {0x48, 0x8b, 0x3b, 0x48, 0x01, 0xdf, 0x48, 0x81, 0xe7, 0x00, 0xf0, 0xff, 0xff, 0xbe, 0x00, 0x10, 0x00, 0x00, 0xba, 0x07, 0x00, 0x00, 0x00, 0x50, 0xb8, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x58});
code.insert(code.end(), {0x48, 0x8b, 0x0b, 0x48, 0x89, 0x04, 0x0b, 0x48, 0x83, 0xc3, 0x18, 0xeb});
code.push_back(static_cast<std::uint8_t>(static_cast<std::int8_t>(static_cast<std::int64_t>(loop) - static_cast<std::int64_t>(code.size() + 1))));
code[exit - 1] = static_cast<std::uint8_t>(code.size() - exit);
code.insert(code.end(), {0x48, 0x83, 0xc4, 0x08, 0x41, 0x5c, 0x5b, 0x5a});
return code;
}
void RebaseProgramHeaders(std::vector<std::uint8_t>& buf, const std::uint64_t tableOffset, const std::uint16_t entrySize, const std::uint16_t count, const std::uint64_t base) {
for (std::uint16_t index = 0; index < count; ++index) {
const auto header = static_cast<std::size_t>(tableOffset + static_cast<std::uint64_t>(index) * entrySize);
const auto type = Io::ReadU32(buf, header + kPhdrTypeOffset);
if (type == PT_NULL || type == PT_GNU_STACK)
continue;
const auto address = Rebased(base, Io::ReadU64(buf, header + kPhdrVaddrOffset), "Program header address");
const auto physical = Rebased(base, Io::ReadU64(buf, header + kPhdrPaddrOffset), "Program header physical address");
if (type == PT_LOAD) {
const auto alignment = Io::ReadU64(buf, header + kPhdrAlignOffset);
if (alignment > 1 && base % alignment != 0)
throw Domain::RelinkerException("Fixed Linux base " + Hex(base) + " is not a multiple of the PT_LOAD alignment " + Hex(alignment));
if (Io::ReadU64(buf, header + kPhdrMemSizeOffset) > kLinuxUserAddressEnd - address)
throw Domain::RelinkerException("PT_LOAD at " + Hex(address) + " ends outside the Linux user address space");
}
Io::WriteU64(buf, header + kPhdrVaddrOffset, address);
Io::WriteU64(buf, header + kPhdrPaddrOffset, physical);
}
}
}
LinuxElfPatcher::LinuxElfPatcher(
std::shared_ptr<IEntryStubBuilder> entryStubBuilder,
std::shared_ptr<IProgramHeaderLayoutBuilder> programHeaderLayoutBuilder,
std::shared_ptr<ISectionHeaderTableBuilder> sectionHeaderTableBuilder,
std::shared_ptr<Io::IByteWriter> byteWriter,
std::optional<std::uint64_t> fixedBase,
bool traceImports
)
: _entryStubBuilder(std::move(entryStubBuilder))
, _programHeaderLayoutBuilder(std::move(programHeaderLayoutBuilder))
, _sectionHeaderTableBuilder(std::move(sectionHeaderTableBuilder))
, _byteWriter(std::move(byteWriter))
, _fixedBase(fixedBase)
, _traceImports(traceImports)
{
}
void LinuxElfPatcher::_appendDynEntry(std::vector<std::uint8_t>& buf, std::int64_t tag, std::uint64_t val) const {
_byteWriter->AppendI64(buf, tag);
_byteWriter->AppendU64(buf, val);
}
void LinuxElfPatcher::_appendTrampoline(
std::vector<std::uint8_t>& buf,
const Codegen::TrampolineSite& site,
const std::uint64_t extraBlockOffset,
const std::function<std::uint64_t(std::uint64_t)>& vaddrOfExtraBlockOffset) const
{
using namespace Codegen::Amd64OnlySubstitutionTable;
if (site.Length < kJmpRel32.Size || site.OriginalBytes.size() != site.Length || site.Body.size() < kJmpRel32.Size || site.ReturnBranchOffset > site.Body.size() - kJmpRel32.Size || site.Body[site.ReturnBranchOffset] != kJmpRel32.Bytes[0])
throw Domain::RelinkerException("Invalid AMD-only trampoline site", site.Offset);
if (site.Offset > extraBlockOffset || site.Length > extraBlockOffset - site.Offset)
throw Domain::RelinkerException("AMD-only instruction is outside the original image", site.Offset);
if (!std::equal(site.OriginalBytes.begin(), site.OriginalBytes.end(), buf.begin() + static_cast<std::ptrdiff_t>(site.Offset)))
throw Domain::RelinkerException("AMD-only site bytes changed before patching", site.Offset);
while (buf.size() % kStubAlignment != 0)
buf.push_back(kTrapFill);
const auto bodyOff = static_cast<std::uint64_t>(buf.size());
const auto bodyVaddr = vaddrOfExtraBlockOffset(bodyOff);
for (const std::uint8_t b : site.Body)
buf.push_back(b);
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(buf.data() + bodyOff, site.ReturnBranchOffset), site.Relocations, site.Address, bodyVaddr, site.Offset);
const auto inRange = [](const std::int64_t displacement) {
return displacement >= std::numeric_limits<std::int32_t>::min() && displacement <= std::numeric_limits<std::int32_t>::max();
};
const auto returnDisplacement = static_cast<std::int64_t>(site.Address + site.Length) - static_cast<std::int64_t>(bodyVaddr + site.ReturnBranchOffset + kJmpRel32.Size);
const auto jumpDisplacement = static_cast<std::int64_t>(bodyVaddr) - static_cast<std::int64_t>(site.Address + kJmpRel32.Size);
if (!inRange(returnDisplacement) || !inRange(jumpDisplacement))
throw Domain::RelinkerException("AMD-only stub exceeds rel32 range", site.Offset);
_byteWriter->WriteU32(buf, static_cast<std::size_t>(bodyOff + site.ReturnBranchOffset + 1), static_cast<std::uint32_t>(returnDisplacement));
std::fill_n(buf.begin() + static_cast<std::ptrdiff_t>(site.Offset), site.Length, kNop1.Bytes[0]);
buf[static_cast<std::size_t>(site.Offset)] = kJmpRel32.Bytes[0];
_byteWriter->WriteU32(buf, static_cast<std::size_t>(site.Offset + 1), static_cast<std::uint32_t>(jumpDisplacement));
}
std::vector<std::uint8_t> LinuxElfPatcher::Patch(
std::vector<std::uint8_t> sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynSection,
const std::uint64_t originalPltGotVaddr,
const std::string& runPath,
const bool lazyBinding,
const bool dependencyDiagnostics,
const std::vector<Codegen::TrampolineSite>& trampolines)
{
if (dependencyDiagnostics)
throw Domain::RelinkerException("Linux target does not support --windows-diagnostics");
if (_fixedBase && lazyBinding)
throw Domain::RelinkerException("A fixed Linux base requires eager binding; --lazy-binding is incompatible");
if (_traceImports && lazyBinding)
throw Domain::RelinkerException("--trace-imports conflicts with --lazy-binding");
if (_traceImports && std::find(dynSection.NeededLibraries.begin(), dynSection.NeededLibraries.end(), "libc.prx") == dynSection.NeededLibraries.end())
throw Domain::RelinkerException("--trace-imports requires libc.prx among the libraries");
if (_fixedBase)
RequireOnlyDependencies(dynSection.DynamicSegmentData);
const auto traced = _traceImports ? TracedImports(dynSection) : std::vector<TracedImport>{};
const std::uint64_t base = _fixedBase.value_or(0);
const auto address = [this, base](const std::uint64_t vaddr) {
return _fixedBase ? Rebased(base, vaddr, "Address") : vaddr;
};
const auto table = [this, base](const std::vector<std::uint8_t>& data, const bool relocations) {
if (!_fixedBase) return data;
return relocations ? RebasedRelocations(data, base) : RebasedSymbols(data, base);
};
std::vector<std::uint8_t> buf = std::move(sourceElf);
const std::uint32_t type = _fixedBase ? ET_EXEC : ET_DYN;
buf[kEhdrOsAbiOffset] = 0;
buf[kEhdrAbiVersionOffset] = 0;
buf[kEhdrTypeOffset] = static_cast<std::uint8_t>(type & 0xFF);
buf[kEhdrTypeOffset + 1] = static_cast<std::uint8_t>((type >> 8) & 0xFF);
_byteWriter->WriteU64(buf, kEhdrShOffOffset, 0);
_byteWriter->WriteU16(buf, kEhdrShNumOffset, 0);
_byteWriter->WriteU16(buf, kEhdrShStrNdxOffset, 0);
const std::uint64_t phOff = *reinterpret_cast<const std::uint64_t*>(buf.data() + kEhdrPhOffOffset);
const std::uint16_t phEntSize = *reinterpret_cast<const std::uint16_t*>(buf.data() + kEhdrPhEntSizeOffset);
const std::uint16_t phNum = *reinterpret_cast<const std::uint16_t*>(buf.data() + kEhdrPhNumOffset);
const auto alignBuf = [](std::vector<std::uint8_t>& b, std::size_t alignment) {
while (b.size() % alignment != 0)
b.push_back(0);
};
const auto dynStrOff = static_cast<std::uint64_t>(buf.size());
for (std::uint8_t b : dynSection.DynStrData)
buf.push_back(b);
const std::uint64_t runPathStrOff = static_cast<std::uint64_t>(buf.size()) - dynStrOff;
for (char c : runPath)
buf.push_back(static_cast<std::uint8_t>(c));
buf.push_back(0);
const std::uint64_t traceSymbolStrOff = static_cast<std::uint64_t>(buf.size()) - dynStrOff;
if (_traceImports) {
for (char c : std::string_view("TraceImport_nid_no_patch"))
buf.push_back(static_cast<std::uint8_t>(c));
buf.push_back(0);
}
const std::uint64_t dynStrSize = static_cast<std::uint64_t>(buf.size()) - dynStrOff;
alignBuf(buf, kDynStrAlignment);
const auto dynSymOff = static_cast<std::uint64_t>(buf.size());
auto symbols = table(dynSection.DynSymData, false);
const auto traceSymbol = symbols.size() / kSymEntrySize;
if (_traceImports) {
Io::AppendU32(symbols, static_cast<std::uint32_t>(traceSymbolStrOff));
symbols.push_back(static_cast<std::uint8_t>((STB_GLOBAL << 4) | STT_FUNC));
symbols.push_back(0);
Io::AppendU16(symbols, SHN_UNDEF);
Io::AppendU64(symbols, 0);
Io::AppendU64(symbols, 0);
}
for (std::uint8_t b : symbols)
buf.push_back(b);
alignBuf(buf, kDynSymAlignment);
const auto hashOff = static_cast<std::uint64_t>(buf.size());
const auto symbolCount = static_cast<std::uint32_t>(symbols.size() / kSymEntrySize);
Io::AppendU32(buf, 1);
Io::AppendU32(buf, symbolCount);
Io::AppendU32(buf, symbolCount > 1 ? 1 : 0);
for (std::uint32_t index = 0; index < symbolCount; ++index)
Io::AppendU32(buf, index != 0 && index + 1 < symbolCount ? index + 1 : 0);
alignBuf(buf, kDynSymAlignment);
const auto relaOff = static_cast<std::uint64_t>(buf.size());
auto relocations = table(dynSection.RelaData, true);
const auto traceRelocation = relocations.size();
if (_traceImports) {
Io::AppendU64(relocations, 0);
Io::AppendU64(relocations, (static_cast<std::uint64_t>(traceSymbol) << 32) | R_X86_64_GLOB_DAT);
Io::AppendU64(relocations, 0);
}
for (std::uint8_t b : relocations)
buf.push_back(b);
alignBuf(buf, kRelaAlignment);
const auto relaPltOff = static_cast<std::uint64_t>(buf.size());
const auto pltRelocations = table(dynSection.RelaPltData, true);
for (std::uint8_t b : pltRelocations)
buf.push_back(b);
alignBuf(buf, kRelaPltAlignment);
const std::uint64_t extraBlockOff = dynStrOff;
const std::uint64_t extraBlockVaddr = _programHeaderLayoutBuilder->ComputeExtraBlockVaddr(originalHeaders, extraBlockOff);
const auto vaddrOfExtraBlockOffset = [extraBlockOff, extraBlockVaddr](std::uint64_t fileOffset) -> std::uint64_t {
if (fileOffset < extraBlockOff)
throw Domain::RelinkerException("Extra block member offset lies before the extra block");
return extraBlockVaddr + (fileOffset - extraBlockOff);
};
std::vector<std::uint8_t> dynSegBuf;
for (std::uint8_t b : dynSection.DynamicSegmentData)
dynSegBuf.push_back(b);
_appendDynEntry(dynSegBuf, DT_STRTAB, address(vaddrOfExtraBlockOffset(dynStrOff)));
_appendDynEntry(dynSegBuf, DT_STRSZ, dynStrSize);
_appendDynEntry(dynSegBuf, DT_SYMTAB, address(vaddrOfExtraBlockOffset(dynSymOff)));
_appendDynEntry(dynSegBuf, DT_SYMENT, kSymEntrySize);
_appendDynEntry(dynSegBuf, DT_HASH, address(vaddrOfExtraBlockOffset(hashOff)));
_appendDynEntry(dynSegBuf, DT_DEBUG, 0);
if (!relocations.empty()) {
_appendDynEntry(dynSegBuf, DT_RELA, address(vaddrOfExtraBlockOffset(relaOff)));
_appendDynEntry(dynSegBuf, DT_RELASZ, relocations.size());
_appendDynEntry(dynSegBuf, DT_RELAENT, kRelaEntrySize);
}
if (!dynSection.RelaPltData.empty()) {
_appendDynEntry(dynSegBuf, DT_JMPREL, address(vaddrOfExtraBlockOffset(relaPltOff)));
_appendDynEntry(dynSegBuf, DT_PLTRELSZ, dynSection.RelaPltData.size());
_appendDynEntry(dynSegBuf, DT_PLTREL, static_cast<std::uint64_t>(DT_RELA));
_appendDynEntry(dynSegBuf, DT_PLTGOT, address(originalPltGotVaddr));
}
if (!lazyBinding)
_appendDynEntry(dynSegBuf, DT_FLAGS, DF_BIND_NOW);
_appendDynEntry(dynSegBuf, DT_RUNPATH, runPathStrOff);
_appendDynEntry(dynSegBuf, DT_NULL, 0);
const auto dynSegOff = static_cast<std::uint64_t>(buf.size());
for (std::uint8_t b : dynSegBuf)
buf.push_back(b);
std::uint64_t traceFunctionVaddr = 0;
std::uint64_t traceTableVaddr = 0;
std::uint64_t traceTableEndVaddr = 0;
std::vector<std::size_t> traceEntries;
if (_traceImports) {
alignBuf(buf, 8);
const auto functionOff = static_cast<std::uint64_t>(buf.size());
traceFunctionVaddr = vaddrOfExtraBlockOffset(functionOff);
Io::AppendU64(buf, 0);
Io::WriteU64(buf, static_cast<std::size_t>(relaOff + traceRelocation), address(traceFunctionVaddr));
traceTableVaddr = vaddrOfExtraBlockOffset(buf.size());
for (std::size_t index = 0; index < traced.size(); ++index) {
traceEntries.push_back(buf.size());
buf.resize(buf.size() + 24);
}
traceTableEndVaddr = vaddrOfExtraBlockOffset(buf.size());
std::map<std::string, std::uint64_t> strings;
const auto addString = [&](const std::string& value) {
if (const auto existing = strings.find(value); existing != strings.end()) return existing->second;
const auto vaddr = vaddrOfExtraBlockOffset(buf.size());
for (char c : value)
buf.push_back(static_cast<std::uint8_t>(c));
buf.push_back(0);
strings.emplace(value, vaddr);
return vaddr;
};
for (std::size_t index = 0; index < traced.size(); ++index) {
const auto entryVaddr = vaddrOfExtraBlockOffset(traceEntries[index]);
const auto relative = [entryVaddr](const std::uint64_t vaddr) { return vaddr - entryVaddr; };
Io::WriteU64(buf, traceEntries[index], relative(traced[index].Slot));
Io::WriteU64(buf, traceEntries[index] + 8, relative(addString(traced[index].Name)));
Io::WriteU64(buf, traceEntries[index] + 16, relative(addString(traced[index].Library)));
}
}
const std::uint64_t realEntryVaddr = *reinterpret_cast<const std::uint64_t*>(buf.data() + kEhdrEntryOffset);
const auto stubOff = static_cast<std::uint64_t>(buf.size());
const auto stubVaddr = vaddrOfExtraBlockOffset(stubOff);
if (_traceImports) {
for (std::uint8_t b : TracePrologue(stubVaddr, traceTableVaddr, traceTableEndVaddr, traceFunctionVaddr))
buf.push_back(b);
}
const auto entryStubVaddr = vaddrOfExtraBlockOffset(buf.size());
for (std::uint8_t b : _entryStubBuilder->BuildEntryStub(address(entryStubVaddr), address(realEntryVaddr)))
buf.push_back(b);
const auto stubSize = static_cast<std::uint64_t>(buf.size()) - stubOff;
_byteWriter->WriteU64(buf, kEhdrEntryOffset, address(stubVaddr));
for (const auto& site : trampolines)
_appendTrampoline(buf, site, extraBlockOff, vaddrOfExtraBlockOffset);
static constexpr char kInterp[] = "/lib64/ld-linux-x86-64.so.2";
const auto interpOff = static_cast<std::uint64_t>(buf.size());
constexpr std::uint64_t interpSize = sizeof(kInterp);
for (char i : kInterp)
buf.push_back(static_cast<std::uint8_t>(i));
std::uint64_t tableOff = phOff;
std::uint16_t tableSlots = phNum;
if (const auto required = _programHeaderLayoutBuilder->RequiredProgramHeaderCount(originalHeaders); required > phNum && required <= std::numeric_limits<std::uint16_t>::max()) {
alignBuf(buf, kPhdrHeaderAlignment);
tableOff = static_cast<std::uint64_t>(buf.size());
tableSlots = static_cast<std::uint16_t>(required);
buf.resize(buf.size() + required * phEntSize);
_byteWriter->WriteU64(buf, kEhdrPhOffOffset, tableOff);
}
const std::uint64_t extraBlockSize = static_cast<std::uint64_t>(buf.size()) - extraBlockOff;
auto headers = originalHeaders;
SeparateSharedLoadPages(buf, headers);
ProgramHeaderLayoutRequest layoutRequest{};
layoutRequest.PhOff = tableOff;
layoutRequest.PhEntSize = phEntSize;
layoutRequest.PhNum = tableSlots;
layoutRequest.OriginalHeaders = headers;
layoutRequest.ExtraBlockOffset = extraBlockOff;
layoutRequest.ExtraBlockVaddr = extraBlockVaddr;
layoutRequest.ExtraBlockSize = extraBlockSize;
layoutRequest.DynamicSegmentOffset = dynSegOff;
layoutRequest.DynamicSegmentSize = dynSegBuf.size();
layoutRequest.InterpOffset = interpOff;
layoutRequest.InterpSize = interpSize;
const std::uint16_t writtenPh = _programHeaderLayoutBuilder->WriteLayout(buf, layoutRequest);
_byteWriter->WriteU16(buf, kEhdrPhNumOffset, writtenPh);
if (_fixedBase)
RebaseProgramHeaders(buf, tableOff, phEntSize, writtenPh, base);
SectionHeaderTableRequest sectionRequest{};
sectionRequest.DynStrOffset = dynStrOff;
sectionRequest.DynStrSize = dynStrSize;
sectionRequest.DynSymOffset = dynSymOff;
sectionRequest.DynSymSize = symbols.size();
sectionRequest.DynamicSegmentOffset = dynSegOff;
sectionRequest.DynamicSegmentSize = dynSegBuf.size();
sectionRequest.StubOffset = stubOff;
sectionRequest.StubSize = stubSize;
_sectionHeaderTableBuilder->WriteTable(buf, sectionRequest);
return buf;
}
}