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:
commit
e305bc4e4a
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue