Skip to content

Commit

Permalink
Uniform displacement treatment
Browse files Browse the repository at this point in the history
  • Loading branch information
mappzor authored and athre0z committed Feb 4, 2024
1 parent 88f88c6 commit df34a98
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 74 deletions.
18 changes: 11 additions & 7 deletions include/Zydis/Encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ typedef struct ZydisEncoderOperand_
*/
ZyanU8 scale;
/**
* The displacement value.
* The displacement value. This value is always treated as 64-bit signed integer, so it's
* important to take this into account when specifying absolute addresses. For example
* to specify a 16-bit address 0x8000 in 16-bit mode it should be sign extended to
* `0xFFFFFFFFFFFF8000`. See `address_size_hint` for more information about absolute
* addresses.
*/
ZyanI64 displacement;
/**
Expand Down Expand Up @@ -303,12 +307,12 @@ typedef struct ZydisEncoderRequest_
* explicit and implicit operands. This hint resolves conflicts when instruction's hidden
* operands scale with address size attribute.
*
* This hint is also used for instructions with absolute memory addresses (memory operands
* with displacement and no registers). Since displacement field is a 64-bit signed integer
* it's not possible to determine desired address mode correctly in all situations. Use
* `ZYDIS_ADDRESS_SIZE_HINT_NONE` to prefer address size default for specified machine mode.
* All other `ZYDIS_ADDRESS_SIZE_*` values will force specific address size or cause encoding
* to fail when it isn't possible to encode address provided.
* This hint is also used for instructions with absolute memory addresses (memory operands with
* displacement and no registers). Since displacement field is a 64-bit signed integer it's not
* possible to determine actual size of the address value in all situations. This hint
* specifies size of the address value provided inside encoder request rather than desired
* address size attribute of encoded instruction. Use `ZYDIS_ADDRESS_SIZE_HINT_NONE` to assume
* address size default for specified machine mode.
*/
ZydisAddressSizeHint address_size_hint;
/**
Expand Down
138 changes: 71 additions & 67 deletions src/Encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,20 +286,17 @@ static ZydisEncodableEncoding ZydisGetEncodableEncoding(ZydisInstructionEncoding
*/
static ZyanU8 ZydisGetMachineModeWidth(ZydisMachineMode machine_mode)
{
switch (machine_mode)
{
case ZYDIS_MACHINE_MODE_REAL_16:
case ZYDIS_MACHINE_MODE_LEGACY_16:
case ZYDIS_MACHINE_MODE_LONG_COMPAT_16:
return 16;
case ZYDIS_MACHINE_MODE_LEGACY_32:
case ZYDIS_MACHINE_MODE_LONG_COMPAT_32:
return 32;
case ZYDIS_MACHINE_MODE_LONG_64:
return 64;
default:
ZYAN_UNREACHABLE;
}
ZYAN_ASSERT((ZyanUSize)machine_mode <= ZYDIS_MACHINE_MODE_MAX_VALUE);
static const ZyanU8 lookup[6] =
{
/* ZYDIS_MACHINE_MODE_LONG_64 */ 64,
/* ZYDIS_MACHINE_MODE_LONG_COMPAT_32 */ 32,
/* ZYDIS_MACHINE_MODE_LONG_COMPAT_16 */ 16,
/* ZYDIS_MACHINE_MODE_LEGACY_32 */ 32,
/* ZYDIS_MACHINE_MODE_LEGACY_16 */ 16,
/* ZYDIS_MACHINE_MODE_REAL_16 */ 16,
};
return lookup[machine_mode];
}

/**
Expand Down Expand Up @@ -330,6 +327,23 @@ static ZyanU8 ZydisGetOszFromHint(ZydisOperandSizeHint hint)
return lookup[hint];
}

/**
* Calculates maximum size of absolute address value based on address size hint.
*
* @param request A pointer to `ZydisEncoderRequest` struct.
*
* @return Maximum address size in bits.
*/
static ZyanU8 ZydisGetMaxAddressSize(const ZydisEncoderRequest *request)
{
ZyanU8 addr_size = ZydisGetAszFromHint(request->address_size_hint);
if (addr_size == 0)
{
addr_size = ZydisGetMachineModeWidth(request->machine_mode);
}
return addr_size;
}

/**
* Calculates effective operand size.
*
Expand Down Expand Up @@ -491,14 +505,14 @@ static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding)
case ZYDIS_OPERAND_ENCODING_JIMM16_32_64:
case ZYDIS_OPERAND_ENCODING_JIMM32_32_64:
case ZYDIS_OPERAND_ENCODING_JIMM16_32_32:
return ZYAN_TRUE;
case ZYDIS_OPERAND_ENCODING_DISP8:
case ZYDIS_OPERAND_ENCODING_DISP16:
case ZYDIS_OPERAND_ENCODING_DISP32:
case ZYDIS_OPERAND_ENCODING_DISP64:
case ZYDIS_OPERAND_ENCODING_DISP16_32_64:
case ZYDIS_OPERAND_ENCODING_DISP32_32_64:
case ZYDIS_OPERAND_ENCODING_DISP16_32_32:
return ZYAN_TRUE;
case ZYDIS_OPERAND_ENCODING_UIMM8:
case ZYDIS_OPERAND_ENCODING_UIMM16:
case ZYDIS_OPERAND_ENCODING_UIMM32:
Expand Down Expand Up @@ -571,17 +585,21 @@ static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, Zyan
return ZydisGetScaledImmSize(match, simm16_32_32_sizes, min_size);
}
case ZYDIS_OPERAND_ENCODING_DISP16_32_64:
{
ZYAN_ASSERT(match->easz == 0);
const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request);
const ZyanU64 uimm = imm & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1));
if (min_size < addr_size && ZydisGetUnsignedImmSize(uimm) > min_size)
{
min_size = addr_size;
}
if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
{
if (min_size < 32)
{
min_size = 32;
}
if (min_size == 32 || min_size == 64)
{
match->easz = eisz = min_size;
}
match->easz = eisz = min_size;
}
else
{
Expand All @@ -595,6 +613,7 @@ static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, Zyan
}
}
break;
}
case ZYDIS_OPERAND_ENCODING_JIMM8:
case ZYDIS_OPERAND_ENCODING_JIMM16:
case ZYDIS_OPERAND_ENCODING_JIMM32:
Expand Down Expand Up @@ -1569,7 +1588,7 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat
{
return ZYAN_FALSE;
}
ZyanI64 displacement = user_op->mem.displacement;
const ZyanI64 displacement = user_op->mem.displacement;
ZyanU8 disp_size = 0;
if (displacement)
{
Expand All @@ -1586,7 +1605,9 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat
if (!(displacement & mask))
{
if (ZydisGetSignedImmSize(displacement >> match->cd8_scale) == 8)
{
disp_size = 8;
}
}
else if (disp_size == 8)
{
Expand Down Expand Up @@ -1933,47 +1954,38 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat
}
else if (disp_size != 8 || !match->cd8_scale)
{
const ZyanU8 addr_size = ZydisGetMaxAddressSize(match->request);
if (disp_size > addr_size)
{
return ZYAN_FALSE;
}
ZyanU8 min_disp_size = match->easz ? match->easz : 16;
if (((min_disp_size == 16) && !(match->definition->address_sizes & ZYDIS_WIDTH_16)) ||
(min_disp_size == 64))
(min_disp_size == 64) ||
(match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64))
{
min_disp_size = 32;
}
if (disp_size < min_disp_size)
{
disp_size = min_disp_size;
}
const ZyanU8 mode_width = ZydisGetMachineModeWidth(match->request->machine_mode);
switch (match->request->address_size_hint)
const ZyanI64 disp = user_op->mem.displacement;
if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)
{
case ZYDIS_ADDRESS_SIZE_HINT_NONE:
if (mode_width >= 32)
{
disp_size = 32;
}
if (mode_width == 64)
candidate_easz = addr_size;
if (addr_size == 32 && disp >= 0 && match->easz != 32)
{
candidate_easz = 64;
}
break;
case ZYDIS_ADDRESS_SIZE_HINT_16:
if (disp_size != 16)
{
return ZYAN_FALSE;
}
break;
case ZYDIS_ADDRESS_SIZE_HINT_32:
disp_size = 32;
break;
case ZYDIS_ADDRESS_SIZE_HINT_64:
disp_size = 32;
candidate_easz = 64;
break;
default:
ZYAN_UNREACHABLE;
}
if (candidate_easz == 0)
else
{
const ZyanU64 uimm = disp & (~(0xFFFFFFFFFFFFFFFFULL << (addr_size - 1) << 1));
if (disp_size < addr_size && ZydisGetUnsignedImmSize(uimm) > disp_size)
{
disp_size = addr_size;
}
candidate_easz = disp_size;
}
disp_only = ZYAN_TRUE;
Expand Down Expand Up @@ -2025,12 +2037,18 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat
break;
}
case ZYDIS_SEMANTIC_OPTYPE_MOFFS:
{
if (user_op->mem.base != ZYDIS_REGISTER_NONE ||
user_op->mem.index != ZYDIS_REGISTER_NONE ||
user_op->mem.scale != 0)
{
return ZYAN_FALSE;
}
const ZyanU8 min_disp_size = ZydisGetSignedImmSize(user_op->mem.displacement);
if (min_disp_size > ZydisGetMaxAddressSize(match->request))
{
return ZYAN_FALSE;
}
if (match->eosz != 0)
{
const ZyanU8 eosz_index = match->eosz >> 5;
Expand All @@ -2053,23 +2071,15 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat
{
return ZYAN_FALSE;
}
// This is not a standard rejection. It's a special case for `mov` instructions (only ones
// to use `moffs` operands). Size of `moffs` is tied to address size attribute, so its
// signedness doesn't matter. However if displacement can be represented as a signed
// integer of smaller size we reject `moffs` variant because it's guaranteed that better
// alternative exists (in terms of size).
ZyanU8 alternative_size = ZydisGetSignedImmSize(user_op->mem.displacement);
const ZyanU8 min_disp_size =
(match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) ? 32 : 16;
if (alternative_size < min_disp_size)
{
alternative_size = min_disp_size;
}
if (alternative_size < match->disp_size)
// This is not a standard rejection. It's a special case for `mov` instructions (`moffs`
// variants only). In 64-bit mode it's possible to get a shorter encoding for addresses
// that can fit into 32-bit displacements.
if (match->disp_size == 64 && min_disp_size < match->disp_size)
{
return ZYAN_FALSE;
}
break;
}
default:
ZYAN_UNREACHABLE;
}
Expand Down Expand Up @@ -4679,15 +4689,9 @@ ZYDIS_EXPORT ZyanStatus ZydisEncoderDecodedInstructionToEncoderRequest(
enc_op->mem.base = dec_op->mem.base;
enc_op->mem.index = dec_op->mem.index;
enc_op->mem.scale = dec_op->mem.type != ZYDIS_MEMOP_TYPE_MIB ? dec_op->mem.scale : 0;
if (dec_op->encoding == ZYDIS_OPERAND_ENCODING_DISP16_32_64)
{
ZydisCalcAbsoluteAddress(instruction, dec_op, 0,
(ZyanU64 *)&enc_op->mem.displacement);
}
else
if (dec_op->mem.disp.has_displacement)
{
enc_op->mem.displacement = dec_op->mem.disp.has_displacement ?
dec_op->mem.disp.value : 0;
enc_op->mem.displacement = dec_op->mem.disp.value;
}
enc_op->mem.size = dec_op->size / 8;
break;
Expand Down

0 comments on commit df34a98

Please sign in to comment.