feat(code): add instruction rewriter implementation

This commit is contained in:
boykopovar 2026-08-04 22:16:55 +03:00
parent 88fca26a28
commit 2ac8a96d70
3 changed files with 286 additions and 0 deletions

View File

@ -11,6 +11,7 @@ add_executable(relinker
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/InstructionScanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionRewriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp

View File

@ -2,6 +2,7 @@
#define CODEGEN_X86_X64INSTRUCTIONREWRITER_HPP
#include <codegen/IInstructionRewriter.hpp>
#include <cstdint>
namespace Codegen {
@ -11,6 +12,71 @@ public:
const std::vector<std::uint8_t>& codeSection,
const RewriteRequest& request
) const override;
private:
enum class ReferenceKind {
None,
Rel8Branch,
Rel32Branch,
RipRelativeDisp32
};
struct ReferenceSite {
ReferenceKind Kind;
std::size_t FieldOffset;
};
static constexpr std::uint8_t OpcodePrefixLock = 0xF0;
static constexpr std::uint8_t OpcodePrefixRepne = 0xF2;
static constexpr std::uint8_t OpcodePrefixRep = 0xF3;
static constexpr std::uint8_t OpcodePrefixSegCs = 0x2E;
static constexpr std::uint8_t OpcodePrefixSegSs = 0x36;
static constexpr std::uint8_t OpcodePrefixSegDs = 0x3E;
static constexpr std::uint8_t OpcodePrefixSegEs = 0x26;
static constexpr std::uint8_t OpcodePrefixSegFs = 0x64;
static constexpr std::uint8_t OpcodePrefixSegGs = 0x65;
static constexpr std::uint8_t OpcodePrefixOperandSize = 0x66;
static constexpr std::uint8_t OpcodePrefixAddressSize = 0x67;
static constexpr std::uint8_t RexMin = 0x40;
static constexpr std::uint8_t RexMax = 0x4F;
static constexpr std::uint8_t TwoByteOpcodeEscape = 0x0F;
static constexpr std::uint8_t OpcodeCallRel32 = 0xE8;
static constexpr std::uint8_t OpcodeJmpRel32 = 0xE9;
static constexpr std::uint8_t OpcodeJmpRel8 = 0xEB;
static constexpr std::uint8_t OpcodeJccRel8Min = 0x70;
static constexpr std::uint8_t OpcodeJccRel8Max = 0x7F;
static constexpr std::uint8_t OpcodeJccRel32Min = 0x80;
static constexpr std::uint8_t OpcodeJccRel32Max = 0x8F;
static constexpr std::size_t Rel32InstructionLength = 5;
static constexpr std::size_t Rel8FieldSize = 1;
static constexpr std::size_t Rel32FieldSize = 4;
static constexpr std::size_t Disp32FieldSize = 4;
static constexpr std::uint8_t ModRmModShift = 6;
static constexpr std::uint8_t ModRmModMask = 0x03;
static constexpr std::uint8_t ModRmRmMask = 0x07;
static constexpr std::uint8_t ModRmModIndirect = 0x00;
static constexpr std::uint8_t ModRmRmRipRelative = 0x05;
[[nodiscard]] static ReferenceSite ClassifyInstruction(
const std::uint8_t* data,
std::size_t length
);
[[nodiscard]] static std::int32_t ReadInt32(const std::uint8_t* data);
static void WriteInt32(std::uint8_t* data, std::int32_t value);
[[nodiscard]] static std::int64_t AdjustedDelta(
std::int64_t instrPosition,
std::int64_t targetPosition,
std::int64_t cutStart,
std::int64_t delta
);
};
}

View File

@ -0,0 +1,219 @@
#include <codegen/x86/X64InstructionRewriter.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <codegen/CodegenException.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <cstdint>
#include <limits>
namespace Codegen {
X64InstructionRewriter::ReferenceSite X64InstructionRewriter::ClassifyInstruction(
const std::uint8_t* data,
std::size_t length
) {
std::size_t pos = 0;
while (pos < length) {
const std::uint8_t b = data[pos];
if (b == OpcodePrefixLock || b == OpcodePrefixRepne || b == OpcodePrefixRep ||
b == OpcodePrefixSegCs || b == OpcodePrefixSegSs || b == OpcodePrefixSegDs ||
b == OpcodePrefixSegEs || b == OpcodePrefixSegFs || b == OpcodePrefixSegGs ||
b == OpcodePrefixOperandSize || b == OpcodePrefixAddressSize) {
pos += 1;
continue;
}
break;
}
if (pos < length && data[pos] >= RexMin && data[pos] <= RexMax) {
pos += 1;
}
if (pos >= length) {
return {ReferenceKind::None, 0};
}
std::uint8_t opcode = data[pos];
const std::size_t opcodeOffset = pos;
pos += 1;
bool twoByteOpcode = false;
if (opcode == TwoByteOpcodeEscape) {
if (pos >= length) {
return {ReferenceKind::None, 0};
}
twoByteOpcode = true;
opcode = data[pos];
pos += 1;
}
if (!twoByteOpcode) {
if (opcode == OpcodeCallRel32 || opcode == OpcodeJmpRel32) {
if (length >= opcodeOffset + Rel32InstructionLength) {
return {ReferenceKind::Rel32Branch, pos};
}
return {ReferenceKind::None, 0};
}
if (opcode == OpcodeJmpRel8 || (opcode >= OpcodeJccRel8Min && opcode <= OpcodeJccRel8Max)) {
if (length >= pos + Rel8FieldSize) {
return {ReferenceKind::Rel8Branch, pos};
}
return {ReferenceKind::None, 0};
}
} else {
if (opcode >= OpcodeJccRel32Min && opcode <= OpcodeJccRel32Max) {
if (length >= pos + Rel32FieldSize) {
return {ReferenceKind::Rel32Branch, pos};
}
return {ReferenceKind::None, 0};
}
}
if (pos >= length) {
return {ReferenceKind::None, 0};
}
const std::uint8_t modrm = data[pos];
const std::uint8_t mod = static_cast<std::uint8_t>((modrm >> ModRmModShift) & ModRmModMask);
const std::uint8_t rm = static_cast<std::uint8_t>(modrm & ModRmRmMask);
if (mod == ModRmModIndirect && rm == ModRmRmRipRelative) {
const std::size_t dispOffset = pos + 1;
if (length >= dispOffset + Disp32FieldSize) {
return {ReferenceKind::RipRelativeDisp32, dispOffset};
}
}
return {ReferenceKind::None, 0};
}
std::int32_t X64InstructionRewriter::ReadInt32(const std::uint8_t* data) {
return static_cast<std::int32_t>(
static_cast<std::uint32_t>(data[0]) |
(static_cast<std::uint32_t>(data[1]) << 8) |
(static_cast<std::uint32_t>(data[2]) << 16) |
(static_cast<std::uint32_t>(data[3]) << 24));
}
void X64InstructionRewriter::WriteInt32(std::uint8_t* data, std::int32_t value) {
const auto unsignedValue = static_cast<std::uint32_t>(value);
data[0] = static_cast<std::uint8_t>(unsignedValue & 0xFF);
data[1] = static_cast<std::uint8_t>((unsignedValue >> 8) & 0xFF);
data[2] = static_cast<std::uint8_t>((unsignedValue >> 16) & 0xFF);
data[3] = static_cast<std::uint8_t>((unsignedValue >> 24) & 0xFF);
}
std::int64_t X64InstructionRewriter::AdjustedDelta(
std::int64_t instrPosition,
std::int64_t targetPosition,
std::int64_t cutStart,
std::int64_t delta
) {
const bool instrBeforeCut = instrPosition < cutStart;
const bool targetBeforeCut = targetPosition < cutStart;
if (instrBeforeCut == targetBeforeCut) {
return 0;
}
return instrBeforeCut ? delta : -delta;
}
RewriteResult X64InstructionRewriter::Rewrite(
const std::vector<std::uint8_t>& codeSection,
const RewriteRequest& request
) const {
const auto offset = static_cast<std::size_t>(request.Offset);
if (offset > codeSection.size()) {
throw CodegenException("Rewrite offset is out of bounds", request.Offset);
}
const X64InstructionDecoder decoder;
const auto decoded = decoder.Decode(codeSection.data() + offset, codeSection.size() - offset);
if (!decoded.Valid || decoded.Length == 0) {
throw CodegenException("Cannot decode instruction at rewrite offset", request.Offset);
}
const auto oldLength = static_cast<std::int64_t>(decoded.Length);
const auto newLength = static_cast<std::int64_t>(request.NewBytes.size());
const std::int64_t delta = newLength - oldLength;
const auto cutStart = static_cast<std::int64_t>(offset);
const std::int64_t cutEnd = cutStart + oldLength;
std::vector<std::uint8_t> rewritten;
rewritten.reserve(codeSection.size() + static_cast<std::size_t>(delta > 0 ? delta : 0));
rewritten.insert(rewritten.end(), codeSection.begin(), codeSection.begin() + offset);
rewritten.insert(rewritten.end(), request.NewBytes.begin(), request.NewBytes.end());
rewritten.insert(rewritten.end(), codeSection.begin() + offset + decoded.Length, codeSection.end());
const auto scanner = MakeInstructionScanner();
const auto matches = scanner->ScanCodeSection(codeSection, 0, codeSection.size());
std::vector<AddressAdjustment> adjustments;
for (const auto& match : matches) {
const auto matchOffset = static_cast<std::int64_t>(match.Offset);
if (matchOffset >= cutStart && matchOffset < cutEnd) {
continue;
}
const auto site = ClassifyInstruction(codeSection.data() + match.Offset, match.Length);
if (site.Kind != ReferenceKind::None) {
const std::int64_t instrEnd = matchOffset + static_cast<std::int64_t>(match.Length);
const std::size_t fieldPosition = static_cast<std::size_t>(match.Offset) + site.FieldOffset;
if (site.Kind == ReferenceKind::Rel8Branch) {
const auto disp = static_cast<std::int8_t>(codeSection[fieldPosition]);
const std::int64_t targetPosition = instrEnd + disp;
const std::int64_t adjust = AdjustedDelta(matchOffset, targetPosition, cutStart, delta);
if (adjust != 0) {
const std::int64_t newDisp = static_cast<std::int64_t>(disp) + adjust;
if (newDisp < std::numeric_limits<std::int8_t>::min() ||
newDisp > std::numeric_limits<std::int8_t>::max()) {
throw CodegenException("Rel8 branch displacement overflow after rewrite", match.Offset);
}
const std::int64_t newMatchOffset = matchOffset < cutStart ? matchOffset : matchOffset + delta;
const auto newFieldPosition = static_cast<std::size_t>(
newMatchOffset + static_cast<std::int64_t>(site.FieldOffset));
rewritten[newFieldPosition] = static_cast<std::uint8_t>(static_cast<std::int8_t>(newDisp));
}
} else {
const auto disp = ReadInt32(codeSection.data() + fieldPosition);
const std::int64_t targetPosition = instrEnd + disp;
const std::int64_t adjust = AdjustedDelta(matchOffset, targetPosition, cutStart, delta);
if (adjust != 0) {
const std::int64_t newDisp = static_cast<std::int64_t>(disp) + adjust;
if (newDisp < std::numeric_limits<std::int32_t>::min() ||
newDisp > std::numeric_limits<std::int32_t>::max()) {
throw CodegenException("Rel32/disp32 displacement overflow after rewrite", match.Offset);
}
const std::int64_t newMatchOffset = matchOffset < cutStart ? matchOffset : matchOffset + delta;
const auto newFieldPosition = static_cast<std::size_t>(
newMatchOffset + static_cast<std::int64_t>(site.FieldOffset));
WriteInt32(rewritten.data() + newFieldPosition, static_cast<std::int32_t>(newDisp));
}
}
}
if (matchOffset >= cutEnd && delta != 0) {
const auto newOffset = static_cast<Domain::FileByteOffset>(matchOffset + delta);
adjustments.push_back({match.Offset, newOffset, delta});
}
}
return {rewritten, adjustments};
}
}