274 lines
14 KiB
C++
274 lines
14 KiB
C++
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
|
#include <elfpatcher/general/ElfConstants.hpp>
|
|
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
|
|
#include <codegen/x86/StubBodyBuilder.hpp>
|
|
#include <io/BufferUtils.hpp>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <span>
|
|
|
|
namespace Elfpatcher {
|
|
|
|
std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector<std::string>& dependencies, const std::string& runPath) const {
|
|
auto bytes = image.Bytes;
|
|
std::vector<Domain::ProgramHeader> headers;
|
|
std::uint64_t end = 0;
|
|
for (auto header : image.Headers) {
|
|
if (header.Type != 1 && header.Type != 7 && header.Type != 0x6474e550 && header.Type != PT_GNU_STACK) continue;
|
|
if (header.Type == 1) {
|
|
end = std::max(end, header.MappedAddress + header.MemorySize);
|
|
if (header.Flags == 0) continue;
|
|
header.Flags |= 4;
|
|
}
|
|
headers.push_back(header);
|
|
}
|
|
if (std::none_of(headers.begin(), headers.end(), [&](const auto& header) { return header.Type == PT_GNU_STACK; }))
|
|
headers.push_back({PT_GNU_STACK, PF_R | PF_W, 0, 0, 0, 0, 0, 16});
|
|
if (end > std::numeric_limits<std::uint64_t>::max() - 0x4000) throw Domain::RelinkerException("Guest virtual address overflow");
|
|
Io::AlignBuffer(bytes, 0x4000);
|
|
const auto extraOffset = bytes.size();
|
|
const auto extraAddress = Io::AlignUp64(end, 0x4000);
|
|
const auto address = [&] { return extraAddress + bytes.size() - extraOffset; };
|
|
const bool passesModuleArgs = image.Init != 0;
|
|
std::uint64_t argsSlot = 0;
|
|
std::uint64_t resultSlot = 0;
|
|
if (passesModuleArgs) {
|
|
Io::AlignBuffer(bytes, 8);
|
|
argsSlot = address();
|
|
bytes.insert(bytes.end(), 8, 0);
|
|
resultSlot = address();
|
|
bytes.insert(bytes.end(), 8, 0);
|
|
}
|
|
auto strings = image.Dynamic.DynStrData;
|
|
auto symbols = image.Dynamic.DynSymData;
|
|
constexpr std::uint64_t kGlobDat = 6;
|
|
const auto addString = [&](const std::string& value) {
|
|
if (strings.size() > std::numeric_limits<std::uint32_t>::max()) throw Domain::RelinkerException("Guest string table too large");
|
|
const auto offset = static_cast<std::uint32_t>(strings.size());
|
|
Io::AppendString(strings, value);
|
|
return offset;
|
|
};
|
|
bool needsTlsResolver = false;
|
|
for (std::size_t index = 1; index < image.Symbols.size(); ++index) {
|
|
const auto& symbol = image.Symbols[index];
|
|
if (image.UsePlatformTlsResolver && symbol.Section == 0 && symbol.Name == "vNe1w4diLCs") {
|
|
Io::WriteU32(symbols, index * 24, addString("__tls_get_addr"));
|
|
needsTlsResolver = true;
|
|
}
|
|
if (symbol.Section == 0 && (symbol.Info >> 4) == 2) symbols[index * 24 + 4] = static_cast<std::uint8_t>(0x10 | (symbol.Info & 15));
|
|
}
|
|
std::vector<std::uint64_t> needed;
|
|
for (const auto& dependency : dependencies) needed.push_back(addString(dependency));
|
|
if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2"));
|
|
const auto addUndefinedImport = [&](const std::string& name) {
|
|
const auto nameOffset = addString(name);
|
|
const auto entry = symbols.size();
|
|
symbols.resize(entry + 24, 0);
|
|
Io::WriteU32(symbols, entry, nameOffset);
|
|
symbols[entry + 4] = 0x12;
|
|
Io::WriteU16(symbols, entry + 6, 0);
|
|
return entry / 24;
|
|
};
|
|
auto relaData = image.Dynamic.RelaData;
|
|
std::uint64_t argsImport = 0;
|
|
std::uint64_t resultImport = 0;
|
|
const auto addGlobDat = [&](std::uint64_t slot, std::uint64_t symbol) {
|
|
const auto entry = relaData.size();
|
|
relaData.resize(entry + 24, 0);
|
|
Io::WriteU64(relaData, entry, slot);
|
|
Io::WriteU64(relaData, entry + 8, (symbol << 32) | kGlobDat);
|
|
};
|
|
if (passesModuleArgs) {
|
|
argsImport = addUndefinedImport("__aps5_get_pending_module_args_nid_no_patch");
|
|
resultImport = addUndefinedImport("__aps5_set_module_init_result_nid_no_patch");
|
|
addGlobDat(argsSlot, argsImport);
|
|
addGlobDat(resultSlot, resultImport);
|
|
}
|
|
const auto soname = addString(image.OutputName);
|
|
const auto search = addString(runPath);
|
|
const auto strAddress = address();
|
|
bytes.insert(bytes.end(), strings.begin(), strings.end());
|
|
Io::AlignBuffer(bytes, 8);
|
|
const auto symAddress = address();
|
|
bytes.insert(bytes.end(), symbols.begin(), symbols.end());
|
|
const auto hashAddress = address();
|
|
const auto count = static_cast<std::uint32_t>(symbols.size() / 24);
|
|
const auto bucketCount = std::max<std::uint32_t>(count, 1);
|
|
std::vector<std::uint32_t> buckets(bucketCount), chains(count);
|
|
for (std::uint32_t index = count; index-- > 1;) {
|
|
std::uint32_t hash = 0;
|
|
for (auto offset = Io::ReadU32(symbols, index * 24); offset < strings.size() && strings[offset] != 0; ++offset) {
|
|
hash = (hash << 4u) + strings[offset];
|
|
hash = (hash ^ ((hash & 0xf0000000u) >> 24u)) & 0x0fffffffu;
|
|
}
|
|
chains[index] = buckets[hash % bucketCount];
|
|
buckets[hash % bucketCount] = index;
|
|
}
|
|
Io::AppendU32(bytes, bucketCount);
|
|
Io::AppendU32(bytes, count);
|
|
for (const auto bucket : buckets) Io::AppendU32(bytes, bucket);
|
|
for (const auto chain : chains) Io::AppendU32(bytes, chain);
|
|
Io::AlignBuffer(bytes, 8);
|
|
const auto relaAddress = address();
|
|
bytes.insert(bytes.end(), relaData.begin(), relaData.end());
|
|
const auto pltAddress = address();
|
|
bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end());
|
|
constexpr std::size_t kArgsDisplacement = 3, kTargetDisplacement = 0x11, kResultDisplacement = 0x19;
|
|
constexpr std::size_t kStubSize = 0x1f;
|
|
const auto stubDisplacement = [](std::uint64_t target, std::uint64_t next) {
|
|
const auto distance = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(next);
|
|
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
|
|
throw Domain::RelinkerException("Guest initializer exceeds relative branch range");
|
|
return static_cast<std::uint32_t>(static_cast<std::int32_t>(distance));
|
|
};
|
|
const auto lifecycle = [&](std::uint64_t target) {
|
|
if (target == 0) return std::uint64_t{};
|
|
const auto start = address();
|
|
std::vector<std::uint8_t> stub(kStubSize, 0xc3);
|
|
stub[0x00] = 0x50;
|
|
stub[0x01] = 0xff; stub[0x02] = 0x15;
|
|
Io::WriteU32(stub, kArgsDisplacement, stubDisplacement(argsSlot, start + 7));
|
|
stub[0x07] = 0x48; stub[0x08] = 0x8b; stub[0x09] = 0x38;
|
|
stub[0x0a] = 0x48; stub[0x0b] = 0x8b; stub[0x0c] = 0x70; stub[0x0d] = 0x08;
|
|
stub[0x0e] = 0x31; stub[0x0f] = 0xd2;
|
|
stub[0x10] = 0xe8;
|
|
Io::WriteU32(stub, kTargetDisplacement, stubDisplacement(target, start + 0x15));
|
|
stub[0x15] = 0x89; stub[0x16] = 0xc7;
|
|
stub[0x17] = 0xff; stub[0x18] = 0x15;
|
|
Io::WriteU32(stub, kResultDisplacement, stubDisplacement(resultSlot, start + 0x1d));
|
|
stub[0x1d] = 0x58;
|
|
stub[0x1e] = 0xc3;
|
|
bytes.insert(bytes.end(), stub.begin(), stub.end());
|
|
return start;
|
|
};
|
|
const auto callThrough = [&](std::uint64_t target) {
|
|
if (target == 0) return std::uint64_t{};
|
|
const auto start = address();
|
|
const auto distance = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(start + 5);
|
|
if (distance < std::numeric_limits<std::int32_t>::min() || distance > std::numeric_limits<std::int32_t>::max())
|
|
throw Domain::RelinkerException("Guest finaliser exceeds relative branch range");
|
|
bytes.push_back(0xe9);
|
|
Io::AppendU32(bytes, static_cast<std::uint32_t>(distance));
|
|
return start;
|
|
};
|
|
const auto init = lifecycle(image.Init);
|
|
const auto fini = callThrough(image.Fini);
|
|
Io::AlignBuffer(bytes, 8);
|
|
const auto dynamicOffset = bytes.size();
|
|
const auto dynamicAddress = address();
|
|
const auto tag = [&](std::uint64_t key, std::uint64_t value) { Io::AppendU64(bytes, key); Io::AppendU64(bytes, value); };
|
|
for (const auto offset : needed) tag(1, offset);
|
|
tag(14, soname);
|
|
tag(29, search);
|
|
tag(4, hashAddress);
|
|
tag(5, strAddress);
|
|
tag(10, strings.size());
|
|
tag(6, symAddress);
|
|
tag(11, 24);
|
|
if (!relaData.empty()) {
|
|
tag(7, relaAddress);
|
|
tag(8, relaData.size());
|
|
tag(9, 24);
|
|
}
|
|
if (!image.Dynamic.RelaPltData.empty()) {
|
|
tag(23, pltAddress);
|
|
tag(2, image.Dynamic.RelaPltData.size());
|
|
tag(20, 7);
|
|
tag(3, image.Got);
|
|
}
|
|
if (!image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); }
|
|
if (!image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); }
|
|
if (init != 0) tag(12, init);
|
|
if (fini != 0) tag(13, fini);
|
|
tag(30, 8);
|
|
tag(0, 0);
|
|
const auto dynamicSize = bytes.size() - dynamicOffset;
|
|
using namespace Codegen::Amd64OnlySubstitutionTable;
|
|
const auto checkedAdd = [](const std::uint64_t left, const std::uint64_t right, const std::uint64_t offset) {
|
|
if (right > std::numeric_limits<std::uint64_t>::max() - left)
|
|
throw Domain::RelinkerException("AMD-only guest stub address overflow", offset);
|
|
return left + right;
|
|
};
|
|
const auto displacement = [](const std::uint64_t target, const std::uint64_t next, const std::uint64_t offset) {
|
|
if (target >= next) {
|
|
const auto distance = target - next;
|
|
if (distance > static_cast<std::uint64_t>(std::numeric_limits<std::int32_t>::max()))
|
|
throw Domain::RelinkerException("AMD-only guest stub exceeds rel32 range", offset);
|
|
return static_cast<std::int32_t>(distance);
|
|
}
|
|
const auto distance = next - target;
|
|
if (distance > static_cast<std::uint64_t>(std::numeric_limits<std::int32_t>::max()) + 1)
|
|
throw Domain::RelinkerException("AMD-only guest stub exceeds rel32 range", offset);
|
|
return static_cast<std::int32_t>(-static_cast<std::int64_t>(distance));
|
|
};
|
|
for (const auto& site : image.Trampolines) {
|
|
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 guest trampoline site", site.Offset);
|
|
if (site.Offset > image.Bytes.size() || site.Length > image.Bytes.size() - site.Offset)
|
|
throw Domain::RelinkerException("AMD-only guest instruction is outside the original image", site.Offset);
|
|
const auto mapped = std::any_of(image.Headers.begin(), image.Headers.end(), [&](const auto& header) {
|
|
if (header.Type != 1 || (header.Flags & 1) == 0 ||
|
|
site.Address < header.MappedAddress || site.Offset < header.Offset)
|
|
return false;
|
|
const auto addressDelta = site.Address - header.MappedAddress;
|
|
const auto offsetDelta = site.Offset - header.Offset;
|
|
return addressDelta == offsetDelta && addressDelta <= header.FileSize &&
|
|
site.Length <= header.FileSize - addressDelta;
|
|
});
|
|
if (!mapped)
|
|
throw Domain::RelinkerException("AMD-only guest instruction is outside an executable segment", site.Offset);
|
|
if (!std::equal(site.OriginalBytes.begin(), site.OriginalBytes.end(),
|
|
bytes.begin() + static_cast<std::ptrdiff_t>(site.Offset)))
|
|
throw Domain::RelinkerException("AMD-only guest site bytes changed before patching", site.Offset);
|
|
bytes.resize(Io::AlignUp(bytes.size(), kStubAlignment), kTrapFill);
|
|
const auto stubOffset = bytes.size();
|
|
const auto stubAddress = checkedAdd(extraAddress, stubOffset - extraOffset, site.Offset);
|
|
checkedAdd(stubAddress, site.Body.size(), site.Offset);
|
|
const auto returnAddress = checkedAdd(site.Address, site.Length, site.Offset);
|
|
const auto stubReturn = checkedAdd(stubAddress, site.ReturnBranchOffset + kJmpRel32.Size, site.Offset);
|
|
const auto siteNext = checkedAdd(site.Address, kJmpRel32.Size, site.Offset);
|
|
const auto returnDisplacement = displacement(returnAddress, stubReturn, site.Offset);
|
|
const auto siteDisplacement = displacement(stubAddress, siteNext, site.Offset);
|
|
bytes.insert(bytes.end(), site.Body.begin(), site.Body.end());
|
|
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(bytes.data() + stubOffset, site.ReturnBranchOffset), site.Relocations, site.Address, stubAddress, site.Offset);
|
|
Io::WriteU32(bytes, stubOffset + site.ReturnBranchOffset + 1,
|
|
static_cast<std::uint32_t>(returnDisplacement));
|
|
std::fill_n(bytes.begin() + static_cast<std::ptrdiff_t>(site.Offset), site.Length, kNop1.Bytes[0]);
|
|
bytes[static_cast<std::size_t>(site.Offset)] = kJmpRel32.Bytes[0];
|
|
Io::WriteU32(bytes, static_cast<std::size_t>(site.Offset + 1),
|
|
static_cast<std::uint32_t>(siteDisplacement));
|
|
}
|
|
const auto phOffset = bytes.size();
|
|
const auto phCount = headers.size() + 2;
|
|
if (phCount > std::numeric_limits<std::uint16_t>::max()) throw Domain::RelinkerException("Too many guest program headers");
|
|
const auto extraSize = bytes.size() + phCount * 56 - extraOffset;
|
|
headers.push_back({1, 7, extraOffset, extraAddress, extraAddress, extraSize, extraSize, 0x4000});
|
|
headers.push_back({2, 6, dynamicOffset, dynamicAddress, dynamicAddress, dynamicSize, dynamicSize, 8});
|
|
for (const auto& header : headers) {
|
|
Io::AppendU32(bytes, header.Type);
|
|
Io::AppendU32(bytes, header.Flags);
|
|
Io::AppendU64(bytes, header.Offset);
|
|
Io::AppendU64(bytes, header.MappedAddress);
|
|
Io::AppendU64(bytes, header.PhysicalAddress);
|
|
Io::AppendU64(bytes, header.FileSize);
|
|
Io::AppendU64(bytes, header.MemorySize);
|
|
Io::AppendU64(bytes, header.Alignment);
|
|
}
|
|
bytes[7] = 0;
|
|
bytes[8] = 0;
|
|
Io::WriteU16(bytes, 16, 3);
|
|
Io::WriteU64(bytes, 24, 0);
|
|
Io::WriteU64(bytes, 32, phOffset);
|
|
Io::WriteU64(bytes, 40, 0);
|
|
Io::WriteU16(bytes, 56, static_cast<std::uint16_t>(phCount));
|
|
Io::WriteU16(bytes, 58, 0);
|
|
Io::WriteU16(bytes, 60, 0);
|
|
Io::WriteU16(bytes, 62, 0);
|
|
return bytes;
|
|
}
|
|
|
|
}
|