refactor(scanner): move scanner from relinker to separated module

This commit is contained in:
boykopovar 2026-08-04 15:28:10 +03:00
parent 9c7570176f
commit 88fca26a28
11 changed files with 407 additions and 204 deletions

View File

@ -9,6 +9,8 @@ add_executable(relinker
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/FileReader.cpp
${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}/relinker/src/parsing/ElfReader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp
@ -21,5 +23,6 @@ target_include_directories(relinker PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include
${CMAKE_CURRENT_SOURCE_DIR}/io/include
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
${CMAKE_CURRENT_SOURCE_DIR}/relinker/include
)

View File

@ -0,0 +1,19 @@
#ifndef CODEGEN_CODEGENEXCEPTION_HPP
#define CODEGEN_CODEGENEXCEPTION_HPP
#include <domain/Types.hpp>
#include <stdexcept>
#include <string>
namespace Codegen {
struct CodegenException : std::runtime_error {
Domain::FileByteOffset FailureOffset;
explicit CodegenException(const std::string& message, const Domain::FileByteOffset failureOffset = 0)
: std::runtime_error(message), FailureOffset(failureOffset) {}
};
}
#endif

View File

@ -0,0 +1,38 @@
#ifndef CODEGEN_CODEGENTYPES_HPP
#define CODEGEN_CODEGENTYPES_HPP
#include <domain/Types.hpp>
#include <cstdint>
#include <vector>
namespace Codegen {
struct DecodedInstruction {
std::size_t Length;
bool Valid;
};
struct InstructionMatch {
Domain::FileByteOffset Offset;
std::size_t Length;
};
struct RewriteRequest {
Domain::FileByteOffset Offset;
std::vector<std::uint8_t> NewBytes;
};
struct AddressAdjustment {
Domain::FileByteOffset OriginalOffset;
Domain::FileByteOffset AdjustedOffset;
std::int64_t Delta;
};
struct RewriteResult {
std::vector<std::uint8_t> Bytes;
std::vector<AddressAdjustment> Adjustments;
};
}
#endif

View File

@ -0,0 +1,21 @@
#ifndef CODEGEN_IINSTRUCTIONDECODER_HPP
#define CODEGEN_IINSTRUCTIONDECODER_HPP
#include <codegen/CodegenTypes.hpp>
#include <cstdint>
namespace Codegen {
class IInstructionDecoder {
public:
virtual ~IInstructionDecoder() = default;
[[nodiscard]] virtual DecodedInstruction Decode(
const std::uint8_t* data,
std::size_t available
) const = 0;
};
}
#endif

View File

@ -0,0 +1,21 @@
#ifndef CODEGEN_IINSTRUCTIONREWRITER_HPP
#define CODEGEN_IINSTRUCTIONREWRITER_HPP
#include <codegen/CodegenTypes.hpp>
#include <vector>
namespace Codegen {
class IInstructionRewriter {
public:
virtual ~IInstructionRewriter() = default;
[[nodiscard]] virtual RewriteResult Rewrite(
const std::vector<std::uint8_t>& codeSection,
const RewriteRequest& request
) const = 0;
};
}
#endif

View File

@ -0,0 +1,26 @@
#ifndef CODEGEN_IINSTRUCTIONSCANNER_HPP
#define CODEGEN_IINSTRUCTIONSCANNER_HPP
#include <codegen/CodegenTypes.hpp>
#include <cstdint>
#include <memory>
#include <vector>
namespace Codegen {
class IInstructionScanner {
public:
virtual ~IInstructionScanner() = default;
[[nodiscard]] virtual std::vector<InstructionMatch> ScanCodeSection(
const std::vector<std::uint8_t>& codeSection,
Domain::FileByteOffset codeSectionOffset,
Domain::FileByteOffset codeSectionSize
) const = 0;
};
std::unique_ptr<IInstructionScanner> MakeInstructionScanner();
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef CODEGEN_X86_X64INSTRUCTIONDECODER_HPP
#define CODEGEN_X86_X64INSTRUCTIONDECODER_HPP
#include <codegen/IInstructionDecoder.hpp>
namespace Codegen {
class X64InstructionDecoder : public IInstructionDecoder {
public:
[[nodiscard]] DecodedInstruction Decode(
const std::uint8_t* data,
std::size_t available
) const override;
};
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef CODEGEN_X86_X64INSTRUCTIONREWRITER_HPP
#define CODEGEN_X86_X64INSTRUCTIONREWRITER_HPP
#include <codegen/IInstructionRewriter.hpp>
namespace Codegen {
class X64InstructionRewriter : public IInstructionRewriter {
public:
[[nodiscard]] RewriteResult Rewrite(
const std::vector<std::uint8_t>& codeSection,
const RewriteRequest& request
) const override;
};
}
#endif

View File

@ -0,0 +1,51 @@
#include <codegen/IInstructionScanner.hpp>
#include <codegen/x86/X64InstructionDecoder.hpp>
#include <algorithm>
#include <memory>
namespace Codegen {
class InstructionScanner : public IInstructionScanner {
public:
[[nodiscard]] std::vector<InstructionMatch> ScanCodeSection(
const std::vector<std::uint8_t>& codeSection,
Domain::FileByteOffset codeSectionOffset,
Domain::FileByteOffset codeSectionSize
) const override;
};
std::vector<InstructionMatch> InstructionScanner::ScanCodeSection(
const std::vector<std::uint8_t>& codeSection,
const Domain::FileByteOffset codeSectionOffset,
const Domain::FileByteOffset codeSectionSize
) const {
const std::size_t limit = std::min(codeSection.size(), static_cast<std::size_t>(codeSectionSize));
std::vector<InstructionMatch> matches;
std::size_t i = 0;
while (i < limit) {
X64InstructionDecoder decoder;
const std::uint8_t* cursor = codeSection.data() + i;
const std::size_t available = limit - i;
const auto decoded = decoder.Decode(cursor, available);
if (!decoded.Valid || decoded.Length == 0) {
i += 1;
continue;
}
matches.push_back({codeSectionOffset + i, decoded.Length});
i += decoded.Length;
}
return matches;
}
std::unique_ptr<IInstructionScanner> MakeInstructionScanner() {
return std::make_unique<InstructionScanner>();
}
}

View File

@ -0,0 +1,183 @@
#include <codegen/x86/X64InstructionDecoder.hpp>
namespace Codegen {
DecodedInstruction X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t available) const {
std::size_t pos = 0;
bool rexPresent = false;
std::uint8_t rex = 0;
bool operandSizeOverride = false;
while (pos < available) {
const std::uint8_t b = data[pos];
if (b == 0xF0 || b == 0xF2 || b == 0xF3 ||
b == 0x2E || b == 0x36 || b == 0x3E || b == 0x26 ||
b == 0x64 || b == 0x65) {
pos += 1;
continue;
}
if (b == 0x66) {
operandSizeOverride = true;
pos += 1;
continue;
}
if (b == 0x67) {
pos += 1;
continue;
}
break;
}
if (pos < available && data[pos] >= 0x40 && data[pos] <= 0x4F) {
rexPresent = true;
rex = data[pos];
pos += 1;
}
if (pos >= available) {
return {1, false};
}
std::uint8_t opcode = data[pos];
pos += 1;
bool twoByteOpcode = false;
if (opcode == 0x0F) {
if (pos >= available) {
return {1, false};
}
twoByteOpcode = true;
opcode = data[pos];
pos += 1;
}
bool hasModRm = false;
std::size_t immediateSize = 0;
if (!twoByteOpcode) {
if ((opcode >= 0x00 && opcode <= 0x03) || (opcode >= 0x08 && opcode <= 0x0B) ||
(opcode >= 0x10 && opcode <= 0x13) || (opcode >= 0x18 && opcode <= 0x1B) ||
(opcode >= 0x20 && opcode <= 0x23) || (opcode >= 0x28 && opcode <= 0x2B) ||
(opcode >= 0x30 && opcode <= 0x33) || (opcode >= 0x38 && opcode <= 0x3B) ||
opcode == 0x62 || opcode == 0x63 ||
(opcode >= 0x84 && opcode <= 0x8F) ||
opcode == 0xC0 || opcode == 0xC1 ||
opcode == 0xC4 || opcode == 0xC5 || opcode == 0xC6 || opcode == 0xC7 ||
opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3 ||
opcode == 0xF6 || opcode == 0xF7 ||
opcode == 0xFE || opcode == 0xFF ||
(opcode >= 0x88 && opcode <= 0x8B)) {
hasModRm = true;
}
if (opcode == 0xC6 || opcode == 0x80 ||
opcode == 0x04 || opcode == 0x0C ||
opcode == 0x14 || opcode == 0x1C ||
opcode == 0x24 || opcode == 0x2C ||
opcode == 0x34 || opcode == 0x3C ||
(opcode >= 0xB0 && opcode <= 0xB7) ||
opcode == 0x6A ||
opcode == 0xA8) {
immediateSize = 1;
} else if (opcode == 0xC7 || opcode == 0x81 ||
opcode == 0x05 || opcode == 0x0D ||
opcode == 0x15 || opcode == 0x1D ||
opcode == 0x25 || opcode == 0x2D ||
opcode == 0x35 || opcode == 0x3D ||
opcode == 0x68 ||
opcode == 0xA9 ||
(opcode >= 0xB8 && opcode <= 0xBF)) {
if (opcode >= 0xB8 && opcode <= 0xBF) {
immediateSize = (rexPresent && (rex & 0x08) != 0) ? 8 : (operandSizeOverride ? 2 : 4);
} else {
immediateSize = operandSizeOverride ? 2 : 4;
}
} else if (opcode == 0x83) {
immediateSize = 1;
}
if (opcode >= 0x70 && opcode <= 0x7F) {
immediateSize = 1;
}
if (opcode == 0xE8 || opcode == 0xE9) {
immediateSize = 4;
}
if (opcode == 0xEB) {
immediateSize = 1;
}
} else {
if (opcode >= 0x80 && opcode <= 0x8F) {
immediateSize = 4;
} else if (opcode == 0xBA) {
hasModRm = true;
immediateSize = 1;
} else if (opcode == 0xA4 || opcode == 0xAC) {
immediateSize = 1;
} else if ((opcode >= 0x40 && opcode <= 0x4F) ||
(opcode >= 0xA3 && opcode <= 0xAB) ||
(opcode >= 0xB0 && opcode <= 0xB7) ||
(opcode >= 0xBC && opcode <= 0xBF) ||
(opcode >= 0x10 && opcode <= 0x17) ||
(opcode >= 0x28 && opcode <= 0x2F) ||
(opcode >= 0x38 && opcode <= 0x3A) ||
(opcode >= 0x54 && opcode <= 0x77) ||
(opcode >= 0xD0 && opcode <= 0xFE) ||
opcode == 0x1F) {
hasModRm = true;
}
}
if (!hasModRm) {
if (pos + immediateSize > available) {
return {pos + immediateSize, false};
}
return {pos + immediateSize, true};
}
if (pos >= available) {
return {pos, false};
}
const std::uint8_t modrm = data[pos];
pos += 1;
const std::uint8_t mod = static_cast<std::uint8_t>((modrm >> 6) & 0x03);
const std::uint8_t rm = static_cast<std::uint8_t>(modrm & 0x07);
if (mod != 0x03 && rm == 0x04) {
if (pos >= available) {
return {pos, false};
}
const std::uint8_t sib = data[pos];
const std::uint8_t base = static_cast<std::uint8_t>(sib & 0x07);
pos += 1;
if (mod == 0x00 && base == 0x05) {
pos += 4;
}
}
if (mod == 0x01) {
pos += 1;
} else if (mod == 0x02) {
pos += 4;
} else if (mod == 0x00 && rm == 0x05) {
pos += 4;
}
pos += immediateSize;
if (pos > available) {
return {pos, false};
}
return {pos, true};
}
}

View File

@ -1,4 +1,5 @@
#include <relinker/analysis/SyscallScanner.hpp>
#include <codegen/IInstructionScanner.hpp>
#include <memory>
#include <sstream>
@ -13,192 +14,6 @@ static constexpr std::uint8_t SYSENTER_BYTE1 = 0x34;
static constexpr std::uint8_t SYSRET_BYTE0 = 0x0F;
static constexpr std::uint8_t SYSRET_BYTE1 = 0x07;
class X64InstructionLengthDecoder {
public:
struct DecodedInstruction {
std::size_t Length;
bool Valid;
};
[[nodiscard]] DecodedInstruction Decode(const std::uint8_t* data, std::size_t available) const {
std::size_t pos = 0;
bool rexPresent = false;
std::uint8_t rex = 0;
bool operandSizeOverride = false;
while (pos < available) {
const std::uint8_t b = data[pos];
if (b == 0xF0 || b == 0xF2 || b == 0xF3 ||
b == 0x2E || b == 0x36 || b == 0x3E || b == 0x26 ||
b == 0x64 || b == 0x65) {
pos += 1;
continue;
}
if (b == 0x66) {
operandSizeOverride = true;
pos += 1;
continue;
}
if (b == 0x67) {
pos += 1;
continue;
}
break;
}
if (pos < available && data[pos] >= 0x40 && data[pos] <= 0x4F) {
rexPresent = true;
rex = data[pos];
pos += 1;
}
if (pos >= available) {
return {1, false};
}
std::uint8_t opcode = data[pos];
pos += 1;
bool twoByteOpcode = false;
if (opcode == 0x0F) {
if (pos >= available) {
return {1, false};
}
twoByteOpcode = true;
opcode = data[pos];
pos += 1;
}
bool hasModRm = false;
std::size_t immediateSize = 0;
if (!twoByteOpcode) {
if ((opcode >= 0x00 && opcode <= 0x03) || (opcode >= 0x08 && opcode <= 0x0B) ||
(opcode >= 0x10 && opcode <= 0x13) || (opcode >= 0x18 && opcode <= 0x1B) ||
(opcode >= 0x20 && opcode <= 0x23) || (opcode >= 0x28 && opcode <= 0x2B) ||
(opcode >= 0x30 && opcode <= 0x33) || (opcode >= 0x38 && opcode <= 0x3B) ||
opcode == 0x62 || opcode == 0x63 ||
(opcode >= 0x84 && opcode <= 0x8F) ||
opcode == 0xC0 || opcode == 0xC1 ||
opcode == 0xC4 || opcode == 0xC5 || opcode == 0xC6 || opcode == 0xC7 ||
opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3 ||
opcode == 0xF6 || opcode == 0xF7 ||
opcode == 0xFE || opcode == 0xFF ||
(opcode >= 0x88 && opcode <= 0x8B)) {
hasModRm = true;
}
if (opcode == 0xC6 || opcode == 0x80 ||
opcode == 0x04 || opcode == 0x0C ||
opcode == 0x14 || opcode == 0x1C ||
opcode == 0x24 || opcode == 0x2C ||
opcode == 0x34 || opcode == 0x3C ||
(opcode >= 0xB0 && opcode <= 0xB7) ||
opcode == 0x6A ||
opcode == 0xA8) {
immediateSize = 1;
} else if (opcode == 0xC7 || opcode == 0x81 ||
opcode == 0x05 || opcode == 0x0D ||
opcode == 0x15 || opcode == 0x1D ||
opcode == 0x25 || opcode == 0x2D ||
opcode == 0x35 || opcode == 0x3D ||
opcode == 0x68 ||
opcode == 0xA9 ||
(opcode >= 0xB8 && opcode <= 0xBF)) {
if (opcode >= 0xB8 && opcode <= 0xBF) {
immediateSize = (rexPresent && (rex & 0x08) != 0) ? 8 : (operandSizeOverride ? 2 : 4);
} else {
immediateSize = operandSizeOverride ? 2 : 4;
}
} else if (opcode == 0x83) {
immediateSize = 1;
}
if (opcode >= 0x70 && opcode <= 0x7F) {
immediateSize = 1;
}
if (opcode == 0xE8 || opcode == 0xE9) {
immediateSize = 4;
}
if (opcode == 0xEB) {
immediateSize = 1;
}
} else {
if (opcode >= 0x80 && opcode <= 0x8F) {
immediateSize = 4;
} else if (opcode == 0xBA) {
hasModRm = true;
immediateSize = 1;
} else if (opcode == 0xA4 || opcode == 0xAC) {
immediateSize = 1;
} else if ((opcode >= 0x40 && opcode <= 0x4F) ||
(opcode >= 0xA3 && opcode <= 0xAB) ||
(opcode >= 0xB0 && opcode <= 0xB7) ||
(opcode >= 0xBC && opcode <= 0xBF) ||
(opcode >= 0x10 && opcode <= 0x17) ||
(opcode >= 0x28 && opcode <= 0x2F) ||
(opcode >= 0x38 && opcode <= 0x3A) ||
(opcode >= 0x54 && opcode <= 0x77) ||
(opcode >= 0xD0 && opcode <= 0xFE) ||
opcode == 0x1F) {
hasModRm = true;
}
}
if (!hasModRm) {
if (pos + immediateSize > available) {
return {pos + immediateSize, false};
}
return {pos + immediateSize, true};
}
if (pos >= available) {
return {pos, false};
}
const std::uint8_t modrm = data[pos];
pos += 1;
const std::uint8_t mod = static_cast<std::uint8_t>((modrm >> 6) & 0x03);
const std::uint8_t rm = static_cast<std::uint8_t>(modrm & 0x07);
if (mod != 0x03 && rm == 0x04) {
if (pos >= available) {
return {pos, false};
}
const std::uint8_t sib = data[pos];
const std::uint8_t base = static_cast<std::uint8_t>(sib & 0x07);
pos += 1;
if (mod == 0x00 && base == 0x05) {
pos += 4;
}
}
if (mod == 0x01) {
pos += 1;
} else if (mod == 0x02) {
pos += 4;
} else if (mod == 0x00 && rm == 0x05) {
pos += 4;
}
pos += immediateSize;
if (pos > available) {
return {pos, false};
}
return {pos, true};
}
};
class SyscallScanner : public ISyscallScanner {
public:
void ScanCodeSectionForSyscalls(
@ -213,16 +28,15 @@ void SyscallScanner::ScanCodeSectionForSyscalls(
const FileByteOffset codeSectionSize) {
const std::size_t limit = std::min(codeSection.size(), static_cast<std::size_t>(codeSectionSize));
std::size_t i = 0;
const auto scanner = Codegen::MakeInstructionScanner();
const auto matches = scanner->ScanCodeSection(codeSection, codeSectionOffset, codeSectionSize);
while (i < limit) {
X64InstructionLengthDecoder decoder;
const std::uint8_t* cursor = codeSection.data() + i;
const std::size_t available = limit - i;
for (const auto& [Offset, Length] : matches) {
const std::size_t i = Offset - codeSectionOffset;
if (available >= 2) {
const std::uint8_t b0 = cursor[0];
const std::uint8_t b1 = cursor[1];
if (i + 1 < limit) {
const std::uint8_t b0 = codeSection[i];
const std::uint8_t b1 = codeSection[i + 1];
const bool isSyscall = (b0 == SYSCALL_BYTE0 && b1 == SYSCALL_BYTE1);
const bool isInt80 = (b0 == INT80_BYTE0 && b1 == INT80_BYTE1);
@ -230,21 +44,12 @@ void SyscallScanner::ScanCodeSectionForSyscalls(
const bool isSysret = (b0 == SYSRET_BYTE0 && b1 == SYSRET_BYTE1);
if (isSyscall || isInt80 || isSysenter || isSysret) {
FileByteOffset instrOffset = codeSectionOffset + i;
const FileByteOffset instrOffset = codeSectionOffset + i;
std::ostringstream msg;
msg << "Forbidden syscall instruction at code offset 0x" << std::hex << instrOffset;
throw RelinkerException(msg.str(), instrOffset);
}
}
const auto decoded = decoder.Decode(cursor, available);
if (!decoded.Valid || decoded.Length == 0) {
i += 1;
continue;
}
i += decoded.Length;
}
}