AnyPS5/core/relinker/codegen/src/Amd64OnlyConverter.cpp

169 lines
6.6 KiB
C++

#include <codegen/IAmd64OnlyConverter.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <codegen/CodegenException.hpp>
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <codegen/x86/X64InstructionRewriter.hpp>
#include <codegen/x86/IAmd64OnlyInstructionMatcher.hpp>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
namespace Codegen {
namespace {
template<typename TOperation>
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<std::uint8_t> fileBytes,
const std::vector<Domain::ProgramHeader>& codeSegments
) const override;
private:
struct Pending {
InstructionMatch Instruction;
Amd64OnlyMatch Substitution;
};
X64InstructionRewriter _rewriter;
std::unique_ptr<IAmd64OnlyInstructionMatcher> _matcher = MakeAmd64OnlyInstructionMatcher();
std::unique_ptr<IInstructionScanner> _scanner = MakeInstructionScanner();
void _convertSegment(
std::vector<std::uint8_t>& fileBytes,
const Domain::ProgramHeader& ph,
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
);
};
std::set<std::uint64_t> Amd64OnlyConverter::_collectBranchTargets(
const std::vector<std::uint8_t>& seg,
const std::vector<InstructionMatch>& matches,
const Domain::ProgramHeader& ph
) {
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));
}
return targets;
}
void Amd64OnlyConverter::_convertSegment(
std::vector<std::uint8_t>& fileBytes,
const Domain::ProgramHeader& ph,
ConvertResult& result
) const {
const auto segOffset = static_cast<std::size_t>(ph.Offset);
const auto segSize = static_cast<std::size_t>(ph.FileSize);
if (segOffset > fileBytes.size() || segSize > fileBytes.size() - segOffset)
throw CodegenException("Code segment exceeds the file", ph.Offset);
std::vector<std::uint8_t> seg(
fileBytes.begin() + static_cast<std::ptrdiff_t>(segOffset),
fileBytes.begin() + static_cast<std::ptrdiff_t>(segOffset + segSize)
);
const auto matches = _atFileOffset(ph.Offset, [&] { return _scanner->ScanCodeSection(seg, 0, seg.size()); });
std::vector<Pending> 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<std::uint64_t>{};
for (const auto& item : pending) {
const auto& match = item.Instruction;
const auto& substitution = item.Substitution;
const auto fileOffset = static_cast<Domain::FileByteOffset>(ph.Offset + match.Offset);
const auto address = static_cast<Domain::VirtualAddress>(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<std::ptrdiff_t>(match.Offset);
result.Trampolines.push_back({
fileOffset,
address,
match.Length,
std::vector<std::uint8_t>(begin, begin + static_cast<std::ptrdiff_t>(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<std::ptrdiff_t>(segOffset));
}
ConvertResult Amd64OnlyConverter::Convert(
std::vector<std::uint8_t> fileBytes,
const std::vector<Domain::ProgramHeader>& codeSegments
) const {
ConvertResult result{{}, 0, {}, {}};
for (const auto& ph : codeSegments)
_convertSegment(fileBytes, ph, result);
result.Bytes = std::move(fileBytes);
return result;
}
}
std::unique_ptr<IAmd64OnlyConverter> MakeAmd64OnlyConverter() {
return std::make_unique<Amd64OnlyConverter>();
}
}