Merge pull request #4483 from fable-void/pr/relinker-jump-table-targets

fix(relinker): treat jump table targets as branch targets when moving instructions
This commit is contained in:
Dean 2026-10-13 07:28:12 +13:00 committed by GitHub
commit e305bc4e4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 90 additions and 30 deletions

View File

@ -14,7 +14,7 @@ public:
[[nodiscard]] virtual ConvertResult Convert(
std::vector<std::uint8_t> fileBytes,
const std::vector<Domain::ProgramHeader>& codeSegments
const std::vector<Domain::ProgramHeader>& segments
) const = 0;
};

View File

@ -7,6 +7,7 @@
#include <codegen/x86/IAmd64OnlyInstructionMatcher.hpp>
#include <codegen/x86/StubBodyBuilder.hpp>
#include <algorithm>
#include <cstring>
#include <limits>
#include <memory>
#include <optional>
@ -29,6 +30,9 @@ auto _atFileOffset(const Domain::FileByteOffset base, const TOperation& operatio
}
constexpr std::int64_t kShortJumpSize = 2;
constexpr std::uint32_t kLoadSegment = 1;
constexpr std::uint32_t kExecutableSegment = 1;
constexpr std::uint8_t kLea = 0x8D;
constexpr std::uint64_t kIslandSearchBytes = 0x100;
struct Placement {
@ -209,7 +213,7 @@ public:
[[nodiscard]] ConvertResult Convert(
std::vector<std::uint8_t> fileBytes,
const std::vector<Domain::ProgramHeader>& codeSegments
const std::vector<Domain::ProgramHeader>& segments
) const override;
private:
@ -225,30 +229,59 @@ private:
void _convertSegment(
std::vector<std::uint8_t>& fileBytes,
const Domain::ProgramHeader& ph,
const std::vector<Domain::ProgramHeader>& segments,
ConvertResult& result
) const;
[[nodiscard]] static std::set<std::uint64_t> _collectBranchTargets(
const std::vector<std::uint8_t>& seg,
const std::vector<InstructionMatch>& matches,
const Domain::ProgramHeader& ph
const Domain::ProgramHeader& ph,
const std::vector<std::uint8_t>& fileBytes,
const std::vector<Domain::ProgramHeader>& segments
);
};
std::set<std::uint64_t> Amd64OnlyConverter::_collectBranchTargets(
const std::vector<std::uint8_t>& seg,
const std::vector<InstructionMatch>& matches,
const Domain::ProgramHeader& ph
const Domain::ProgramHeader& ph,
const std::vector<std::uint8_t>& fileBytes,
const std::vector<Domain::ProgramHeader>& segments
) {
const auto startsInstruction = [&](const std::uint64_t address) {
const auto offset = address - ph.MappedAddress;
const auto match = std::lower_bound(matches.begin(), matches.end(), offset, [](const InstructionMatch& item, const std::uint64_t value) { return item.Offset < value; });
return match != matches.end() && match->Offset == offset;
};
const X64InstructionDecoder decoder;
std::set<std::uint64_t> targets;
for (const auto& match : matches) {
const auto info = decoder.DecodeInstruction(seg.data() + match.Offset, match.Length);
if (!info.HasBranchTarget || info.HasRipRelativeDisp)
continue;
const auto target = static_cast<std::int64_t>(ph.MappedAddress + match.Offset + info.Length) + info.BranchDisp;
if (target >= 0)
targets.insert(static_cast<std::uint64_t>(target));
const auto next = static_cast<std::int64_t>(ph.MappedAddress + match.Offset + info.Length);
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
const auto target = next + info.BranchDisp;
if (target >= 0)
targets.insert(static_cast<std::uint64_t>(target));
} else if (info.HasRipRelativeDisp && info.Opcode == kLea) {
std::int32_t displacement;
std::memcpy(&displacement, seg.data() + match.Offset + info.RipRelativeDispOffset, sizeof(displacement));
const auto table = static_cast<std::uint64_t>(next + displacement);
for (const auto& data : segments) {
if (data.Type != kLoadSegment || table < data.MappedAddress || table - data.MappedAddress >= data.FileSize)
continue;
if (data.Offset > fileBytes.size() || data.FileSize > fileBytes.size() - data.Offset)
throw CodegenException("Segment exceeds the file", data.Offset);
for (auto entry = table; entry - data.MappedAddress + sizeof(std::int32_t) <= data.FileSize; entry += sizeof(std::int32_t)) {
std::int32_t relative;
std::memcpy(&relative, fileBytes.data() + data.Offset + (entry - data.MappedAddress), sizeof(relative));
const auto target = table + static_cast<std::uint64_t>(static_cast<std::int64_t>(relative));
if (!startsInstruction(target))
break;
targets.insert(target);
}
}
}
}
return targets;
}
@ -256,6 +289,7 @@ std::set<std::uint64_t> Amd64OnlyConverter::_collectBranchTargets(
void Amd64OnlyConverter::_convertSegment(
std::vector<std::uint8_t>& fileBytes,
const Domain::ProgramHeader& ph,
const std::vector<Domain::ProgramHeader>& segments,
ConvertResult& result
) const {
const auto segOffset = static_cast<std::size_t>(ph.Offset);
@ -283,7 +317,7 @@ void Amd64OnlyConverter::_convertSegment(
const bool needsBranchTargets = std::any_of(pending.begin(), pending.end(), [](const Pending& item) {
return item.Substitution.Lowering == Amd64OnlyLowering::Trampoline;
});
const auto branchTargets = needsBranchTargets ? _collectBranchTargets(seg, matches, ph) : std::set<std::uint64_t>{};
const auto branchTargets = needsBranchTargets ? _collectBranchTargets(seg, matches, ph, fileBytes, segments) : std::set<std::uint64_t>{};
std::set<std::size_t> pendingIndices;
for (const auto& item : pending)
@ -395,11 +429,13 @@ void Amd64OnlyConverter::_convertSegment(
ConvertResult Amd64OnlyConverter::Convert(
std::vector<std::uint8_t> fileBytes,
const std::vector<Domain::ProgramHeader>& codeSegments
const std::vector<Domain::ProgramHeader>& segments
) const {
ConvertResult result{};
for (const auto& ph : codeSegments)
_convertSegment(fileBytes, ph, result);
for (const auto& ph : segments) {
if (ph.Type == kLoadSegment && (ph.Flags & kExecutableSegment) != 0)
_convertSegment(fileBytes, ph, segments, result);
}
result.Bytes = std::move(fileBytes);
return result;
}

View File

@ -886,6 +886,43 @@ void converterWideStoreWindows() {
requireFailure([&] { (void)convert(withStore(far, tail)); }, "A 256-bit store whose movable neighbours are all beyond a short jump was kept");
}
void converterJumpTableTargets() {
const auto converter = Codegen::MakeAmd64OnlyConverter(Codegen::Amd64OnlyTarget::Rosetta);
const Bytes leaTable = {0x48, 0x8D, 0x0D, 0xF9, 0x1F, 0x00, 0x00};
const Bytes dispatch = {0x48, 0x63, 0x04, 0x81, 0x48, 0x01, 0xC8, 0xFF, 0xE0, 0x48, 0x83, 0xC1, 0x01, 0xC5, 0xFC, 0x11, 0x07, 0x48, 0x83, 0xC2, 0x01, 0xC3};
const Bytes store = {0xC5, 0xFC, 0x11, 0x07};
const Domain::ProgramHeader code{1, 5, 0x200, 0x1000, 0, 29, 29, 16};
const Domain::ProgramHeader data{1, 4, 0x300, 0x3000, 0, 0x40, 0x40, 16};
const auto table = [](const std::uint64_t base, const std::vector<std::uint64_t>& targets) {
Bytes bytes;
for (const auto target : targets) {
const auto relative = static_cast<std::uint32_t>(target - base);
for (std::size_t byte = 0; byte < 4; ++byte)
bytes.push_back(static_cast<std::uint8_t>(relative >> (byte * 8)));
}
return bytes;
};
const auto convert = [&](const Bytes& first, const std::vector<std::pair<std::size_t, Bytes>>& placed, const std::vector<Domain::ProgramHeader>& headers) {
Bytes file(0x400, 0xCC);
std::copy(first.begin(), first.end(), file.begin() + 0x200);
std::copy(dispatch.begin(), dispatch.end(), file.begin() + 0x207);
for (const auto& [offset, bytes] : placed)
std::copy(bytes.begin(), bytes.end(), file.begin() + static_cast<std::ptrdiff_t>(offset));
return converter->Convert(file, headers);
};
const auto placedAt = [](const Codegen::ConvertResult& result, const Domain::FileByteOffset offset) {
return result.Trampolines.size() == 1 && result.Trampolines[0].Offset == offset && result.Trampolines[0].Length == 8;
};
require(placedAt(convert(leaTable, {{0x300, table(0x3000, {0x1010, 0x1018})}, {0x330, store}}, {code, data}), 0x210), "A 256-bit store moved the instruction after it although a jump table enters that instruction");
require(placedAt(convert(leaTable, {{0x300, table(0x3000, {0x1010, 0x1019, 0x1018})}}, {code, data}), 0x214), "A jump table was read past an entry that does not start an instruction");
require(placedAt(convert({0x48, 0x8B, 0x0D, 0xF9, 0x1F, 0x00, 0x00}, {{0x300, table(0x3000, {0x1010, 0x1018})}}, {code, data}), 0x214), "A jump table was read at an address loaded rather than formed by LEA");
require(placedAt(convert({0x48, 0x8D, 0x0D, 0xF7, 0x1F, 0x00, 0x00}, {{0x2FE, table(0x2FFE, {0x1018})}}, {code, data}), 0x214), "A jump table was read at an address outside every segment");
require(placedAt(convert(leaTable, {{0x300, table(0x3000, {0x1010})}, {0x380, table(0x3000, {0x1018})}}, {code, data, {0x61000000, 4, 0x380, 0x3000, 0, 0x10, 0x10, 16}}), 0x214), "A jump table was read from a segment that is not loaded");
require(placedAt(convert(leaTable, {{0x300, table(0x3000, {0x1010, 0x1018})}, {0x3A0, store}}, {code, data, {2, 5, 0x3A0, 0x2000, 0, 4, 4, 16}}), 0x210), "A segment that is not loaded was converted");
requireFailure([&] { (void)convert(leaTable, {{0x300, table(0x3000, {0x1010, 0x1018})}}, {code, {1, 4, 0x300, 0x3000, 0, 0x1000, 0x1000, 16}}); }, "A jump table was read from a segment that exceeds the file");
}
void converterRosetta() {
const auto converter = Codegen::MakeAmd64OnlyConverter(Codegen::Amd64OnlyTarget::Rosetta);
Bytes file(0x300, 0xCC);
@ -1637,6 +1674,7 @@ int main() {
converterRosetta();
wideStoreSplit();
converterWideStoreWindows();
converterJumpTableTargets();
converterReciprocal();
converterStrayRex();
rewriterStrayRex();

View File

@ -66,8 +66,8 @@ int Run(const int argc, char* argv[]) {
std::vector<Codegen::TrampolineSite> trampolines;
if (args.toIntel) {
const auto codeSegments = Relinker::ElfReader(sourceBytes).ReadCodeSegments();
auto converted = Codegen::MakeAmd64OnlyConverter(args.toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(sourceBytes), codeSegments);
const auto segments = Relinker::ElfReader(sourceBytes).ReadProgramHeaders();
auto converted = Codegen::MakeAmd64OnlyConverter(args.toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(sourceBytes), segments);
sourceBytes = std::move(converted.Bytes);
trampolines = std::move(converted.Trampolines);
std::map<std::string, std::size_t> stubsByName;

View File

@ -18,7 +18,6 @@ public:
[[nodiscard]] virtual FileByteOffset TranslateVirtualAddress(VirtualAddress address) const = 0;
[[nodiscard]] virtual std::vector<std::uint8_t> ReadSection(const SectionHeader& header) const = 0;
[[nodiscard]] virtual std::vector<std::uint8_t> ReadSegment(const ProgramHeader& header) const = 0;
[[nodiscard]] virtual std::vector<ProgramHeader> ReadCodeSegments() const = 0;
[[nodiscard]] virtual std::uint64_t GetFileSize() const = 0;
[[nodiscard]] virtual const std::vector<std::uint8_t>& GetRawBytes() const = 0;
};

View File

@ -17,7 +17,6 @@ public:
FileByteOffset TranslateVirtualAddress(VirtualAddress address) const override;
std::vector<std::uint8_t> ReadSection(const SectionHeader& header) const override;
std::vector<std::uint8_t> ReadSegment(const ProgramHeader& header) const override;
std::vector<ProgramHeader> ReadCodeSegments() const override;
std::uint64_t GetFileSize() const override;
const std::vector<std::uint8_t>& GetRawBytes() const override;

View File

@ -342,7 +342,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
std::vector<Domain::ProgramHeader> codeHeaders;
for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header);
if (toIntel) {
auto converted = Codegen::MakeAmd64OnlyConverter(toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(image.Bytes), codeHeaders);
auto converted = Codegen::MakeAmd64OnlyConverter(toRosetta ? Codegen::Amd64OnlyTarget::Rosetta : Codegen::Amd64OnlyTarget::Intel)->Convert(std::move(image.Bytes), image.Headers);
image.Trampolines = std::move(converted.Trampolines);
image.Bytes = std::move(converted.Bytes);
}

View File

@ -263,18 +263,6 @@ std::vector<std::uint8_t> ElfReader::ReadSegment(const ProgramHeader& header) co
_fileBuffer.begin() + header.Offset + header.FileSize);
}
std::vector<ProgramHeader> ElfReader::ReadCodeSegments() const {
static constexpr std::uint32_t kPtLoad = 1;
static constexpr std::uint32_t kPfX = 0x1;
std::vector<ProgramHeader> result;
for (const auto& ph : ReadProgramHeaders()) {
if (ph.Type == kPtLoad && (ph.Flags & kPfX)) {
result.push_back(ph);
}
}
return result;
}
std::uint64_t ElfReader::GetFileSize() const {
return _fileBuffer.size();
}