Skip to content

Commit

Permalink
Merge pull request #132 from zyantific/refactor-encode-variant
Browse files Browse the repository at this point in the history
Refactor some specifics about the encoding
  • Loading branch information
ZehMatt authored Jun 18, 2024
2 parents 8ffedf9 + bb4faf9 commit 5bf4e65
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 53 deletions.
77 changes: 70 additions & 7 deletions tests/src/tests/tests.serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1229,30 +1229,33 @@ namespace zasm::tests
std::string("Error at node \"and rbp, 0x123456789abcdf\" with id 0: Impossible instruction"));
}

TEST(SerializationTests, TestSerializationJecxzBad)
template<typename TMnemonic>
static void TestSerializationLabelRangeBad(MachineMode mode, TMnemonic mnemonic, const char* mnemonicName)
{
Program program(MachineMode::AMD64);
Program program(mode);

x86::Assembler a(program);
auto labelLoop = a.createLabel();
ASSERT_EQ(a.jecxz(labelLoop), ErrorCode::None);
ASSERT_EQ(a.emit(mnemonic, labelLoop), ErrorCode::None);
ASSERT_EQ(a.dd(0, 256), ErrorCode::None);
ASSERT_EQ(a.bind(labelLoop), ErrorCode::None);

Serializer serializer;
auto res = serializer.serialize(program, 0x140015000);
ASSERT_EQ(res, ErrorCode::AddressOutOfRange);

ASSERT_EQ(res.getErrorMessage(), std::string("Error at node \"jecxz L0\" with id 0: Label out of range for operand 0"));
const auto errMsg = std::string("Error at node \"") + mnemonicName
+ " L0\" with id 0: Label out of range for operand 0";
ASSERT_EQ(res.getErrorMessage(), errMsg);
}

TEST(SerializationTests, TestSerializationJecxzGood)
template<typename TMnemonic> static void TestSerializationLabelRangeGood(MachineMode mode, TMnemonic mnemonic)
{
Program program(MachineMode::AMD64);
Program program(mode);

x86::Assembler a(program);
auto labelLoop = a.createLabel();
ASSERT_EQ(a.jecxz(labelLoop), ErrorCode::None);
ASSERT_EQ(a.emit(mnemonic, labelLoop), ErrorCode::None);
ASSERT_EQ(a.dd(0), ErrorCode::None);
ASSERT_EQ(a.bind(labelLoop), ErrorCode::None);

Expand All @@ -1261,6 +1264,66 @@ namespace zasm::tests
ASSERT_EQ(res, ErrorCode::None);
}

TEST(SerializationTests, TestSerializationJcxzBad)
{
TestSerializationLabelRangeBad(MachineMode::I386, x86::Mnemonic::Jcxz, "jcxz");
}

TEST(SerializationTests, TestSerializationJcxzGood)
{
TestSerializationLabelRangeGood(MachineMode::I386, x86::Mnemonic::Jcxz);
}

TEST(SerializationTests, TestSerializationJecxzBad)
{
TestSerializationLabelRangeBad(MachineMode::AMD64, x86::Mnemonic::Jecxz, "jecxz");
}

TEST(SerializationTests, TestSerializationJecxzGood)
{
TestSerializationLabelRangeGood(MachineMode::AMD64, x86::Mnemonic::Jecxz);
}

TEST(SerializationTests, TestSerializationJrcxzBad)
{
TestSerializationLabelRangeBad(MachineMode::AMD64, x86::Mnemonic::Jrcxz, "jrcxz");
}

TEST(SerializationTests, TestSerializationJrcxzGood)
{
TestSerializationLabelRangeGood(MachineMode::AMD64, x86::Mnemonic::Jrcxz);
}

TEST(SerializationTests, TestSerializationLoopBad)
{
TestSerializationLabelRangeBad(MachineMode::AMD64, x86::Mnemonic::Loop, "loop");
}

TEST(SerializationTests, TestSerializationLoopGood)
{
TestSerializationLabelRangeGood(MachineMode::AMD64, x86::Mnemonic::Loop);
}

TEST(SerializationTests, TestSerializationLoopeBad)
{
TestSerializationLabelRangeBad(MachineMode::AMD64, x86::Mnemonic::Loope, "loope");
}

TEST(SerializationTests, TestSerializationLoopeGood)
{
TestSerializationLabelRangeGood(MachineMode::AMD64, x86::Mnemonic::Loope);
}

TEST(SerializationTests, TestSerializationLoopneBad)
{
TestSerializationLabelRangeBad(MachineMode::AMD64, x86::Mnemonic::Loopne, "loopne");
}

TEST(SerializationTests, TestSerializationLoopneGood)
{
TestSerializationLabelRangeGood(MachineMode::AMD64, x86::Mnemonic::Loopne);
}

TEST(SerializationTests, TestBadMemoryDisplacement)
{
Program program(MachineMode::AMD64);
Expand Down
8 changes: 0 additions & 8 deletions zasm/include/zasm/x86/emitter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2086,14 +2086,6 @@ namespace zasm::x86
{
return emit(x86::Mnemonic::Jecxz, a);
}
inline Error jknzd(const Mask& a, const Imm& b)
{
return emit(x86::Mnemonic::Jknzd, a, b);
}
inline Error jkzd(const Mask& a, const Imm& b)
{
return emit(x86::Mnemonic::Jkzd, a, b);
}
inline Error jl(const Imm& a)
{
return emit(x86::Mnemonic::Jl, a);
Expand Down
85 changes: 47 additions & 38 deletions zasm/src/zasm/src/encoder/encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ namespace zasm

static constexpr std::int32_t kHintRequiresSize = -1;

static constexpr auto kAllowedEncodingX86 = static_cast<ZydisEncodableEncoding>(
ZYDIS_ENCODABLE_ENCODING_LEGACY | ZYDIS_ENCODABLE_ENCODING_3DNOW);

static constexpr auto kAllowedEncodingX64 = static_cast<ZydisEncodableEncoding>(
ZYDIS_ENCODABLE_ENCODING_LEGACY | ZYDIS_ENCODABLE_ENCODING_3DNOW | ZYDIS_ENCODABLE_ENCODING_XOP
| ZYDIS_ENCODABLE_ENCODING_VEX | ZYDIS_ENCODABLE_ENCODING_EVEX);

struct EncodeVariantsInfo
{
bool isControlFlow{};
std::int8_t encodeSizeRel8{ -1 };
std::int8_t encodeSizeRel32{ -1 };
std::int8_t cfOperandIndex{ -1 };

constexpr bool canEncodeRel8() const noexcept
{
Expand All @@ -55,32 +63,30 @@ namespace zasm
std::array<EncodeVariantsInfo, ZydisMnemonic::ZYDIS_MNEMONIC_MAX_VALUE> data{};

// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers)
data[ZYDIS_MNEMONIC_JMP] = EncodeVariantsInfo{ true, 2, 5 };
data[ZYDIS_MNEMONIC_JB] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JBE] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JCXZ] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_JECXZ] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_JKNZD] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_JKZD] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_JRCXZ] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_JL] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JLE] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNB] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNBE] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNL] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNLE] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNO] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNP] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNS] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JNZ] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JO] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JP] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JS] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_JZ] = EncodeVariantsInfo{ true, 2, 6 };
data[ZYDIS_MNEMONIC_LOOP] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_LOOPE] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_LOOPNE] = EncodeVariantsInfo{ true, 2, -1 };
data[ZYDIS_MNEMONIC_CALL] = EncodeVariantsInfo{ true, -1, 5 };
data[ZYDIS_MNEMONIC_JMP] = EncodeVariantsInfo{ true, 2, 5, 0 };
data[ZYDIS_MNEMONIC_JB] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JBE] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JCXZ] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_JECXZ] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_JRCXZ] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_JL] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JLE] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNB] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNBE] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNL] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNLE] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNO] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNP] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNS] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JNZ] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JO] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JP] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JS] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_JZ] = EncodeVariantsInfo{ true, 2, 6, 0 };
data[ZYDIS_MNEMONIC_LOOP] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_LOOPE] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_LOOPNE] = EncodeVariantsInfo{ true, 2, -1, 0 };
data[ZYDIS_MNEMONIC_CALL] = EncodeVariantsInfo{ true, -1, 5, 0 };
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers)

return data;
Expand Down Expand Up @@ -194,16 +200,17 @@ namespace zasm
return ErrorCode::None;
}

static int64_t getTemporaryRel(EncoderState& state) noexcept
static int64_t getTemporaryRel(EncoderState& state, const EncodeVariantsInfo& encodeInfo) noexcept
{
auto* ctx = state.ctx;

std::int64_t kTempRel = kTemporaryRel32Value;
std::int64_t kTempRel = 0;

// NOTE: Workaround for some instructions that only accept rel8
if (state.req.mnemonic == ZYDIS_MNEMONIC_JCXZ || state.req.mnemonic == ZYDIS_MNEMONIC_JECXZ
|| state.req.mnemonic == ZYDIS_MNEMONIC_JKNZD || state.req.mnemonic == ZYDIS_MNEMONIC_LOOP
|| state.req.mnemonic == ZYDIS_MNEMONIC_LOOPE || state.req.mnemonic == ZYDIS_MNEMONIC_LOOPNE)
if (encodeInfo.canEncodeRel32())
{
kTempRel = kTemporaryRel32Value;
}
else if (encodeInfo.canEncodeRel8())
{
kTempRel = kTemporaryRel8Value;
}
Expand All @@ -217,9 +224,10 @@ namespace zasm
auto* ctx = state.ctx;
auto desiredBranchType = ZydisBranchType::ZYDIS_BRANCH_TYPE_NONE;

// Initially a temporary placeholder. Make sure this is within rel32 if a
// context is provided.
std::int64_t immValue = getTemporaryRel(state);
const auto& encodeInfo = getEncodeVariantInfo(state.req.mnemonic);

// Initially a temporary placeholder.
std::int64_t immValue = getTemporaryRel(state, encodeInfo);

std::optional<std::int64_t> labelVA;
if (ctx != nullptr && !isLabelExternal(ctx->program, src.getId()))
Expand All @@ -232,8 +240,7 @@ namespace zasm
}

// Check if this operand is used as the control flow target.
const auto& encodeInfo = getEncodeVariantInfo(state.req.mnemonic);
if (state.operandIndex == 0 && encodeInfo.isControlFlow)
if (encodeInfo.isControlFlow && state.operandIndex == encodeInfo.cfOperandIndex)
{
const auto targetAddress = labelVA.has_value() ? *labelVA : immValue;

Expand Down Expand Up @@ -288,7 +295,7 @@ namespace zasm

// Check if this operand is used as the control flow target.
const auto& encodeInfo = getEncodeVariantInfo(state.req.mnemonic);
if (state.operandIndex == 0 && encodeInfo.isControlFlow)
if (encodeInfo.isControlFlow && state.operandIndex == encodeInfo.cfOperandIndex)
{
const auto targetAddress = immValue;
const auto [addrRel, branchType] = processRelAddress(encodeInfo, ctx, targetAddress);
Expand Down Expand Up @@ -532,10 +539,12 @@ namespace zasm
if (mode == MachineMode::AMD64)
{
req.machine_mode = ZYDIS_MACHINE_MODE_LONG_64;
req.allowed_encodings = kAllowedEncodingX64;
}
else if (mode == MachineMode::I386)
{
req.machine_mode = ZYDIS_MACHINE_MODE_LONG_COMPAT_32;
req.allowed_encodings = kAllowedEncodingX86;
}
req.mnemonic = static_cast<ZydisMnemonic>(mnemonic.value());
req.prefixes = getAttribs(attribs);
Expand Down

0 comments on commit 5bf4e65

Please sign in to comment.