diff --git a/include/Zydis/Encoder.h b/include/Zydis/Encoder.h index 8748ae86..a890c8cc 100644 --- a/include/Zydis/Encoder.h +++ b/include/Zydis/Encoder.h @@ -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; /** @@ -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; /** diff --git a/src/Encoder.c b/src/Encoder.c index 39a78282..c64ea1bc 100644 --- a/src/Encoder.c +++ b/src/Encoder.c @@ -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]; } /** @@ -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. * @@ -491,7 +505,6 @@ 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: @@ -499,6 +512,7 @@ static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding) 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: @@ -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 { @@ -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: @@ -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) { @@ -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) { @@ -1933,9 +1954,15 @@ 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; } @@ -1943,37 +1970,22 @@ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *mat { 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; @@ -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; @@ -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; } @@ -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;