#include #include #include #include #include #include #include #include #include #include #include namespace Codegen { namespace { template auto _atFileOffset(const Domain::FileByteOffset base, const TOperation& operation) { try { return operation(); } catch (const CodegenException& e) { throw CodegenException(e.what(), base + e.FailureOffset); } } class Amd64OnlyConverter : public IAmd64OnlyConverter { public: [[nodiscard]] ConvertResult Convert( std::vector fileBytes, const std::vector& codeSegments ) const override; private: struct Pending { InstructionMatch Instruction; Amd64OnlyMatch Substitution; }; X64InstructionRewriter _rewriter; std::unique_ptr _matcher = MakeAmd64OnlyInstructionMatcher(); std::unique_ptr _scanner = MakeInstructionScanner(); void _convertSegment( std::vector& fileBytes, const Domain::ProgramHeader& ph, ConvertResult& result ) const; [[nodiscard]] static std::set _collectBranchTargets( const std::vector& seg, const std::vector& matches, const Domain::ProgramHeader& ph ); }; std::set Amd64OnlyConverter::_collectBranchTargets( const std::vector& seg, const std::vector& matches, const Domain::ProgramHeader& ph ) { const X64InstructionDecoder decoder; std::set 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(ph.MappedAddress + match.Offset + info.Length) + info.BranchDisp; if (target >= 0) targets.insert(static_cast(target)); } return targets; } void Amd64OnlyConverter::_convertSegment( std::vector& fileBytes, const Domain::ProgramHeader& ph, ConvertResult& result ) const { const auto segOffset = static_cast(ph.Offset); const auto segSize = static_cast(ph.FileSize); if (segOffset > fileBytes.size() || segSize > fileBytes.size() - segOffset) throw CodegenException("Code segment exceeds the file", ph.Offset); std::vector seg( fileBytes.begin() + static_cast(segOffset), fileBytes.begin() + static_cast(segOffset + segSize) ); const auto matches = _atFileOffset(ph.Offset, [&] { return _scanner->ScanCodeSection(seg, 0, seg.size()); }); std::vector pending; for (const auto& match : matches) { auto substitution = _atFileOffset(ph.Offset + match.Offset, [&] { return _matcher->Match(seg.data() + match.Offset, match.Length); }); if (substitution.has_value()) pending.push_back({match, std::move(*substitution)}); } if (pending.empty()) return; 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{}; for (const auto& item : pending) { const auto& match = item.Instruction; const auto& substitution = item.Substitution; const auto fileOffset = static_cast(ph.Offset + match.Offset); const auto address = static_cast(ph.MappedAddress + match.Offset); std::size_t replacementLength = 0; switch (substitution.Lowering) { case Amd64OnlyLowering::InPlace: { if (substitution.ReplacementBytes.size() != match.Length) throw CodegenException("Intel substitution changes the instruction length", fileOffset); seg = _atFileOffset(ph.Offset, [&] { return _rewriter.Rewrite(seg, {match.Offset, substitution.ReplacementBytes}).Bytes; }); replacementLength = substitution.ReplacementBytes.size(); ++result.ReplacedCount; break; } case Amd64OnlyLowering::Trampoline: { if (match.Length < Amd64OnlySubstitutionTable::kJmpRel32.Size) throw CodegenException("AMD-only instruction too short for a jump", fileOffset); const auto hit = branchTargets.upper_bound(address); if (hit != branchTargets.end() && *hit < address + match.Length) throw CodegenException("Branch enters an AMD-only instruction", ph.Offset + (*hit - ph.MappedAddress)); const auto begin = seg.begin() + static_cast(match.Offset); result.Trampolines.push_back({ fileOffset, address, match.Length, std::vector(begin, begin + static_cast(match.Length)), substitution.StubBody, substitution.ReturnBranchOffset }); replacementLength = substitution.StubBody.size(); break; } case Amd64OnlyLowering::Unsupported: throw CodegenException("AMD-only instruction without Intel lowering: " + substitution.InstructionName, fileOffset); } result.Reports.push_back({substitution.InstructionName, fileOffset, match.Length, replacementLength, substitution.Lowering}); } if (seg.size() != segSize) throw CodegenException("Code segment size changed during Intel conversion", ph.Offset); std::copy(seg.begin(), seg.end(), fileBytes.begin() + static_cast(segOffset)); } ConvertResult Amd64OnlyConverter::Convert( std::vector fileBytes, const std::vector& codeSegments ) const { ConvertResult result{{}, 0, {}, {}}; for (const auto& ph : codeSegments) _convertSegment(fileBytes, ph, result); result.Bytes = std::move(fileBytes); return result; } } std::unique_ptr MakeAmd64OnlyConverter() { return std::make_unique(); } }