/*************************************************************************************************** Zyan Disassembler Library (Zydis) Original Author : Mappa * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ***************************************************************************************************/ // ReSharper disable CppClangTidyClangDiagnosticSwitchEnum // ReSharper disable CppClangTidyClangDiagnosticCoveredSwitchDefault // ReSharper disable CppClangTidyClangDiagnosticImplicitFallthrough #include #include #include #include #include /* ============================================================================================== */ /* Macros */ /* ============================================================================================== */ /* ---------------------------------------------------------------------------------------------- */ /* Constants */ /* ---------------------------------------------------------------------------------------------- */ #define ZYDIS_OPSIZE_MAP_BYTEOP 1 #define ZYDIS_OPSIZE_MAP_DEFAULT64 4 #define ZYDIS_OPSIZE_MAP_FORCE64 5 #define ZYDIS_ADSIZE_MAP_IGNORED 1 #define ZYDIS_LEGACY_SEGMENTS (ZYDIS_ATTRIB_HAS_SEGMENT_CS | \ ZYDIS_ATTRIB_HAS_SEGMENT_SS | \ ZYDIS_ATTRIB_HAS_SEGMENT_DS | \ ZYDIS_ATTRIB_HAS_SEGMENT_ES) #define ZYDIS_ENCODABLE_PREFIXES_NO_SEGMENTS (ZYDIS_ENCODABLE_PREFIXES ^ \ ZYDIS_ATTRIB_HAS_SEGMENT) /* ---------------------------------------------------------------------------------------------- */ /* ============================================================================================== */ /* Internal enums and types */ /* ============================================================================================== */ /** * Usage of `REX.W` prefix makes it impossible to use some byte-sized registers. Values of this * enum are used to track and facilitate enforcement of these restrictions. */ typedef enum ZydisEncoderRexType_ { ZYDIS_REX_TYPE_UNKNOWN, ZYDIS_REX_TYPE_REQUIRED, ZYDIS_REX_TYPE_FORBIDDEN, /** * Maximum value of this enum. */ ZYDIS_REX_TYPE_MAX_VALUE = ZYDIS_REX_TYPE_FORBIDDEN, /** * The minimum number of bits required to represent all values of this enum. */ ZYDIS_REX_TYPE_REQUIRED_BITS = ZYAN_BITS_TO_REPRESENT(ZYDIS_REX_TYPE_MAX_VALUE) } ZydisEncoderRexType; /** * Primary structure used during instruction matching phase. Once filled it contains information * about matched instruction definition and some values deduced from encoder request. It gets * converted to `ZydisEncoderInstruction` during instruction building phase. */ typedef struct ZydisEncoderInstructionMatch_ { /** * A pointer to the `ZydisEncoderRequest` instance. */ const ZydisEncoderRequest *request; /** * A pointer to the `ZydisEncodableInstruction` instance. */ const ZydisEncodableInstruction *definition; /** * A pointer to the `ZydisInstructionDefinition` instance. */ const ZydisInstructionDefinition *base_definition; /** * A pointer to the `ZydisOperandDefinition` array. */ const ZydisOperandDefinition *operands; /** * Encodable attributes for this instruction. */ ZydisInstructionAttributes attributes; /** * Effective operand size attribute. */ ZyanU8 eosz; /** * Effective address size attribute. */ ZyanU8 easz; /** * Effective displacement size. */ ZyanU8 disp_size; /** * Effective immediate size. */ ZyanU8 imm_size; /** * Exponent of compressed displacement scale factor (2^cd8_scale) */ ZyanU8 cd8_scale; /** * `REX` prefix constraints. */ ZydisEncoderRexType rex_type; /** * True for special cases where operand size attribute must be lower than 64 bits. */ ZyanBool eosz64_forbidden; /** * True when instruction definition has relative operand (used for branching instructions). */ ZyanBool has_rel_operand; } ZydisEncoderInstructionMatch; /** * Encapsulates information about writable buffer. */ typedef struct ZydisEncoderBuffer_ { /** * A pointer to actual data buffer. */ ZyanU8 *buffer; /** * Size of this buffer. */ ZyanUSize size; /** * Current write offset. */ ZyanUSize offset; } ZydisEncoderBuffer; /** * Low-level instruction representation. Once filled this structure contains all information * required for final instruction emission phase. */ typedef struct ZydisEncoderInstruction_ { /** * Encodable attributes for this instruction. */ ZydisInstructionAttributes attributes; /** * The instruction encoding. */ ZydisInstructionEncoding encoding; /** * The opcode map. */ ZydisOpcodeMap opcode_map; /** * The opcode. */ ZyanU8 opcode; /** * The `vvvv` field (`VEX`, `EVEX`, `MVEX`, `XOP`). */ ZyanU8 vvvv; /** * The `sss` field (`MVEX`). */ ZyanU8 sss; /** * The mask register ID. */ ZyanU8 mask; /** * The vector length. */ ZyanU8 vector_length; /** * The `mod` component of Mod/RM byte. */ ZyanU8 mod; /** * The `reg` component of Mod/RM byte. */ ZyanU8 reg; /** * The `rm` component of Mod/RM byte. */ ZyanU8 rm; /** * The scale component of SIB byte. */ ZyanU8 scale; /** * The index component of SIB byte. */ ZyanU8 index; /** * The base component of SIB byte. */ ZyanU8 base; /** * The `REX.W` bit. */ ZyanBool rex_w; /** * True if using zeroing mask (`EVEX`). */ ZyanBool zeroing; /** * True if using eviction hint (`MVEX`). */ ZyanBool eviction_hint; /** * Size of displacement value. */ ZyanU8 disp_size; /** * Size of immediate value. */ ZyanU8 imm_size; /** * The displacement value. */ ZyanU64 disp; /** * The immediate value. */ ZyanU64 imm; } ZydisEncoderInstruction; /* ============================================================================================== */ /* Internal functions */ /* ============================================================================================== */ /** * Converts `ZydisInstructionEncoding` to `ZydisEncodableEncoding`. * * @param encoding `ZydisInstructionEncoding` value to convert. * * @return Equivalent `ZydisEncodableEncoding` value. */ static ZydisEncodableEncoding ZydisGetEncodableEncoding(ZydisInstructionEncoding encoding) { static const ZydisEncodableEncoding encoding_lookup[6] = { ZYDIS_ENCODABLE_ENCODING_LEGACY, ZYDIS_ENCODABLE_ENCODING_3DNOW, ZYDIS_ENCODABLE_ENCODING_XOP, ZYDIS_ENCODABLE_ENCODING_VEX, ZYDIS_ENCODABLE_ENCODING_EVEX, ZYDIS_ENCODABLE_ENCODING_MVEX, }; ZYAN_ASSERT((ZyanUSize)encoding <= ZYDIS_INSTRUCTION_ENCODING_MAX_VALUE); return encoding_lookup[encoding]; } /** * Converts `ZydisMachineMode` to default stack width value expressed in bits. * * @param machine_mode `ZydisMachineMode` value to convert. * * @return Stack width for requested machine mode. */ 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; } } /** * Converts `ZydisAddressSizeHint` to address size expressed in bits. * * @param hint Address size hint. * * @return Address size in bits. */ static ZyanU8 ZydisGetAszFromHint(ZydisAddressSizeHint hint) { ZYAN_ASSERT((ZyanUSize)hint <= ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE); static const ZyanU8 lookup[ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE + 1] = { 0, 16, 32, 64 }; return lookup[hint]; } /** * Converts `ZydisOperandSizeHint` to operand size expressed in bits. * * @param hint Operand size hint. * * @return Operand size in bits. */ static ZyanU8 ZydisGetOszFromHint(ZydisOperandSizeHint hint) { ZYAN_ASSERT((ZyanUSize)hint <= ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE); static const ZyanU8 lookup[ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE + 1] = { 0, 8, 16, 32, 64 }; return lookup[hint]; } /** * Calculates effective operand size. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param size_table Array of possible size values for different operand sizes. * @param desired_size Operand size requested by caller. * @param exact_match_mode True if desired_size must be matched exactly, false when * "not lower than" matching is desired. * * @return Effective operand size in bits. */ static ZyanU8 ZydisGetOperandSizeFromElementSize(ZydisEncoderInstructionMatch *match, const ZyanU16 *size_table, ZyanU16 desired_size, ZyanBool exact_match_mode) { if ((match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_DEFAULT64) && (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)) { if ((exact_match_mode && (size_table[2] == desired_size)) || (!exact_match_mode && (size_table[2] >= desired_size))) { return 64; } else if (size_table[0] == desired_size) { return 16; } } else if ((match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64) && (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64)) { if (size_table[2] == desired_size) { return 64; } } else { static const ZyanI8 eosz_priority_lookup[4][3] = { { 0, 1, -1 }, { 1, 0, -1 }, { 1, 2, 0 }, }; const ZyanU8 eosz_index = ZydisGetMachineModeWidth(match->request->machine_mode) >> 5; for (int i = 0; i < 3; ++i) { const ZyanI8 eosz_candidate = eosz_priority_lookup[eosz_index][i]; if ((eosz_candidate == -1) || !(match->definition->operand_sizes & (1 << eosz_candidate))) { continue; } if ((exact_match_mode && (size_table[eosz_candidate] == desired_size)) || (!exact_match_mode && (size_table[eosz_candidate] >= desired_size))) { return 16 << eosz_candidate; } } } return 0; } /** * Calculates effective immediate size. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param size_table Array of possible size values for different operand sizes. * @param min_imm_size Minimum immediate size. * * @return Effective operand size in bits. */ static ZyanU8 ZydisGetScaledImmSize(ZydisEncoderInstructionMatch *match, const ZyanU16 *size_table, ZyanU8 min_imm_size) { if (match->eosz == 0) { match->eosz = ZydisGetOperandSizeFromElementSize(match, size_table, min_imm_size, ZYAN_FALSE); return match->eosz != 0 ? (ZyanU8)size_table[match->eosz >> 5] : 0; } const ZyanU8 index = match->eosz >> 5; return size_table[index] >= min_imm_size ? (ZyanU8)size_table[index] : 0; } /** * Calculates size of smallest integral type able to represent provided signed value. * * @param imm Immediate to be represented. * * @return Size of smallest integral type able to represent provided signed value. */ static ZyanU8 ZydisGetSignedImmSize(ZyanI64 imm) { if (imm >= ZYAN_INT8_MIN && imm <= ZYAN_INT8_MAX) { return 8; } if (imm >= ZYAN_INT16_MIN && imm <= ZYAN_INT16_MAX) { return 16; } if (imm >= ZYAN_INT32_MIN && imm <= ZYAN_INT32_MAX) { return 32; } return 64; } /** * Calculates size of smallest integral type able to represent provided unsigned value. * * @param imm Immediate to be represented. * * @return Size of smallest integral type able to represent provided unsigned value. */ static ZyanU8 ZydisGetUnsignedImmSize(ZyanU64 imm) { if (imm <= ZYAN_UINT8_MAX) { return 8; } if (imm <= ZYAN_UINT16_MAX) { return 16; } if (imm <= ZYAN_UINT32_MAX) { return 32; } return 64; } /** * Checks if operand encoding encodes a signed immediate value. * * @param encoding Operand encoding for immediate value. * * @return True for encodings that represent signed values, false otherwise. */ static ZyanBool ZydisIsImmSigned(ZydisOperandEncoding encoding) { switch (encoding) { case ZYDIS_OPERAND_ENCODING_SIMM8: case ZYDIS_OPERAND_ENCODING_SIMM16: case ZYDIS_OPERAND_ENCODING_SIMM32: case ZYDIS_OPERAND_ENCODING_SIMM64: case ZYDIS_OPERAND_ENCODING_SIMM16_32_64: case ZYDIS_OPERAND_ENCODING_SIMM32_32_64: case ZYDIS_OPERAND_ENCODING_SIMM16_32_32: case ZYDIS_OPERAND_ENCODING_JIMM8: case ZYDIS_OPERAND_ENCODING_JIMM16: case ZYDIS_OPERAND_ENCODING_JIMM32: case ZYDIS_OPERAND_ENCODING_JIMM64: 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: case ZYDIS_OPERAND_ENCODING_UIMM8: case ZYDIS_OPERAND_ENCODING_UIMM16: case ZYDIS_OPERAND_ENCODING_UIMM32: case ZYDIS_OPERAND_ENCODING_UIMM64: case ZYDIS_OPERAND_ENCODING_UIMM16_32_64: case ZYDIS_OPERAND_ENCODING_UIMM32_32_64: case ZYDIS_OPERAND_ENCODING_UIMM16_32_32: case ZYDIS_OPERAND_ENCODING_IS4: return ZYAN_FALSE; default: ZYAN_UNREACHABLE; } } /** * Calculates effective immediate size. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param imm Immediate value to encode. * @param def_op Operand definition for immediate operand. * * @return Effective operand size in bits (0 if function failed). */ static ZyanU8 ZydisGetEffectiveImmSize(ZydisEncoderInstructionMatch *match, ZyanI64 imm, const ZydisOperandDefinition *def_op) { ZyanU8 eisz = 0; ZyanU8 min_size = ZydisIsImmSigned((ZydisOperandEncoding)def_op->op.encoding) ? ZydisGetSignedImmSize(imm) : ZydisGetUnsignedImmSize((ZyanU64)imm); switch (def_op->op.encoding) { case ZYDIS_OPERAND_ENCODING_UIMM8: case ZYDIS_OPERAND_ENCODING_SIMM8: eisz = 8; break; case ZYDIS_OPERAND_ENCODING_IS4: ZYAN_ASSERT(def_op->element_type == ZYDIS_IELEMENT_TYPE_UINT8); eisz = ((ZyanU64)imm <= 15) ? 8 : 0; break; case ZYDIS_OPERAND_ENCODING_UIMM16: case ZYDIS_OPERAND_ENCODING_SIMM16: eisz = 16; break; case ZYDIS_OPERAND_ENCODING_UIMM32: case ZYDIS_OPERAND_ENCODING_SIMM32: eisz = 32; break; case ZYDIS_OPERAND_ENCODING_UIMM64: case ZYDIS_OPERAND_ENCODING_SIMM64: eisz = 64; break; case ZYDIS_OPERAND_ENCODING_UIMM16_32_64: case ZYDIS_OPERAND_ENCODING_SIMM16_32_64: { static const ZyanU16 simm16_32_64_sizes[3] = { 16, 32, 64 }; return ZydisGetScaledImmSize(match, simm16_32_64_sizes, min_size); } case ZYDIS_OPERAND_ENCODING_UIMM32_32_64: case ZYDIS_OPERAND_ENCODING_SIMM32_32_64: { static const ZyanU16 simm32_32_64_sizes[3] = { 32, 32, 64 }; return ZydisGetScaledImmSize(match, simm32_32_64_sizes, min_size); } case ZYDIS_OPERAND_ENCODING_UIMM16_32_32: case ZYDIS_OPERAND_ENCODING_SIMM16_32_32: { static const ZyanU16 simm16_32_32_sizes[3] = { 16, 32, 32 }; return ZydisGetScaledImmSize(match, simm16_32_32_sizes, min_size); } case ZYDIS_OPERAND_ENCODING_DISP16_32_64: ZYAN_ASSERT(match->easz == 0); 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; } } else { if (min_size < 16) { min_size = 16; } if (min_size == 16 || min_size == 32) { match->easz = eisz = min_size; } } break; case ZYDIS_OPERAND_ENCODING_JIMM8: case ZYDIS_OPERAND_ENCODING_JIMM16: case ZYDIS_OPERAND_ENCODING_JIMM32: case ZYDIS_OPERAND_ENCODING_JIMM64: { ZyanU8 jimm_index = def_op->op.encoding - ZYDIS_OPERAND_ENCODING_JIMM8; if ((match->request->branch_width != ZYDIS_BRANCH_WIDTH_NONE) && (match->request->branch_width != (ZydisBranchWidth)(ZYDIS_BRANCH_WIDTH_8 + jimm_index))) { return 0; } eisz = 8 << jimm_index; break; } case ZYDIS_OPERAND_ENCODING_JIMM16_32_32: switch (match->request->branch_width) { case ZYDIS_BRANCH_WIDTH_NONE: { static const ZyanU16 jimm16_32_32_sizes[3] = { 16, 32, 32 }; return ZydisGetScaledImmSize(match, jimm16_32_32_sizes, min_size); } case ZYDIS_BRANCH_WIDTH_16: eisz = 16; break; case ZYDIS_BRANCH_WIDTH_32: eisz = 32; break; case ZYDIS_BRANCH_WIDTH_8: case ZYDIS_BRANCH_WIDTH_64: return 0; default: ZYAN_UNREACHABLE; } break; default: ZYAN_UNREACHABLE; } return eisz >= min_size ? eisz : 0; } /** * Checks if register width is compatible with effective operand size. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param reg_width Register width in bits. * * @return True if width is compatible, false otherwise. */ static ZyanBool ZydisCheckOsz(ZydisEncoderInstructionMatch *match, ZydisRegisterWidth reg_width) { ZYAN_ASSERT(reg_width <= ZYAN_UINT8_MAX); if (match->eosz == 0) { if (reg_width == 8) { return ZYAN_FALSE; } match->eosz = (ZyanU8)reg_width; return ZYAN_TRUE; } return match->eosz == (ZyanU8)reg_width ? ZYAN_TRUE : ZYAN_FALSE; } /** * Checks if register width is compatible with effective address size. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param reg_width Register width in bits. * * @return True if width is compatible, false otherwise. */ static ZyanBool ZydisCheckAsz(ZydisEncoderInstructionMatch *match, ZydisRegisterWidth reg_width) { ZYAN_ASSERT(reg_width <= ZYAN_UINT8_MAX); if (match->easz == 0) { if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) && (reg_width == 16)) { return ZYAN_FALSE; } match->easz = (ZyanU8)reg_width; return ZYAN_TRUE; } return match->easz == (ZyanU8)reg_width ? ZYAN_TRUE : ZYAN_FALSE; } /** * Checks if specified register is valid for provided register class, encoding and machine mode. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param reg `ZydisRegister` value. * @param reg_class Register class. * * @return True if register value is allowed, false otherwise. */ static ZyanBool ZydisIsRegisterAllowed(ZydisEncoderInstructionMatch *match, ZydisRegister reg, ZydisRegisterClass reg_class) { const ZyanI8 reg_id = ZydisRegisterGetId(reg); ZYAN_ASSERT(reg_id >= 0 && reg_id <= 31); if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { if ((match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_EVEX) && (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_MVEX) && (reg_class != ZYDIS_REGCLASS_GPR8) && (reg_id >= 16)) { return ZYAN_FALSE; } } else { if (reg_class == ZYDIS_REGCLASS_GPR64) { return ZYAN_FALSE; } if (reg_id >= 8) { return ZYAN_FALSE; } } return ZYAN_TRUE; } /** * Checks if specified scale value is valid for use with SIB addressing. * * @param scale Scale value. * * @return True if value is valid, false otherwise. */ static ZyanBool ZydisIsScaleValid(ZyanU8 scale) { switch (scale) { case 0: case 1: case 2: case 4: case 8: return ZYAN_TRUE; default: return ZYAN_FALSE; } } /** * Enforces register usage constraints associated with usage of `REX` prefix. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param reg `ZydisRegister` value. * @param addressing_mode True if checked address is used for address calculations. This * implies more permissive checks. * * @return True if register usage is allowed, false otherwise. */ static ZyanBool ZydisValidateRexType(ZydisEncoderInstructionMatch *match, ZydisRegister reg, ZyanBool addressing_mode) { switch (reg) { case ZYDIS_REGISTER_AL: case ZYDIS_REGISTER_CL: case ZYDIS_REGISTER_DL: case ZYDIS_REGISTER_BL: return ZYAN_TRUE; case ZYDIS_REGISTER_AH: case ZYDIS_REGISTER_CH: case ZYDIS_REGISTER_DH: case ZYDIS_REGISTER_BH: if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN) { match->rex_type = ZYDIS_REX_TYPE_FORBIDDEN; } else if (match->rex_type == ZYDIS_REX_TYPE_REQUIRED) { return ZYAN_FALSE; } break; case ZYDIS_REGISTER_SPL: case ZYDIS_REGISTER_BPL: case ZYDIS_REGISTER_SIL: case ZYDIS_REGISTER_DIL: case ZYDIS_REGISTER_R8B: case ZYDIS_REGISTER_R9B: case ZYDIS_REGISTER_R10B: case ZYDIS_REGISTER_R11B: case ZYDIS_REGISTER_R12B: case ZYDIS_REGISTER_R13B: case ZYDIS_REGISTER_R14B: case ZYDIS_REGISTER_R15B: if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN) { match->rex_type = ZYDIS_REX_TYPE_REQUIRED; } else if (match->rex_type == ZYDIS_REX_TYPE_FORBIDDEN) { return ZYAN_FALSE; } break; default: if ((ZydisRegisterGetId(reg) > 7) || (!addressing_mode && (ZydisRegisterGetClass(reg) == ZYDIS_REGCLASS_GPR64))) { if (match->rex_type == ZYDIS_REX_TYPE_UNKNOWN) { match->rex_type = ZYDIS_REX_TYPE_REQUIRED; } else if (match->rex_type == ZYDIS_REX_TYPE_FORBIDDEN) { return ZYAN_FALSE; } } break; } return ZYAN_TRUE; } /** * Checks if specified register is valid for use with SIB addressing. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param reg_class Register class. * @param reg `ZydisRegister` value. * * @return True if register value is allowed, false otherwise. */ static ZyanBool ZydisIsValidAddressingClass(ZydisEncoderInstructionMatch *match, ZydisRegisterClass reg_class, ZydisRegister reg) { ZyanBool result; const ZyanBool is_64 = (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64); switch (reg_class) { case ZYDIS_REGCLASS_INVALID: return ZYAN_TRUE; case ZYDIS_REGCLASS_GPR16: result = !is_64; break; case ZYDIS_REGCLASS_GPR32: result = is_64 || ZydisRegisterGetId(reg) < 8; break; case ZYDIS_REGCLASS_GPR64: result = is_64; break; default: return ZYAN_FALSE; } return result && ZydisValidateRexType(match, reg, ZYAN_TRUE); } /** * Helper function that determines correct `ModR/M.RM` value for 16-bit addressing mode. * * @param base `ZydisRegister` used as `SIB.base`. * @param index `ZydisRegister` used as `SIB.index`. * * @return `ModR/M.RM` value (-1 if function failed). */ static ZyanI8 ZydisGetRm16(ZydisRegister base, ZydisRegister index) { static const ZydisRegister modrm16_lookup[8][2] = { { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_SI }, { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_DI }, { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_SI }, { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_DI }, { ZYDIS_REGISTER_SI, ZYDIS_REGISTER_NONE }, { ZYDIS_REGISTER_DI, ZYDIS_REGISTER_NONE }, { ZYDIS_REGISTER_BP, ZYDIS_REGISTER_NONE }, { ZYDIS_REGISTER_BX, ZYDIS_REGISTER_NONE }, }; for (ZyanI8 i = 0; i < (ZyanI8)ZYAN_ARRAY_LENGTH(modrm16_lookup); ++i) { if ((modrm16_lookup[i][0] == base) && (modrm16_lookup[i][1] == index)) { return i; } } return -1; } /** * Encodes `MVEX.sss` field for specified broadcast mode. * * @param broadcast Broadcast mode. * * @return Corresponding `MVEX.sss` value. */ static ZyanU8 ZydisEncodeMvexBroadcastMode(ZydisBroadcastMode broadcast) { switch (broadcast) { case ZYDIS_BROADCAST_MODE_INVALID: return 0; case ZYDIS_BROADCAST_MODE_1_TO_16: case ZYDIS_BROADCAST_MODE_1_TO_8: return 1; case ZYDIS_BROADCAST_MODE_4_TO_16: case ZYDIS_BROADCAST_MODE_4_TO_8: return 2; default: ZYAN_UNREACHABLE; } } /** * Encodes `MVEX.sss` field for specified conversion mode. * * @param conversion Conversion mode. * * @return Corresponding `MVEX.sss` value. */ static ZyanU8 ZydisEncodeMvexConversionMode(ZydisConversionMode conversion) { switch (conversion) { case ZYDIS_CONVERSION_MODE_INVALID: return 0; case ZYDIS_CONVERSION_MODE_FLOAT16: return 3; case ZYDIS_CONVERSION_MODE_UINT8: return 4; case ZYDIS_CONVERSION_MODE_SINT8: return 5; case ZYDIS_CONVERSION_MODE_UINT16: return 6; case ZYDIS_CONVERSION_MODE_SINT16: return 7; default: ZYAN_UNREACHABLE; } } /** * Determines scale factor for compressed 8-bit displacement (`EVEX` instructions only). * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return log2(scale factor) */ static ZyanU8 ZydisGetCompDispScaleEvex(const ZydisEncoderInstructionMatch *match) { const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; ZYAN_ASSERT(match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX); ZYAN_ASSERT(evex_def->tuple_type); ZYAN_ASSERT(evex_def->element_size); const ZyanU8 vector_length = match->definition->vector_length - ZYDIS_VECTOR_LENGTH_128; static const ZyanU8 size_indexes[ZYDIS_IELEMENT_SIZE_MAX_VALUE + 1] = { 0, 0, 0, 1, 2, 4 }; ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(size_indexes)); const ZyanU8 size_index = size_indexes[evex_def->element_size]; switch (evex_def->tuple_type) { case ZYDIS_TUPLETYPE_FV: { static const ZyanU8 scales[2][3][3] = { /*B0*/ { /*16*/ { 4, 5, 6 }, /*32*/ { 4, 5, 6 }, /*64*/ { 4, 5, 6 } }, /*B1*/ { /*16*/ { 1, 1, 1 }, /*32*/ { 2, 2, 2 }, /*64*/ { 3, 3, 3 } } }; const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0; ZYAN_ASSERT(size_index < 3); return scales[broadcast][size_index][vector_length]; } case ZYDIS_TUPLETYPE_HV: { static const ZyanU8 scales[2][2][3] = { /*B0*/ { /*16*/ { 3, 4, 5 }, /*32*/ { 3, 4, 5 } }, /*B1*/ { /*16*/ { 1, 1, 1 }, /*32*/ { 2, 2, 2 } } }; const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0; ZYAN_ASSERT(size_index < 3); return scales[broadcast][size_index][vector_length]; } case ZYDIS_TUPLETYPE_FVM: { static const ZyanU8 scales[3] = { 4, 5, 6 }; return scales[vector_length]; } case ZYDIS_TUPLETYPE_GSCAT: case ZYDIS_TUPLETYPE_T1S: { static const ZyanU8 scales[6] = { /* */ 0, /* 8*/ 0, /* 16*/ 1, /* 32*/ 2, /* 64*/ 3, /*128*/ 4 }; ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(scales)); return scales[evex_def->element_size]; } case ZYDIS_TUPLETYPE_T1F: { static const ZyanU8 scales[3] = { /* 16*/ 1, /* 32*/ 2, /* 64*/ 3 }; ZYAN_ASSERT(size_index < 3); return scales[size_index]; } case ZYDIS_TUPLETYPE_T1_4X: return 4; case ZYDIS_TUPLETYPE_T2: return match->definition->rex_w ? 4 : 3; case ZYDIS_TUPLETYPE_T4: return match->definition->rex_w ? 5 : 4; case ZYDIS_TUPLETYPE_T8: return 5; case ZYDIS_TUPLETYPE_HVM: { static const ZyanU8 scales[3] = { 3, 4, 5 }; return scales[vector_length]; } case ZYDIS_TUPLETYPE_QVM: { static const ZyanU8 scales[3] = { 2, 3, 4 }; return scales[vector_length]; } case ZYDIS_TUPLETYPE_OVM: { static const ZyanU8 scales[3] = { 1, 2, 3 }; return scales[vector_length]; } case ZYDIS_TUPLETYPE_M128: return 4; case ZYDIS_TUPLETYPE_DUP: { static const ZyanU8 scales[3] = { 3, 5, 6 }; return scales[vector_length]; } case ZYDIS_TUPLETYPE_QUARTER: { static const ZyanU8 scales[2][3] = { /*B0*/ { 2, 3, 4 }, /*B1*/ { 1, 1, 1 } }; const ZyanU8 broadcast = match->request->evex.broadcast ? 1 : 0; return scales[broadcast][vector_length]; } default: ZYAN_UNREACHABLE; } } /** * Determines scale factor for compressed 8-bit displacement (`MVEX` instructions only). * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return log2(scale factor) */ static ZyanU8 ZydisGetCompDispScaleMvex(const ZydisEncoderInstructionMatch *match) { const ZydisInstructionDefinitionMVEX *mvex_def = (const ZydisInstructionDefinitionMVEX *)match->base_definition; ZyanU8 index = mvex_def->has_element_granularity; ZYAN_ASSERT(!index || !mvex_def->broadcast); if (!index && mvex_def->broadcast) { switch (mvex_def->broadcast) { case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_8: case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_16: index = 1; break; case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_8: case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_16: index = 2; break; default: ZYAN_UNREACHABLE; } } const ZyanU8 sss = ZydisEncodeMvexBroadcastMode(match->request->mvex.broadcast) | ZydisEncodeMvexConversionMode(match->request->mvex.conversion); switch (mvex_def->functionality) { case ZYDIS_MVEX_FUNC_IGNORED: case ZYDIS_MVEX_FUNC_INVALID: case ZYDIS_MVEX_FUNC_RC: case ZYDIS_MVEX_FUNC_SAE: case ZYDIS_MVEX_FUNC_SWIZZLE_32: case ZYDIS_MVEX_FUNC_SWIZZLE_64: return 0; case ZYDIS_MVEX_FUNC_F_32: case ZYDIS_MVEX_FUNC_I_32: case ZYDIS_MVEX_FUNC_F_64: case ZYDIS_MVEX_FUNC_I_64: return 6; case ZYDIS_MVEX_FUNC_SF_32: case ZYDIS_MVEX_FUNC_SF_32_BCST: case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16: case ZYDIS_MVEX_FUNC_UF_32: { static const ZyanU8 lookup[3][8] = { { 6, 2, 4, 5, 4, 4, 5, 5 }, { 2, 0, 0, 1, 0, 0, 1, 1 }, { 4, 0, 0, 3, 2, 2, 3, 3 } }; ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index])); return lookup[index][sss]; } case ZYDIS_MVEX_FUNC_SI_32: case ZYDIS_MVEX_FUNC_UI_32: case ZYDIS_MVEX_FUNC_SI_32_BCST: case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16: { static const ZyanU8 lookup[3][8] = { { 6, 2, 4, 0, 4, 4, 5, 5 }, { 2, 0, 0, 0, 0, 0, 1, 1 }, { 4, 0, 0, 0, 2, 2, 3, 3 } }; ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index])); return lookup[index][sss]; } case ZYDIS_MVEX_FUNC_SF_64: case ZYDIS_MVEX_FUNC_UF_64: case ZYDIS_MVEX_FUNC_SI_64: case ZYDIS_MVEX_FUNC_UI_64: { static const ZyanU8 lookup[3][3] = { { 6, 3, 5 }, { 3, 0, 0 }, { 5, 0, 0 } }; ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index])); return lookup[index][sss]; } case ZYDIS_MVEX_FUNC_DF_32: case ZYDIS_MVEX_FUNC_DI_32: { static const ZyanU8 lookup[2][8] = { { 6, 0, 0, 5, 4, 4, 5, 5 }, { 2, 0, 0, 1, 0, 0, 1, 1 } }; ZYAN_ASSERT(index < 2); ZYAN_ASSERT(sss < ZYAN_ARRAY_LENGTH(lookup[index])); return lookup[index][sss]; } case ZYDIS_MVEX_FUNC_DF_64: case ZYDIS_MVEX_FUNC_DI_64: ZYAN_ASSERT(index < 2); return index == 0 ? 6 : 3; default: ZYAN_UNREACHABLE; } } /** * Determines scale factor for compressed 8-bit displacement. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return log2(scale factor) */ static ZyanU8 ZydisGetCompDispScale(const ZydisEncoderInstructionMatch *match) { switch (match->definition->encoding) { case ZYDIS_INSTRUCTION_ENCODING_LEGACY: case ZYDIS_INSTRUCTION_ENCODING_3DNOW: case ZYDIS_INSTRUCTION_ENCODING_XOP: case ZYDIS_INSTRUCTION_ENCODING_VEX: return 0; case ZYDIS_INSTRUCTION_ENCODING_EVEX: return ZydisGetCompDispScaleEvex(match); case ZYDIS_INSTRUCTION_ENCODING_MVEX: return ZydisGetCompDispScaleMvex(match); default: ZYAN_UNREACHABLE; } } /** * Checks if requested operand matches register operand from instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param user_op Operand definition from `ZydisEncoderRequest` structure. * @param def_op Decoder's operand definition from current instruction definition. * * @return True if operands match, false otherwise. */ static ZyanBool ZydisIsRegisterOperandCompatible(ZydisEncoderInstructionMatch *match, const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op) { const ZydisRegisterClass reg_class = ZydisRegisterGetClass(user_op->reg.value); const ZydisRegisterWidth reg_width = ZydisRegisterClassGetWidth(match->request->machine_mode, reg_class); if (reg_width == 0) { return ZYAN_FALSE; } ZyanBool is4_expected_value = ZYAN_FALSE; switch (def_op->type) { case ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_REG: switch (def_op->op.reg.type) { case ZYDIS_IMPLREG_TYPE_STATIC: if (def_op->op.reg.reg.reg != user_op->reg.value) { return ZYAN_FALSE; } break; case ZYDIS_IMPLREG_TYPE_GPR_OSZ: if ((reg_class != ZYDIS_REGCLASS_GPR8) && (reg_class != ZYDIS_REGCLASS_GPR16) && (reg_class != ZYDIS_REGCLASS_GPR32) && (reg_class != ZYDIS_REGCLASS_GPR64)) { return ZYAN_FALSE; } if (def_op->op.reg.reg.id != ZydisRegisterGetId(user_op->reg.value)) { return ZYAN_FALSE; } if (!ZydisCheckOsz(match, reg_width)) { return ZYAN_FALSE; } break; case ZYDIS_IMPLREG_TYPE_GPR_ASZ: if ((reg_class != ZYDIS_REGCLASS_GPR8) && (reg_class != ZYDIS_REGCLASS_GPR16) && (reg_class != ZYDIS_REGCLASS_GPR32) && (reg_class != ZYDIS_REGCLASS_GPR64)) { return ZYAN_FALSE; } if (def_op->op.reg.reg.id != ZydisRegisterGetId(user_op->reg.value)) { return ZYAN_FALSE; } if (!ZydisCheckAsz(match, reg_width)) { return ZYAN_FALSE; } break; default: ZYAN_UNREACHABLE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR8: if (reg_class != ZYDIS_REGCLASS_GPR8) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR16: if (reg_class != ZYDIS_REGCLASS_GPR16) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR32: if (reg_class != ZYDIS_REGCLASS_GPR32) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR64: if (reg_class != ZYDIS_REGCLASS_GPR64) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR16_32_64: if ((reg_class != ZYDIS_REGCLASS_GPR16) && (reg_class != ZYDIS_REGCLASS_GPR32) && (reg_class != ZYDIS_REGCLASS_GPR64)) { return ZYAN_FALSE; } if (!ZydisCheckOsz(match, reg_width)) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR32_32_64: if ((reg_class != ZYDIS_REGCLASS_GPR32) && (reg_class != ZYDIS_REGCLASS_GPR64)) { return ZYAN_FALSE; } if (match->eosz == 0) { if (reg_class == ZYDIS_REGCLASS_GPR64) { match->eosz = 64; } else { match->eosz64_forbidden = ZYAN_TRUE; } } else if (match->eosz != (ZyanU8)reg_width) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } if (!ZydisValidateRexType(match, user_op->reg.value, ZYAN_FALSE)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR16_32_32: if ((reg_class != ZYDIS_REGCLASS_GPR16) && (reg_class != ZYDIS_REGCLASS_GPR32)) { return ZYAN_FALSE; } if (!ZydisCheckOsz(match, reg_width)) { if (match->eosz != 64 || reg_class != ZYDIS_REGCLASS_GPR32) { return ZYAN_FALSE; } } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_GPR_ASZ: if ((reg_class != ZYDIS_REGCLASS_GPR16) && (reg_class != ZYDIS_REGCLASS_GPR32) && (reg_class != ZYDIS_REGCLASS_GPR64)) { return ZYAN_FALSE; } if (!ZydisCheckAsz(match, reg_width)) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_FPR: if (reg_class != ZYDIS_REGCLASS_X87) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_MMX: if (reg_class != ZYDIS_REGCLASS_MMX) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_XMM: if (reg_class != ZYDIS_REGCLASS_XMM) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } is4_expected_value = def_op->op.encoding == ZYDIS_OPERAND_ENCODING_IS4; break; case ZYDIS_SEMANTIC_OPTYPE_YMM: if (reg_class != ZYDIS_REGCLASS_YMM) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } is4_expected_value = def_op->op.encoding == ZYDIS_OPERAND_ENCODING_IS4; break; case ZYDIS_SEMANTIC_OPTYPE_ZMM: if (reg_class != ZYDIS_REGCLASS_ZMM) { return ZYAN_FALSE; } if (!ZydisIsRegisterAllowed(match, user_op->reg.value, reg_class)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_TMM: if (reg_class != ZYDIS_REGCLASS_TMM) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_BND: if (reg_class != ZYDIS_REGCLASS_BOUND) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_SREG: if (reg_class != ZYDIS_REGCLASS_SEGMENT) { return ZYAN_FALSE; } if ((def_op->actions & ZYDIS_OPERAND_ACTION_MASK_WRITE) && (user_op->reg.value == ZYDIS_REGISTER_CS)) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_CR: { if (reg_class != ZYDIS_REGCLASS_CONTROL) { return ZYAN_FALSE; } static const ZyanU8 cr_lookup[16] = { 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }; const ZyanI8 reg_id = ZydisRegisterGetId(user_op->reg.value); if ((match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) && (reg_id == 8)) { return ZYAN_FALSE; } if (!cr_lookup[reg_id]) { return ZYAN_FALSE; } break; } case ZYDIS_SEMANTIC_OPTYPE_DR: if (reg_class != ZYDIS_REGCLASS_DEBUG) { return ZYAN_FALSE; } if (user_op->reg.value >= ZYDIS_REGISTER_DR8) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_MASK: if (reg_class != ZYDIS_REGCLASS_MASK) { return ZYAN_FALSE; } // MVEX does not require similar policy check if ((match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) && (def_op->op.encoding == ZYDIS_OPERAND_ENCODING_MASK)) { const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; ZYAN_ASSERT((evex_def->mask_policy != ZYDIS_MASK_POLICY_INVALID) && (evex_def->mask_policy != ZYDIS_MASK_POLICY_FORBIDDEN)); if ((evex_def->mask_policy == ZYDIS_MASK_POLICY_REQUIRED) && (user_op->reg.value == ZYDIS_REGISTER_K0)) { return ZYAN_FALSE; } if ((evex_def->mask_policy == ZYDIS_MASK_POLICY_ALLOWED) && (match->request->evex.zeroing_mask) && (user_op->reg.value == ZYDIS_REGISTER_K0)) { return ZYAN_FALSE; } } break; default: ZYAN_UNREACHABLE; } if (user_op->reg.is4 != is4_expected_value) { return ZYAN_FALSE; } return ZYAN_TRUE; } /** * Checks if requested operand matches memory operand from instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param user_op Operand definition from `ZydisEncoderRequest` structure. * @param def_op Decoder's operand definition from current instruction definition. * * @return True if operands match, false otherwise. */ static ZyanBool ZydisIsMemoryOperandCompatible(ZydisEncoderInstructionMatch *match, const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op) { switch (def_op->type) { case ZYDIS_SEMANTIC_OPTYPE_MEM: case ZYDIS_SEMANTIC_OPTYPE_AGEN: case ZYDIS_SEMANTIC_OPTYPE_MIB: case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBX: case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBY: case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBZ: { if ((def_op->type == ZYDIS_SEMANTIC_OPTYPE_MIB) && (user_op->mem.scale != 0)) { return ZYAN_FALSE; } ZyanI64 displacement = user_op->mem.displacement; ZyanU8 disp_size = 0; if (displacement) { disp_size = ZydisGetSignedImmSize(displacement); if (disp_size > 32) { return ZYAN_FALSE; } if (ZydisGetMachineModeWidth(match->request->machine_mode) == 16) { if ((ZyanI16)displacement == 0) { disp_size = 0; } else { disp_size = ZydisGetSignedImmSize((ZyanI16)displacement); } } match->cd8_scale = ZydisGetCompDispScale(match); if (match->cd8_scale) { const ZyanI64 mask = (1 << match->cd8_scale) - 1; if (!(displacement & mask)) { disp_size = ZydisGetSignedImmSize(displacement >> match->cd8_scale); } else if (disp_size == 8) { disp_size = 16; } } } if (def_op->type != ZYDIS_SEMANTIC_OPTYPE_AGEN) { if (match->eosz != 0) { const ZyanU8 eosz_index = match->eosz >> 5; if (def_op->size[eosz_index] != user_op->mem.size) { return ZYAN_FALSE; } } else if ((match->definition->vector_length != ZYDIS_VECTOR_LENGTH_INVALID) || (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX)) { ZyanU8 eosz_index = ZydisGetMachineModeWidth(match->request->machine_mode) >> 5; if (match->eosz64_forbidden && (eosz_index == 2)) { eosz_index = 1; } ZyanU16 allowed_mem_size = def_op->size[eosz_index]; if ((!allowed_mem_size) && (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_VEX)) { ZYAN_ASSERT((match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) || (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX)); switch (match->definition->vector_length) { case ZYDIS_VECTOR_LENGTH_128: allowed_mem_size = 16; break; case ZYDIS_VECTOR_LENGTH_256: allowed_mem_size = 32; break; case ZYDIS_VECTOR_LENGTH_INVALID: ZYAN_ASSERT(match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX); ZYAN_FALLTHROUGH; case ZYDIS_VECTOR_LENGTH_512: allowed_mem_size = 64; break; default: ZYAN_UNREACHABLE; } if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) { const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; static const ZyanU8 element_sizes[ZYDIS_IELEMENT_SIZE_MAX_VALUE + 1] = { 0, 1, 2, 4, 8, 16 }; ZYAN_ASSERT(evex_def->element_size < ZYAN_ARRAY_LENGTH(element_sizes)); const ZyanU8 element_size = element_sizes[evex_def->element_size]; if (match->request->evex.broadcast || evex_def->broadcast) { allowed_mem_size = element_size; } else { switch (evex_def->tuple_type) { case ZYDIS_TUPLETYPE_FV: break; case ZYDIS_TUPLETYPE_HV: allowed_mem_size /= 2; break; case ZYDIS_TUPLETYPE_QUARTER: allowed_mem_size /= 4; break; default: ZYAN_UNREACHABLE; } } } else { const ZydisInstructionDefinitionMVEX *mvex_def = (const ZydisInstructionDefinitionMVEX *)match->base_definition; ZyanU16 element_size; switch (match->request->mvex.conversion) { case ZYDIS_CONVERSION_MODE_INVALID: switch (mvex_def->functionality) { case ZYDIS_MVEX_FUNC_SF_32: case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16: case ZYDIS_MVEX_FUNC_UF_32: case ZYDIS_MVEX_FUNC_DF_32: case ZYDIS_MVEX_FUNC_SI_32: case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16: case ZYDIS_MVEX_FUNC_UI_32: case ZYDIS_MVEX_FUNC_DI_32: allowed_mem_size = 64; element_size = 4; break; case ZYDIS_MVEX_FUNC_SF_64: case ZYDIS_MVEX_FUNC_UF_64: case ZYDIS_MVEX_FUNC_DF_64: case ZYDIS_MVEX_FUNC_SI_64: case ZYDIS_MVEX_FUNC_UI_64: case ZYDIS_MVEX_FUNC_DI_64: allowed_mem_size = 64; element_size = 8; break; case ZYDIS_MVEX_FUNC_SF_32_BCST: case ZYDIS_MVEX_FUNC_SI_32_BCST: allowed_mem_size = 32; element_size = 4; break; default: ZYAN_UNREACHABLE; } break; case ZYDIS_CONVERSION_MODE_FLOAT16: case ZYDIS_CONVERSION_MODE_SINT16: case ZYDIS_CONVERSION_MODE_UINT16: allowed_mem_size = 32; element_size = 2; break; case ZYDIS_CONVERSION_MODE_SINT8: case ZYDIS_CONVERSION_MODE_UINT8: allowed_mem_size = 16; element_size = 1; break; default: ZYAN_UNREACHABLE; } ZYAN_ASSERT(!mvex_def->broadcast || !match->request->mvex.broadcast); switch (mvex_def->broadcast) { case ZYDIS_MVEX_STATIC_BROADCAST_NONE: break; case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_8: case ZYDIS_MVEX_STATIC_BROADCAST_1_TO_16: allowed_mem_size = element_size; break; case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_8: case ZYDIS_MVEX_STATIC_BROADCAST_4_TO_16: allowed_mem_size = element_size * 4; break; default: ZYAN_UNREACHABLE; } switch (match->request->mvex.broadcast) { case ZYDIS_BROADCAST_MODE_INVALID: break; case ZYDIS_BROADCAST_MODE_1_TO_8: case ZYDIS_BROADCAST_MODE_1_TO_16: allowed_mem_size = element_size; break; case ZYDIS_BROADCAST_MODE_4_TO_8: case ZYDIS_BROADCAST_MODE_4_TO_16: allowed_mem_size = element_size * 4; break; default: ZYAN_UNREACHABLE; } } } if (user_op->mem.size != allowed_mem_size) { return ZYAN_FALSE; } } else if (match->definition->rex_w) { match->eosz = 64; } else if (match->definition->vector_length == ZYDIS_VECTOR_LENGTH_INVALID) { match->eosz = ZydisGetOperandSizeFromElementSize(match, def_op->size, user_op->mem.size, ZYAN_TRUE); if (match->eosz == 0) { return ZYAN_FALSE; } } else { ZYAN_UNREACHABLE; } } else { if (match->easz != 0) { if (match->easz != user_op->mem.size) { return ZYAN_FALSE; } } else { switch (user_op->mem.size) { case 2: case 4: case 8: match->easz = (ZyanU8)user_op->mem.size << 3; break; default: return ZYAN_FALSE; } } } ZydisRegisterClass vsib_index_class = ZYDIS_REGCLASS_INVALID; ZyanBool is_vsib = ZYAN_TRUE; switch (def_op->type) { case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBX: vsib_index_class = ZYDIS_REGCLASS_XMM; break; case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBY: vsib_index_class = ZYDIS_REGCLASS_YMM; break; case ZYDIS_SEMANTIC_OPTYPE_MEM_VSIBZ: vsib_index_class = ZYDIS_REGCLASS_ZMM; break; default: is_vsib = ZYAN_FALSE; break; } const ZyanBool is_rip_relative = (user_op->mem.base == ZYDIS_REGISTER_RIP) || (user_op->mem.base == ZYDIS_REGISTER_EIP); if (is_rip_relative) { const ZyanBool no_rip_rel = ZYDIS_OPDEF_GET_MEM_HIGH_BIT(match->base_definition->op_rm); if (no_rip_rel || ((match->definition->modrm & 7) == 4)) { return ZYAN_FALSE; } } const ZydisRegisterClass reg_base_class = ZydisRegisterGetClass(user_op->mem.base); if ((reg_base_class == ZYDIS_REGCLASS_INVALID) && (user_op->mem.base != ZYDIS_REGISTER_NONE)) { return ZYAN_FALSE; } const ZydisRegisterClass reg_index_class = ZydisRegisterGetClass(user_op->mem.index); if ((reg_index_class == ZYDIS_REGCLASS_INVALID) && (user_op->mem.index != ZYDIS_REGISTER_NONE)) { return ZYAN_FALSE; } if (is_vsib) { const ZyanU8 mode_width = ZydisGetMachineModeWidth(match->request->machine_mode); const ZyanI8 reg_index_id = ZydisRegisterGetId(user_op->mem.index); if (((match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) || (reg_base_class != ZYDIS_REGCLASS_GPR64)) && (reg_base_class != ZYDIS_REGCLASS_GPR32) && (reg_base_class != ZYDIS_REGCLASS_INVALID)) { return ZYAN_FALSE; } if ((reg_base_class == ZYDIS_REGCLASS_GPR32) && (mode_width != 64) && (ZydisRegisterGetId(user_op->mem.base) > 7)) { return ZYAN_FALSE; } ZyanU8 max_reg_id = 7; if (mode_width == 64) { max_reg_id = match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_VEX ? 31 : 15; } if ((reg_index_class != vsib_index_class) || (reg_index_id > max_reg_id)) { return ZYAN_FALSE; } } else { if (!ZydisIsValidAddressingClass(match, reg_base_class, user_op->mem.base)) { if (!is_rip_relative || match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) { return ZYAN_FALSE; } } if (!ZydisIsValidAddressingClass(match, reg_index_class, user_op->mem.index)) { return ZYAN_FALSE; } if (reg_base_class != ZYDIS_REGCLASS_INVALID && reg_index_class != ZYDIS_REGCLASS_INVALID && reg_base_class != reg_index_class) { return ZYAN_FALSE; } if (user_op->mem.index == ZYDIS_REGISTER_ESP || user_op->mem.index == ZYDIS_REGISTER_RSP) { return ZYAN_FALSE; } } if (reg_index_class != ZYDIS_REGCLASS_INVALID && user_op->mem.scale == 0 && def_op->type != ZYDIS_SEMANTIC_OPTYPE_MIB) { return ZYAN_FALSE; } if (reg_index_class == ZYDIS_REGCLASS_INVALID && user_op->mem.scale != 0) { return ZYAN_FALSE; } ZyanU8 candidate_easz = 0; ZyanBool disp_only = ZYAN_FALSE; if (reg_base_class != ZYDIS_REGCLASS_INVALID) { if (is_rip_relative) { candidate_easz = user_op->mem.base == ZYDIS_REGISTER_RIP ? 64 : 32; } else { candidate_easz = (ZyanU8)ZydisRegisterClassGetWidth(match->request->machine_mode, reg_base_class); } } else if (reg_index_class != ZYDIS_REGCLASS_INVALID) { if (is_vsib) { candidate_easz = ZydisGetMachineModeWidth(match->request->machine_mode); } else { candidate_easz = (ZyanU8)ZydisRegisterClassGetWidth(match->request->machine_mode, reg_index_class); } } else { 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 = 32; } if (ZydisGetUnsignedImmSize(displacement) == 16) { disp_size = 16; } if (disp_size < min_disp_size) { disp_size = min_disp_size; } if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { candidate_easz = match->easz == 32 ? 32 : 64; } else { candidate_easz = disp_size; } disp_only = ZYAN_TRUE; } if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { if (is_rip_relative && reg_index_class != ZYDIS_REGCLASS_INVALID) { return ZYAN_FALSE; } } else { if (candidate_easz == 16 && !disp_only) { if (disp_size > 16) { return ZYAN_FALSE; } const ZyanI8 rm16 = ZydisGetRm16(user_op->mem.base, user_op->mem.index); if (rm16 == -1) { return ZYAN_FALSE; } const ZyanU8 allowed_scale = rm16 < 4 ? 1 : 0; if (user_op->mem.scale != allowed_scale) { return ZYAN_FALSE; } } } if (match->easz != 0) { if (match->easz != candidate_easz) { return ZYAN_FALSE; } } else { match->easz = candidate_easz; } if ((match->base_definition->address_size_map == ZYDIS_ADSIZE_MAP_IGNORED) && (match->easz != ZydisGetMachineModeWidth(match->request->machine_mode))) { return ZYAN_FALSE; } match->disp_size = disp_size; 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; } if (match->eosz != 0) { const ZyanU8 eosz_index = match->eosz >> 5; if (def_op->size[eosz_index] != user_op->mem.size) { return ZYAN_FALSE; } } else { match->eosz = ZydisGetOperandSizeFromElementSize(match, def_op->size, user_op->mem.size, ZYAN_TRUE); if (match->eosz == 0) { return ZYAN_FALSE; } } match->disp_size = ZydisGetEffectiveImmSize(match, user_op->mem.displacement, def_op); if (match->disp_size == 0) { 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) { return ZYAN_FALSE; } break; default: ZYAN_UNREACHABLE; } return ZYAN_TRUE; } /** * Checks if requested operand matches pointer operand from instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param user_op Operand definition from `ZydisEncoderRequest` structure. * * @return True if operands match, false otherwise. */ static ZyanBool ZydisIsPointerOperandCompatible(ZydisEncoderInstructionMatch *match, const ZydisEncoderOperand *user_op) { ZYAN_ASSERT(match->eosz == 0); ZYAN_ASSERT(match->request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64); ZYAN_ASSERT((match->request->branch_type == ZYDIS_BRANCH_TYPE_NONE) || (match->request->branch_type == ZYDIS_BRANCH_TYPE_FAR)); const ZyanU8 min_disp_size = ZydisGetUnsignedImmSize(user_op->ptr.offset); const ZyanU8 desired_disp_size = (match->request->branch_width == ZYDIS_BRANCH_WIDTH_NONE) ? ZydisGetMachineModeWidth(match->request->machine_mode) : (4 << match->request->branch_width); if (min_disp_size > desired_disp_size) { return ZYAN_FALSE; } match->eosz = match->disp_size = desired_disp_size; match->imm_size = 16; return ZYAN_TRUE; } /** * Checks if requested operand matches immediate operand from instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param user_op Operand definition from `ZydisEncoderRequest` structure. * @param def_op Decoder's operand definition from current instruction definition. * * @return True if operands match, false otherwise. */ static ZyanBool ZydisIsImmediateOperandCompabile(ZydisEncoderInstructionMatch *match, const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op) { switch (def_op->type) { case ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_IMM1: if (user_op->imm.u != 1) { return ZYAN_FALSE; } break; case ZYDIS_SEMANTIC_OPTYPE_IMM: case ZYDIS_SEMANTIC_OPTYPE_REL: { const ZyanU8 imm_size = ZydisGetEffectiveImmSize(match, user_op->imm.s, def_op); if (def_op->op.encoding != ZYDIS_OPERAND_ENCODING_IS4) { if (imm_size == 0) { return ZYAN_FALSE; } if (match->imm_size) { ZYAN_ASSERT(match->disp_size == 0); match->disp_size = match->imm_size; } } else { ZYAN_ASSERT(match->imm_size == 0); if (imm_size != 8) { return ZYAN_FALSE; } } match->imm_size = imm_size; match->has_rel_operand = (def_op->type == ZYDIS_SEMANTIC_OPTYPE_REL); break; } default: ZYAN_UNREACHABLE; } return ZYAN_TRUE; } /** * Checks if requested boardcast mode is compatible with instruction definition. * * @param evex_def Definition for `EVEX`-encoded instruction. * @param vector_length Vector length. * @param broadcast Requested broadcast mode. * * @return True if broadcast mode is compatible, false otherwise. */ static ZyanBool ZydisIsBroadcastModeCompatible(const ZydisInstructionDefinitionEVEX *evex_def, ZydisVectorLength vector_length, ZydisBroadcastMode broadcast) { if (broadcast == ZYDIS_BROADCAST_MODE_INVALID) { return ZYAN_TRUE; } ZyanU8 vector_size = 0; ZYAN_ASSERT(vector_length != ZYDIS_VECTOR_LENGTH_INVALID); switch (vector_length) { case ZYDIS_VECTOR_LENGTH_128: vector_size = 16; break; case ZYDIS_VECTOR_LENGTH_256: vector_size = 32; break; case ZYDIS_VECTOR_LENGTH_512: vector_size = 64; break; default: ZYAN_UNREACHABLE; } switch (evex_def->tuple_type) { case ZYDIS_TUPLETYPE_FV: break; case ZYDIS_TUPLETYPE_HV: vector_size /= 2; break; case ZYDIS_TUPLETYPE_QUARTER: vector_size /= 4; break; default: ZYAN_UNREACHABLE; } ZyanU8 element_size; switch (evex_def->element_size) { case ZYDIS_IELEMENT_SIZE_16: element_size = 2; break; case ZYDIS_IELEMENT_SIZE_32: element_size = 4; break; case ZYDIS_IELEMENT_SIZE_64: element_size = 8; break; default: ZYAN_UNREACHABLE; } ZydisBroadcastMode allowed_mode; const ZyanU8 element_count = vector_size / element_size; switch (element_count) { case 2: allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_2; break; case 4: allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_4; break; case 8: allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_8; break; case 16: allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_16; break; case 32: allowed_mode = ZYDIS_BROADCAST_MODE_1_TO_32; break; default: ZYAN_UNREACHABLE; } if (broadcast != allowed_mode) { return ZYAN_FALSE; } return ZYAN_TRUE; } /** * Checks if requested `EVEX`-specific features are compatible with instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param request A pointer to `ZydisEncoderRequest` struct. * * @return True if features are compatible, false otherwise. */ static ZyanBool ZydisAreEvexFeaturesCompatible(const ZydisEncoderInstructionMatch *match, const ZydisEncoderRequest *request) { if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_EVEX) { return ZYAN_TRUE; } const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; if ((!evex_def->accepts_zero_mask) && (evex_def->mask_override != ZYDIS_MASK_OVERRIDE_ZEROING) && (request->evex.zeroing_mask)) { return ZYAN_FALSE; } switch (evex_def->functionality) { case ZYDIS_EVEX_FUNC_INVALID: if ((request->evex.sae) || (request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID)) { return ZYAN_FALSE; } break; case ZYDIS_EVEX_FUNC_BC: if ((request->evex.sae) || (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID)) { return ZYAN_FALSE; } if (!ZydisIsBroadcastModeCompatible(evex_def, match->definition->vector_length, request->evex.broadcast)) { return ZYAN_FALSE; } break; case ZYDIS_EVEX_FUNC_RC: if (request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) { return ZYAN_FALSE; } if (request->evex.rounding == ZYDIS_ROUNDING_MODE_INVALID) { if (request->evex.sae) { return ZYAN_FALSE; } } else { if (!request->evex.sae) { return ZYAN_FALSE; } } break; case ZYDIS_EVEX_FUNC_SAE: if ((request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID)) { return ZYAN_FALSE; } break; default: ZYAN_UNREACHABLE; } return ZYAN_TRUE; } /** * Checks if requested `MVEX`-specific features are compatible with instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param request A pointer to `ZydisEncoderRequest` struct. * * @return True if features are compatible, false otherwise. */ static ZyanBool ZydisAreMvexFeaturesCompatible(const ZydisEncoderInstructionMatch *match, const ZydisEncoderRequest *request) { if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_MVEX) { return ZYAN_TRUE; } if (((match->definition->modrm >> 6) == 3) && (request->mvex.eviction_hint)) { return ZYAN_FALSE; } const ZydisInstructionDefinitionMVEX *mvex_def = (const ZydisInstructionDefinitionMVEX *)match->base_definition; switch (mvex_def->functionality) { case ZYDIS_MVEX_FUNC_IGNORED: case ZYDIS_MVEX_FUNC_INVALID: case ZYDIS_MVEX_FUNC_F_32: case ZYDIS_MVEX_FUNC_I_32: case ZYDIS_MVEX_FUNC_F_64: case ZYDIS_MVEX_FUNC_I_64: case ZYDIS_MVEX_FUNC_UF_64: case ZYDIS_MVEX_FUNC_UI_64: case ZYDIS_MVEX_FUNC_DF_64: case ZYDIS_MVEX_FUNC_DI_64: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_RC: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.eviction_hint)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SAE: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.eviction_hint)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SWIZZLE_32: case ZYDIS_MVEX_FUNC_SWIZZLE_64: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SF_32: if ((request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16)) { return ZYAN_FALSE; } if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_FLOAT16) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SI_32: if ((request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16)) { return ZYAN_FALSE; } if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT8) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SF_32_BCST: case ZYDIS_MVEX_FUNC_SI_32_BCST: if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_16) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SF_32_BCST_4TO16: case ZYDIS_MVEX_FUNC_SI_32_BCST_4TO16: if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_16)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_SF_64: case ZYDIS_MVEX_FUNC_SI_64: if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_1_TO_8) && (request->mvex.broadcast != ZYDIS_BROADCAST_MODE_4_TO_8)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_UF_32: case ZYDIS_MVEX_FUNC_DF_32: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } break; case ZYDIS_MVEX_FUNC_UI_32: case ZYDIS_MVEX_FUNC_DI_32: if ((request->mvex.broadcast != ZYDIS_BROADCAST_MODE_INVALID) || (request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID) || (request->mvex.swizzle != ZYDIS_SWIZZLE_MODE_INVALID) || (request->mvex.sae)) { return ZYAN_FALSE; } if ((request->mvex.conversion != ZYDIS_CONVERSION_MODE_INVALID) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT8) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT8) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_UINT16) && (request->mvex.conversion != ZYDIS_CONVERSION_MODE_SINT16)) { return ZYAN_FALSE; } break; default: ZYAN_UNREACHABLE; } return ZYAN_TRUE; } /** * Checks if operands specified in encoder request satisfy additional constraints mandated by * matched instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return True if operands passed the checks, false otherwise. */ static ZyanBool ZydisCheckConstraints(const ZydisEncoderInstructionMatch *match) { const ZydisEncoderOperand *operands = match->request->operands; ZyanBool is_gather = ZYAN_FALSE; switch (match->definition->encoding) { case ZYDIS_INSTRUCTION_ENCODING_VEX: { const ZydisInstructionDefinitionVEX *vex_def = (const ZydisInstructionDefinitionVEX *)match->base_definition; if (vex_def->is_gather) { ZYAN_ASSERT(match->request->operand_count == 3); ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT(operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY); ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER); const ZyanI8 dest = ZydisRegisterGetId(operands[0].reg.value); const ZyanI8 index = ZydisRegisterGetId(operands[1].mem.index); const ZyanI8 mask = ZydisRegisterGetId(operands[2].reg.value); // If any pair of the index, mask, or destination registers are the same, the // instruction results a UD fault. if ((dest == index) || (dest == mask) || (index == mask)) { return ZYAN_FALSE; } } if (vex_def->no_source_source_match) { ZYAN_ASSERT(match->request->operand_count == 3); ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT(operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER); const ZydisRegister dest = operands[0].reg.value; const ZydisRegister source1 = operands[1].reg.value; const ZydisRegister source2 = operands[2].reg.value; // AMX-E4: #UD if srcdest == src1 OR src1 == src2 OR srcdest == src2. if ((dest == source1) || (source1 == source2) || (dest == source2)) { return ZYAN_FALSE; } } return ZYAN_TRUE; } case ZYDIS_INSTRUCTION_ENCODING_EVEX: { const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; is_gather = evex_def->is_gather; if (evex_def->no_source_dest_match) { ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT((operands[3].type == ZYDIS_OPERAND_TYPE_REGISTER) || (operands[3].type == ZYDIS_OPERAND_TYPE_MEMORY)); const ZydisRegister dest = operands[0].reg.value; const ZydisRegister source1 = operands[2].reg.value; const ZydisRegister source2 = (operands[3].type == ZYDIS_OPERAND_TYPE_REGISTER) ? operands[3].reg.value : ZYDIS_REGISTER_NONE; if ((dest == source1) || (dest == source2)) { return ZYAN_FALSE; } } break; } case ZYDIS_INSTRUCTION_ENCODING_MVEX: { const ZydisInstructionDefinitionMVEX *mvex_def = (const ZydisInstructionDefinitionMVEX *)match->base_definition; is_gather = mvex_def->is_gather; break; } default: return ZYAN_TRUE; } if ((is_gather) && (operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER)) { ZYAN_ASSERT(match->request->operand_count == 3); ZYAN_ASSERT(operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER); ZYAN_ASSERT(operands[2].type == ZYDIS_OPERAND_TYPE_MEMORY); const ZyanI8 dest = ZydisRegisterGetId(operands[0].reg.value); const ZyanI8 index = ZydisRegisterGetId(operands[2].mem.index); // EVEX: The instruction will #UD fault if the destination vector zmm1 is the same as // index vector VINDEX. // MVEX: The KNC GATHER instructions forbid using the same vector register for destination // and for the index. (https://github.com/intelxed/xed/issues/281#issuecomment-970074554) if (dest == index) { return ZYAN_FALSE; } } return ZYAN_TRUE; } /** * Checks if operands and encoding-specific features from `ZydisEncoderRequest` match * encoder's instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param request A pointer to `ZydisEncoderRequest` struct. * * @return True if definition is compatible, false otherwise. */ static ZyanBool ZydisIsDefinitionCompatible(ZydisEncoderInstructionMatch *match, const ZydisEncoderRequest *request) { ZYAN_ASSERT(request->operand_count == match->base_definition->operand_count_visible); match->operands = ZydisGetOperandDefinitions(match->base_definition); if (!ZydisAreEvexFeaturesCompatible(match, request)) { return ZYAN_FALSE; } if (!ZydisAreMvexFeaturesCompatible(match, request)) { return ZYAN_FALSE; } for (ZyanU8 i = 0; i < request->operand_count; ++i) { const ZydisEncoderOperand *user_op = &request->operands[i]; const ZydisOperandDefinition *def_op = &match->operands[i]; ZYAN_ASSERT(def_op->visibility != ZYDIS_OPERAND_VISIBILITY_HIDDEN); ZyanBool is_compatible = ZYAN_FALSE; switch (user_op->type) { case ZYDIS_OPERAND_TYPE_REGISTER: is_compatible = ZydisIsRegisterOperandCompatible(match, user_op, def_op); break; case ZYDIS_OPERAND_TYPE_MEMORY: is_compatible = ZydisIsMemoryOperandCompatible(match, user_op, def_op); break; case ZYDIS_OPERAND_TYPE_POINTER: is_compatible = ZydisIsPointerOperandCompatible(match, user_op); break; case ZYDIS_OPERAND_TYPE_IMMEDIATE: is_compatible = ZydisIsImmediateOperandCompabile(match, user_op, def_op); break; default: ZYAN_UNREACHABLE; } if (!is_compatible) { return ZYAN_FALSE; } } ZyanU8 eosz = 0; if (match->base_definition->branch_type != ZYDIS_BRANCH_TYPE_NONE) { switch (request->branch_width) { case ZYDIS_BRANCH_WIDTH_NONE: break; case ZYDIS_BRANCH_WIDTH_8: if ((!match->has_rel_operand) || (match->base_definition->branch_type != ZYDIS_BRANCH_TYPE_SHORT)) { return ZYAN_FALSE; } break; case ZYDIS_BRANCH_WIDTH_16: eosz = 16; break; case ZYDIS_BRANCH_WIDTH_32: eosz = ((match->has_rel_operand) && (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) && (match->base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64)) ? 64 : 32; break; case ZYDIS_BRANCH_WIDTH_64: if (match->has_rel_operand) { return ZYAN_FALSE; } eosz = 64; break; default: ZYAN_UNREACHABLE; } } if (eosz) { if (match->eosz != 0) { if (match->eosz != eosz) { return ZYAN_FALSE; } } else { match->eosz = eosz; } } if (!ZydisCheckConstraints(match)) { return ZYAN_FALSE; } return ZYAN_TRUE; } /** * Checks if requested set of prefixes is compatible with instruction definition. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return A zyan status code. */ static ZyanBool ZydisArePrefixesCompatible(const ZydisEncoderInstructionMatch *match) { // Early-exit optimization for when no prefixes are requested at all. if (!(match->attributes & ZYDIS_ENCODABLE_PREFIXES)) { return ZYAN_TRUE; } if ((!match->base_definition->accepts_segment) && (match->attributes & ZYDIS_ATTRIB_HAS_SEGMENT)) { return ZYAN_FALSE; } if (match->definition->encoding != ZYDIS_INSTRUCTION_ENCODING_LEGACY) { return !(match->attributes & ZYDIS_ENCODABLE_PREFIXES_NO_SEGMENTS); } const ZydisInstructionDefinitionLEGACY *legacy_def = (const ZydisInstructionDefinitionLEGACY *)match->base_definition; if ((!legacy_def->accepts_LOCK) && (match->attributes & ZYDIS_ATTRIB_HAS_LOCK)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_REP) && (match->attributes & ZYDIS_ATTRIB_HAS_REP)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_REPEREPZ) && (match->attributes & ZYDIS_ATTRIB_HAS_REPE)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_REPNEREPNZ) && (match->attributes & ZYDIS_ATTRIB_HAS_REPNE)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_BOUND) && (match->attributes & ZYDIS_ATTRIB_HAS_BND)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_XACQUIRE) && (match->attributes & ZYDIS_ATTRIB_HAS_XACQUIRE)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_XRELEASE) && (match->attributes & ZYDIS_ATTRIB_HAS_XRELEASE)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_branch_hints) && (match->attributes & (ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN | ZYDIS_ATTRIB_HAS_BRANCH_TAKEN))) { return ZYAN_FALSE; } if ((!legacy_def->accepts_NOTRACK) && (match->attributes & ZYDIS_ATTRIB_HAS_NOTRACK)) { return ZYAN_FALSE; } if ((!legacy_def->accepts_hle_without_lock) && (match->attributes & (ZYDIS_ATTRIB_HAS_XACQUIRE | ZYDIS_ATTRIB_HAS_XRELEASE)) && !(match->attributes & ZYDIS_ATTRIB_HAS_LOCK)) { return ZYAN_FALSE; } return ZYAN_TRUE; } /** * Returns operand mask containing information about operand count and types in a compressed form. * * @param request A pointer to `ZydisEncoderRequest` struct. * * @return Operand mask. */ static ZyanU16 ZydisGetOperandMask(const ZydisEncoderRequest *request) { ZyanU16 operand_mask = request->operand_count; ZyanU8 bit_offset = ZYAN_BITS_TO_REPRESENT(ZYDIS_ENCODER_MAX_OPERANDS); for (ZyanU8 i = 0; i < request->operand_count; ++i) { operand_mask |= (request->operands[i].type - ZYDIS_OPERAND_TYPE_REGISTER) << bit_offset; bit_offset += ZYAN_BITS_TO_REPRESENT( ZYDIS_OPERAND_TYPE_MAX_VALUE - ZYDIS_OPERAND_TYPE_REGISTER); } return operand_mask; } /** * Handles optimization opportunities indicated by `swappable` field in instruction definition * structure. See `ZydisEncodableInstruction` for more information. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return True if definition has been swapped, false otherwise. */ static ZyanBool ZydisHandleSwappableDefinition(ZydisEncoderInstructionMatch *match) { if (!match->definition->swappable) { return ZYAN_FALSE; } // Special case for ISA-wide unique conflict between two `mov` variants // mov gpr16_32_64(encoding=opcode), imm(encoding=simm16_32_64,scale_factor=osz) // mov gpr16_32_64(encoding=modrm_rm), imm(encoding=simm16_32_32,scale_factor=osz) if (match->request->mnemonic == ZYDIS_MNEMONIC_MOV) { const ZyanU8 imm_size = ZydisGetSignedImmSize(match->request->operands[1].imm.s); if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) && (match->eosz == 64) && (imm_size < 64)) { return ZYAN_TRUE; } } ZYAN_ASSERT((match->request->operand_count == 2) || (match->request->operand_count == 3)); const ZyanU8 src_index = (match->request->operand_count == 3) ? 2 : 1; const ZyanI8 dest_id = ZydisRegisterGetId(match->request->operands[0].reg.value); const ZyanI8 src_id = ZydisRegisterGetId(match->request->operands[src_index].reg.value); if ((dest_id <= 7) && (src_id > 7)) { ++match->definition; ZydisGetInstructionDefinition(match->definition->encoding, match->definition->instruction_reference, &match->base_definition); match->operands = ZydisGetOperandDefinitions(match->base_definition); return ZYAN_TRUE; } return ZYAN_FALSE; } /** * This function attempts to find a matching instruction definition for provided encoder request. * * @param request A pointer to `ZydisEncoderRequest` struct. * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * * @return A zyan status code. */ static ZyanStatus ZydisFindMatchingDefinition(const ZydisEncoderRequest *request, ZydisEncoderInstructionMatch *match) { ZYAN_MEMSET(match, 0, sizeof(ZydisEncoderInstructionMatch)); match->request = request; match->attributes = request->prefixes; const ZydisEncodableInstruction *definition = ZYAN_NULL; const ZyanU8 definition_count = ZydisGetEncodableInstructions(request->mnemonic, &definition); ZYAN_ASSERT(definition && definition_count); const ZydisWidthFlag mode_width = ZydisGetMachineModeWidth(request->machine_mode) >> 4; const ZyanBool is_compat = (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_16) || (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_32); const ZyanU8 default_asz = ZydisGetAszFromHint(request->address_size_hint); const ZyanU8 default_osz = ZydisGetOszFromHint(request->operand_size_hint); const ZyanU16 operand_mask = ZydisGetOperandMask(request); for (ZyanU8 i = 0; i < definition_count; ++i, ++definition) { if (definition->operand_mask != operand_mask) { continue; } const ZydisInstructionDefinition *base_definition = ZYAN_NULL; ZydisGetInstructionDefinition(definition->encoding, definition->instruction_reference, &base_definition); if (!(definition->modes & mode_width)) { continue; } if ((request->allowed_encodings != ZYDIS_ENCODABLE_ENCODING_DEFAULT) && !(ZydisGetEncodableEncoding(definition->encoding) & request->allowed_encodings)) { continue; } if (request->machine_mode == ZYDIS_MACHINE_MODE_REAL_16) { if (base_definition->requires_protected_mode) { continue; } switch (definition->encoding) { case ZYDIS_INSTRUCTION_ENCODING_XOP: case ZYDIS_INSTRUCTION_ENCODING_VEX: case ZYDIS_INSTRUCTION_ENCODING_EVEX: case ZYDIS_INSTRUCTION_ENCODING_MVEX: continue; default: break; } } else if ((request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) && (definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX)) { continue; } if (is_compat && base_definition->no_compat_mode) { continue; } if ((request->branch_type != ZYDIS_BRANCH_TYPE_NONE) && (request->branch_type != base_definition->branch_type)) { continue; } if ((base_definition->branch_type == ZYDIS_BRANCH_TYPE_NONE) && (request->branch_width != ZYDIS_BRANCH_WIDTH_NONE)) { continue; } match->definition = definition; match->base_definition = base_definition; match->operands = ZYAN_NULL; match->easz = definition->accepts_hint == ZYDIS_SIZE_HINT_ASZ ? default_asz : 0; match->eosz = definition->accepts_hint == ZYDIS_SIZE_HINT_OSZ ? default_osz : 0; match->disp_size = match->imm_size = match->cd8_scale = 0; match->rex_type = ZYDIS_REX_TYPE_UNKNOWN; match->eosz64_forbidden = ZYAN_FALSE; match->has_rel_operand = ZYAN_FALSE; if ((base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_BYTEOP) && (match->eosz == 8)) { continue; } if (!ZydisArePrefixesCompatible(match)) { continue; } if (!ZydisIsDefinitionCompatible(match, request)) { continue; } if (ZydisHandleSwappableDefinition(match)) { if (definition == match->definition) { continue; } ++i; definition = match->definition; base_definition = match->base_definition; } if (match->easz == 0) { if (definition->address_sizes & mode_width) { match->easz = (ZyanU8)(mode_width << 4); } else if (mode_width == ZYDIS_WIDTH_16) { match->easz = 32; } else if (mode_width == ZYDIS_WIDTH_32) { match->easz = 16; } else { match->easz = 32; } ZYAN_ASSERT(definition->address_sizes & (match->easz >> 4)); } else if (!(definition->address_sizes & (match->easz >> 4))) { continue; } if (mode_width == ZYDIS_WIDTH_64) { if (base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_DEFAULT64) { if (match->eosz == 0) { ZYAN_ASSERT(definition->operand_sizes & (ZYDIS_WIDTH_16 | ZYDIS_WIDTH_64)); if (definition->operand_sizes & ZYDIS_WIDTH_64) { match->eosz = 64; } else { match->eosz = 16; } } else if (match->eosz == 32) { continue; } } else if (base_definition->operand_size_map == ZYDIS_OPSIZE_MAP_FORCE64) { if (match->eosz == 0) { match->eosz = 64; } else if (match->eosz != 64) { continue; } } } if (match->eosz == 0) { const ZydisWidthFlag default_width = (mode_width == ZYDIS_WIDTH_64) ? ZYDIS_WIDTH_32 : mode_width; if (definition->operand_sizes & default_width) { match->eosz = (ZyanU8)(default_width << 4); } else if (definition->operand_sizes & ZYDIS_WIDTH_16) { match->eosz = 16; } else if (definition->operand_sizes & ZYDIS_WIDTH_32) { match->eosz = 32; } else { match->eosz = 64; } } else if (match->eosz64_forbidden && match->eosz == 64) { continue; } else if (!(definition->operand_sizes & (match->eosz >> 4))) { continue; } return ZYAN_STATUS_SUCCESS; } return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; } /** * Emits unsigned integer value. * * @param data Value to emit. * @param size Value size in bytes. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitUInt(ZyanU64 data, ZyanU8 size, ZydisEncoderBuffer *buffer) { ZYAN_ASSERT(size == 1 || size == 2 || size == 4 || size == 8); const ZyanUSize new_offset = buffer->offset + size; if (new_offset > buffer->size) { return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE; } // TODO: fix for big-endian systems // The size variable is not passed on purpose to allow the compiler // to generate better code with a known size at compile time. if (size == 1) { ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 1); } else if (size == 2) { ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 2); } else if (size == 4) { ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 4); } else if (size == 8) { ZYAN_MEMCPY(buffer->buffer + buffer->offset, &data, 8); } else { ZYAN_UNREACHABLE; } buffer->offset = new_offset; return ZYAN_STATUS_SUCCESS; } /** * Emits a single byte. * * @param byte Value to emit. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitByte(ZyanU8 byte, ZydisEncoderBuffer *buffer) { return ZydisEmitUInt(byte, 1, buffer); } /** * Emits legact prefixes. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitLegacyPrefixes(const ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZyanBool compressed_prefixes = ZYAN_FALSE; switch (instruction->encoding) { case ZYDIS_INSTRUCTION_ENCODING_XOP: case ZYDIS_INSTRUCTION_ENCODING_VEX: case ZYDIS_INSTRUCTION_ENCODING_EVEX: case ZYDIS_INSTRUCTION_ENCODING_MVEX: compressed_prefixes = ZYAN_TRUE; break; default: break; } // Group 1 if (instruction->attributes & ZYDIS_ATTRIB_HAS_LOCK) { ZYAN_CHECK(ZydisEmitByte(0xF0, buffer)); } if (!compressed_prefixes) { if (instruction->attributes & (ZYDIS_ATTRIB_HAS_REPNE | ZYDIS_ATTRIB_HAS_BND | ZYDIS_ATTRIB_HAS_XACQUIRE)) { ZYAN_CHECK(ZydisEmitByte(0xF2, buffer)); } if (instruction->attributes & (ZYDIS_ATTRIB_HAS_REP | ZYDIS_ATTRIB_HAS_REPE | ZYDIS_ATTRIB_HAS_XRELEASE)) { ZYAN_CHECK(ZydisEmitByte(0xF3, buffer)); } } // Group 2 if (instruction->attributes & (ZYDIS_ATTRIB_HAS_SEGMENT_CS | ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN)) { ZYAN_CHECK(ZydisEmitByte(0x2E, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_SS) { ZYAN_CHECK(ZydisEmitByte(0x36, buffer)); } if (instruction->attributes & (ZYDIS_ATTRIB_HAS_SEGMENT_DS | ZYDIS_ATTRIB_HAS_BRANCH_TAKEN)) { ZYAN_CHECK(ZydisEmitByte(0x3E, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_ES) { ZYAN_CHECK(ZydisEmitByte(0x26, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_FS) { ZYAN_CHECK(ZydisEmitByte(0x64, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_SEGMENT_GS) { ZYAN_CHECK(ZydisEmitByte(0x65, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_NOTRACK) { ZYAN_CHECK(ZydisEmitByte(0x3E, buffer)); } // Group 3 if (!compressed_prefixes) { if (instruction->attributes & ZYDIS_ATTRIB_HAS_OPERANDSIZE) { ZYAN_CHECK(ZydisEmitByte(0x66, buffer)); } } // Group 4 if (instruction->attributes & ZYDIS_ATTRIB_HAS_ADDRESSSIZE) { ZYAN_CHECK(ZydisEmitByte(0x67, buffer)); } return ZYAN_STATUS_SUCCESS; } /** * Encodes low nibble of `REX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param high_r A pointer to `ZyanBool` variable that will be set to true when the * highest `ModR/M.reg` bit cannot be encoded using `REX` prefix. * * @return A zyan status code. */ static ZyanU8 ZydisEncodeRexLowNibble(const ZydisEncoderInstruction *instruction, ZyanBool *high_r) { if (high_r) { *high_r = ZYAN_FALSE; } ZyanU8 rex = 0; if ((instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM) && (instruction->attributes & ZYDIS_ATTRIB_HAS_SIB)) { if (instruction->base & 0x08) { rex |= 1; } if (instruction->index & 0x08) { rex |= 2; } if (instruction->reg & 0x08) { rex |= 4; } if (high_r && (instruction->reg & 0x10)) { *high_r = ZYAN_TRUE; } } else if (instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM) { if (instruction->rm & 0x08) { rex |= 1; } if (instruction->rm & 0x10) { rex |= 2; } if (instruction->reg & 0x08) { rex |= 4; } if (high_r && (instruction->reg & 0x10)) { *high_r = ZYAN_TRUE; } } else { if (instruction->rm & 0x08) { rex |= 1; } } if (instruction->rex_w) { rex |= 8; } return rex; } /** * Emits `REX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitRex(const ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { const ZyanU8 rex = ZydisEncodeRexLowNibble(instruction, ZYAN_NULL); if (rex || (instruction->attributes & ZYDIS_ATTRIB_HAS_REX)) { ZYAN_CHECK(ZydisEmitByte(0x40 | rex, buffer)); } return ZYAN_STATUS_SUCCESS; } /** * Encodes common parts of `VEX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param mmmmm A pointer to `ZyanU8` variable that will receive `VEX.mmmmm` * @param pp A pointer to `ZyanU8` variable that will receive `VEX.pp` * @param vvvv A pointer to `ZyanU8` variable that will receive `VEX.vvvv` * @param rex A pointer to `ZyanU8` variable that will receive 'REX` * @param high_r A pointer to `ZyanBool` variable that will be set to true when the * highest `ModR/M.reg` bit cannot be encoded using `REX` prefix. */ static void ZydisEncodeVexCommons(ZydisEncoderInstruction *instruction, ZyanU8 *mmmmm, ZyanU8 *pp, ZyanU8 *vvvv, ZyanU8 *rex, ZyanBool *high_r) { switch (instruction->opcode_map) { case ZYDIS_OPCODE_MAP_DEFAULT: case ZYDIS_OPCODE_MAP_0F: case ZYDIS_OPCODE_MAP_0F38: case ZYDIS_OPCODE_MAP_0F3A: case ZYDIS_OPCODE_MAP_MAP5: case ZYDIS_OPCODE_MAP_MAP6: *mmmmm = (ZyanU8)instruction->opcode_map; break; case ZYDIS_OPCODE_MAP_XOP8: case ZYDIS_OPCODE_MAP_XOP9: case ZYDIS_OPCODE_MAP_XOPA: *mmmmm = 8 + ((ZyanU8)instruction->opcode_map - ZYDIS_OPCODE_MAP_XOP8); break; default: ZYAN_UNREACHABLE; } instruction->opcode_map = ZYDIS_OPCODE_MAP_DEFAULT; *pp = 0; if (instruction->attributes & ZYDIS_ATTRIB_HAS_OPERANDSIZE) { *pp = 1; } else if (instruction->attributes & ZYDIS_ATTRIB_HAS_REP) { *pp = 2; } else if (instruction->attributes & ZYDIS_ATTRIB_HAS_REPNE) { *pp = 3; } *vvvv = ~instruction->vvvv; *rex = ZydisEncodeRexLowNibble(instruction, high_r); } /** * Emits `XOP` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitXop(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZyanU8 mmmmm, pp, vvvv, rex; ZydisEncodeVexCommons(instruction, &mmmmm, &pp, &vvvv, &rex, ZYAN_NULL); ZYAN_ASSERT(instruction->vector_length <= 1); const ZyanU8 b1 = (((~rex) & 0x07) << 5) | mmmmm; const ZyanU8 b2 = ((rex & 0x08) << 4) | ((vvvv & 0xF) << 3) | (instruction->vector_length << 2) | pp; ZYAN_CHECK(ZydisEmitByte(0x8F, buffer)); ZYAN_CHECK(ZydisEmitByte(b1, buffer)); ZYAN_CHECK(ZydisEmitByte(b2, buffer)); return ZYAN_STATUS_SUCCESS; } /** * Emits `VEX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitVex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZyanU8 mmmmm, pp, vvvv, rex; ZydisEncodeVexCommons(instruction, &mmmmm, &pp, &vvvv, &rex, ZYAN_NULL); ZYAN_ASSERT(instruction->vector_length <= 1); if (mmmmm != 1 || (rex & 0x0B)) { const ZyanU8 b1 = (((~rex) & 0x07) << 5) | mmmmm; const ZyanU8 b2 = ((rex & 0x08) << 4) | ((vvvv & 0xF) << 3) | (instruction->vector_length << 2) | pp; ZYAN_CHECK(ZydisEmitByte(0xC4, buffer)); ZYAN_CHECK(ZydisEmitByte(b1, buffer)); ZYAN_CHECK(ZydisEmitByte(b2, buffer)); } else { const ZyanU8 b1 = (((~rex) & 0x04) << 5) | ((vvvv & 0xF) << 3) | (instruction->vector_length << 2) | pp; ZYAN_CHECK(ZydisEmitByte(0xC5, buffer)); ZYAN_CHECK(ZydisEmitByte(b1, buffer)); } return ZYAN_STATUS_SUCCESS; } /** * Encodes common parts of `EVEX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param p0 A pointer to `ZyanU8` variable that will receive 2nd byte of `EVEX` prefix. * @param p1 A pointer to `ZyanU8` variable that will receive 3rd byte of `EVEX` prefix. * @param vvvvv A pointer to `ZyanU8` variable that will receive `EVEX.vvvvv`. */ static void ZydisEncodeEvexCommons(ZydisEncoderInstruction *instruction, ZyanU8 *p0, ZyanU8 *p1, ZyanU8 *vvvvv) { ZyanBool high_r; ZyanU8 mmmmm, pp, rex; ZydisEncodeVexCommons(instruction, &mmmmm, &pp, vvvvv, &rex, &high_r); *p0 = (((~rex) & 0x07) << 5) | mmmmm; if (!high_r) { *p0 |= 0x10; } *p1 = ((rex & 0x08) << 4) | ((*vvvvv & 0x0F) << 3) | 0x04 | pp; } /** * Emits `EVEX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitEvex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZyanU8 p0, p1, vvvvv; ZydisEncodeEvexCommons(instruction, &p0, &p1, &vvvvv); ZyanU8 p2 = (instruction->vector_length << 5) | ((vvvvv & 0x10) >> 1) | instruction->mask; if (instruction->zeroing) { p2 |= 0x80; } if (instruction->attributes & ZYDIS_ATTRIB_HAS_EVEX_B) { p2 |= 0x10; } if (instruction->index & 0x10) { p2 &= 0xF7; } ZYAN_CHECK(ZydisEmitByte(0x62, buffer)); ZYAN_CHECK(ZydisEmitByte(p0, buffer)); ZYAN_CHECK(ZydisEmitByte(p1, buffer)); ZYAN_CHECK(ZydisEmitByte(p2, buffer)); return ZYAN_STATUS_SUCCESS; } /** * Emits `MVEX` prefix. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitMvex(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZyanU8 p0, p1, vvvvv; ZydisEncodeEvexCommons(instruction, &p0, &p1, &vvvvv); ZyanU8 p2 = (instruction->sss << 4) | ((vvvvv & 0x10) >> 1) | instruction->mask; if (instruction->eviction_hint) { p2 |= 0x80; } if (instruction->index & 0x10) { p2 &= 0xF7; } ZYAN_CHECK(ZydisEmitByte(0x62, buffer)); ZYAN_CHECK(ZydisEmitByte(p0, buffer)); ZYAN_CHECK(ZydisEmitByte(p1 & 0xFB, buffer)); ZYAN_CHECK(ZydisEmitByte(p2, buffer)); return ZYAN_STATUS_SUCCESS; } /** * Emits instruction as stream of bytes. * * @param instruction A pointer to `ZydisEncoderInstruction` struct. * @param buffer A pointer to `ZydisEncoderBuffer` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEmitInstruction(ZydisEncoderInstruction *instruction, ZydisEncoderBuffer *buffer) { ZYAN_CHECK(ZydisEmitLegacyPrefixes(instruction, buffer)); switch (instruction->encoding) { case ZYDIS_INSTRUCTION_ENCODING_LEGACY: case ZYDIS_INSTRUCTION_ENCODING_3DNOW: ZYAN_CHECK(ZydisEmitRex(instruction, buffer)); break; case ZYDIS_INSTRUCTION_ENCODING_XOP: ZYAN_CHECK(ZydisEmitXop(instruction, buffer)); break; case ZYDIS_INSTRUCTION_ENCODING_VEX: ZYAN_CHECK(ZydisEmitVex(instruction, buffer)); break; case ZYDIS_INSTRUCTION_ENCODING_EVEX: ZYAN_CHECK(ZydisEmitEvex(instruction, buffer)); break; case ZYDIS_INSTRUCTION_ENCODING_MVEX: ZYAN_CHECK(ZydisEmitMvex(instruction, buffer)); break; default: ZYAN_UNREACHABLE; } switch (instruction->opcode_map) { case ZYDIS_OPCODE_MAP_DEFAULT: break; case ZYDIS_OPCODE_MAP_0F: ZYAN_CHECK(ZydisEmitByte(0x0F, buffer)); break; case ZYDIS_OPCODE_MAP_0F38: ZYAN_CHECK(ZydisEmitByte(0x0F, buffer)); ZYAN_CHECK(ZydisEmitByte(0x38, buffer)); break; case ZYDIS_OPCODE_MAP_0F3A: ZYAN_CHECK(ZydisEmitByte(0x0F, buffer)); ZYAN_CHECK(ZydisEmitByte(0x3A, buffer)); break; case ZYDIS_OPCODE_MAP_0F0F: ZYAN_CHECK(ZydisEmitByte(0x0F, buffer)); ZYAN_CHECK(ZydisEmitByte(0x0F, buffer)); break; default: ZYAN_UNREACHABLE; } if (instruction->encoding != ZYDIS_INSTRUCTION_ENCODING_3DNOW) { ZYAN_CHECK(ZydisEmitByte(instruction->opcode, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_MODRM) { const ZyanU8 modrm = (instruction->mod << 6) | ((instruction->reg & 7) << 3) | (instruction->rm & 7); ZYAN_CHECK(ZydisEmitByte(modrm, buffer)); } if (instruction->attributes & ZYDIS_ATTRIB_HAS_SIB) { const ZyanU8 sib = (instruction->scale << 6) | ((instruction->index & 7) << 3) | (instruction->base & 7); ZYAN_CHECK(ZydisEmitByte(sib, buffer)); } if (instruction->disp_size) { ZYAN_CHECK(ZydisEmitUInt(instruction->disp, instruction->disp_size / 8, buffer)); } if (instruction->imm_size) { ZYAN_CHECK(ZydisEmitUInt(instruction->imm, instruction->imm_size / 8, buffer)); } if (instruction->encoding == ZYDIS_INSTRUCTION_ENCODING_3DNOW) { ZYAN_CHECK(ZydisEmitByte(instruction->opcode, buffer)); } return ZYAN_STATUS_SUCCESS; } /** * Encodes register operand as fields inside `ZydisEncoderInstruction` structure. * * @param user_op Validated operand definition from `ZydisEncoderRequest` structure. * @param def_op Decoder's operand definition from instruction definition. * @param instruction A pointer to `ZydisEncoderInstruction` struct. */ void ZydisBuildRegisterOperand(const ZydisEncoderOperand *user_op, const ZydisOperandDefinition *def_op, ZydisEncoderInstruction *instruction) { if (def_op->type == ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_REG) { return; } ZyanU8 reg_id = 0; if (ZydisRegisterGetClass(user_op->reg.value) != ZYDIS_REGCLASS_GPR8) { reg_id = (ZyanU8)ZydisRegisterGetId(user_op->reg.value); } else { static const ZyanU8 reg8_lookup[] = { 0, 1, 2, 3, // AL, CL, DL, BL 4, 5, 6, 7, // AH, CH, DH, BH 4, 5, 6, 7, // SPL, BPL, SIL, DIL 8, 9, 10, 11, 12, 13, 14, 15, // R8B-R15B }; ZYAN_ASSERT( ((ZyanUSize)user_op->reg.value - ZYDIS_REGISTER_AL) < ZYAN_ARRAY_LENGTH(reg8_lookup)); reg_id = reg8_lookup[user_op->reg.value - ZYDIS_REGISTER_AL]; if (user_op->reg.value >= ZYDIS_REGISTER_SPL && user_op->reg.value <= ZYDIS_REGISTER_DIL) { instruction->attributes |= ZYDIS_ATTRIB_HAS_REX; } } switch (def_op->op.encoding) { case ZYDIS_OPERAND_ENCODING_MODRM_REG: instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM; instruction->reg = reg_id; break; case ZYDIS_OPERAND_ENCODING_MODRM_RM: instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM; instruction->rm = reg_id; break; case ZYDIS_OPERAND_ENCODING_OPCODE: instruction->opcode += reg_id & 7; instruction->rm = reg_id; break; case ZYDIS_OPERAND_ENCODING_NDSNDD: instruction->vvvv = reg_id; break; case ZYDIS_OPERAND_ENCODING_IS4: instruction->imm_size = 8; instruction->imm = reg_id << 4; break; case ZYDIS_OPERAND_ENCODING_MASK: instruction->mask = reg_id; break; default: ZYAN_UNREACHABLE; } } /** * Encodes memory operand as fields inside `ZydisEncoderInstruction` structure. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param user_op Decoder's operand definition from instruction definition. * @param instruction A pointer to `ZydisEncoderInstruction` struct. */ static void ZydisBuildMemoryOperand(ZydisEncoderInstructionMatch *match, const ZydisEncoderOperand *user_op, ZydisEncoderInstruction *instruction) { instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM; instruction->disp = (ZyanU64)user_op->mem.displacement; if (match->easz == 16) { const ZyanI8 rm = ZydisGetRm16(user_op->mem.base, user_op->mem.index); if (rm != -1) { instruction->rm = (ZyanU8)rm; instruction->disp_size = match->disp_size; switch (instruction->disp_size) { case 0: if (rm == 6) { instruction->disp_size = 8; instruction->mod = 1; } break; case 8: instruction->mod = 1; break; case 16: instruction->mod = 2; break; default: ZYAN_UNREACHABLE; } } else { instruction->rm = 6; instruction->disp_size = 16; } return; } if (user_op->mem.index == ZYDIS_REGISTER_NONE) { if (user_op->mem.base == ZYDIS_REGISTER_NONE) { if (match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { instruction->rm = 4; instruction->attributes |= ZYDIS_ATTRIB_HAS_SIB; instruction->base = 5; instruction->index = 4; } else { instruction->rm = 5; } instruction->disp_size = 32; return; } else if ((user_op->mem.base == ZYDIS_REGISTER_RIP) || (user_op->mem.base == ZYDIS_REGISTER_EIP)) { instruction->rm = 5; instruction->disp_size = 32; return; } } const ZyanU8 reg_base_id = (ZyanU8)ZydisRegisterGetId(user_op->mem.base); const ZyanU8 reg_index_id = (ZyanU8)ZydisRegisterGetId(user_op->mem.index); instruction->disp_size = match->disp_size; switch (instruction->disp_size) { case 0: if (reg_base_id == 5 || reg_base_id == 13) { instruction->disp_size = 8; instruction->disp = 0; instruction->mod = 1; } break; case 8: instruction->mod = 1; break; case 16: instruction->disp_size = 32; ZYAN_FALLTHROUGH; case 32: instruction->mod = 2; break; default: ZYAN_UNREACHABLE; } if ((user_op->mem.index == ZYDIS_REGISTER_NONE) && (reg_base_id != 4) && (reg_base_id != 12) && ((match->definition->modrm & 7) != 4)) { instruction->rm = reg_base_id; return; } instruction->rm = 4; instruction->attributes |= ZYDIS_ATTRIB_HAS_SIB; if (reg_base_id != 0xFF) { instruction->base = reg_base_id; } else { instruction->base = 5; instruction->mod = 0; instruction->disp_size = 32; } if (reg_index_id != 0xFF) { instruction->index = reg_index_id; } else { instruction->index = 4; } switch (user_op->mem.scale) { case 0: case 1: break; case 2: instruction->scale = 1; break; case 4: instruction->scale = 2; break; case 8: instruction->scale = 3; break; default: ZYAN_UNREACHABLE; } } /** * Encodes instruction as emittable `ZydisEncoderInstruction` struct. * * @param match A pointer to `ZydisEncoderInstructionMatch` struct. * @param instruction A pointer to `ZydisEncoderInstruction` struct. * * @return A zyan status code. */ static ZyanStatus ZydisBuildInstruction(ZydisEncoderInstructionMatch *match, ZydisEncoderInstruction *instruction) { ZYAN_MEMSET(instruction, 0, sizeof(ZydisEncoderInstruction)); instruction->attributes = match->attributes; instruction->encoding = match->definition->encoding; instruction->opcode_map = match->definition->opcode_map; instruction->opcode = match->definition->opcode; instruction->rex_w = match->definition->rex_w; instruction->mod = (match->definition->modrm >> 6) & 3; instruction->reg = (match->definition->modrm >> 3) & 7; instruction->rm = match->definition->modrm & 7; if (match->definition->modrm) { instruction->attributes |= ZYDIS_ATTRIB_HAS_MODRM; } switch (match->definition->vector_length) { case ZYDIS_VECTOR_LENGTH_INVALID: case ZYDIS_VECTOR_LENGTH_128: instruction->vector_length = 0; break; case ZYDIS_VECTOR_LENGTH_256: instruction->vector_length = 1; break; case ZYDIS_VECTOR_LENGTH_512: instruction->vector_length = 2; break; default: ZYAN_UNREACHABLE; } if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_EVEX) { const ZydisInstructionDefinitionEVEX *evex_def = (const ZydisInstructionDefinitionEVEX *)match->base_definition; if (evex_def->mask_override != ZYDIS_MASK_OVERRIDE_ZEROING) { instruction->zeroing = match->request->evex.zeroing_mask; } if ((match->request->evex.sae) || (match->request->evex.broadcast != ZYDIS_BROADCAST_MODE_INVALID)) { instruction->attributes |= ZYDIS_ATTRIB_HAS_EVEX_B; } if (match->request->evex.rounding != ZYDIS_ROUNDING_MODE_INVALID) { instruction->attributes |= ZYDIS_ATTRIB_HAS_EVEX_B; switch (match->request->evex.rounding) { case ZYDIS_ROUNDING_MODE_RN: instruction->vector_length = 0; break; case ZYDIS_ROUNDING_MODE_RD: instruction->vector_length = 1; break; case ZYDIS_ROUNDING_MODE_RU: instruction->vector_length = 2; break; case ZYDIS_ROUNDING_MODE_RZ: instruction->vector_length = 3; break; default: ZYAN_UNREACHABLE; } } } else if (match->definition->encoding == ZYDIS_INSTRUCTION_ENCODING_MVEX) { instruction->sss |= ZydisEncodeMvexBroadcastMode(match->request->mvex.broadcast); instruction->sss |= ZydisEncodeMvexConversionMode(match->request->mvex.conversion); switch (match->request->mvex.rounding) { case ZYDIS_ROUNDING_MODE_INVALID: break; case ZYDIS_ROUNDING_MODE_RN: case ZYDIS_ROUNDING_MODE_RD: case ZYDIS_ROUNDING_MODE_RU: case ZYDIS_ROUNDING_MODE_RZ: instruction->sss |= match->request->mvex.rounding - ZYDIS_ROUNDING_MODE_RN; break; default: ZYAN_UNREACHABLE; } switch (match->request->mvex.swizzle) { case ZYDIS_SWIZZLE_MODE_INVALID: break; case ZYDIS_SWIZZLE_MODE_DCBA: case ZYDIS_SWIZZLE_MODE_CDAB: case ZYDIS_SWIZZLE_MODE_BADC: case ZYDIS_SWIZZLE_MODE_DACB: case ZYDIS_SWIZZLE_MODE_AAAA: case ZYDIS_SWIZZLE_MODE_BBBB: case ZYDIS_SWIZZLE_MODE_CCCC: case ZYDIS_SWIZZLE_MODE_DDDD: instruction->sss |= match->request->mvex.swizzle - ZYDIS_SWIZZLE_MODE_DCBA; break; default: ZYAN_UNREACHABLE; } if ((match->request->mvex.sae) || (match->request->mvex.eviction_hint) || (match->request->mvex.rounding != ZYDIS_ROUNDING_MODE_INVALID)) { instruction->eviction_hint = ZYAN_TRUE; } if (match->request->mvex.sae) { instruction->sss |= 4; } // Following instructions violate general `MVEX.EH` handling rules. In all other cases this // bit is used either as eviction hint (memory operands present) or to encode MVEX-specific // functionality (register forms). Instructions listed below use `MVEX.EH` to identify // different instructions with memory operands and don't treat it as eviction hint. switch (match->request->mnemonic) { case ZYDIS_MNEMONIC_VMOVNRAPD: case ZYDIS_MNEMONIC_VMOVNRAPS: instruction->eviction_hint = ZYAN_FALSE; break; case ZYDIS_MNEMONIC_VMOVNRNGOAPD: case ZYDIS_MNEMONIC_VMOVNRNGOAPS: instruction->eviction_hint = ZYAN_TRUE; break; default: break; } } switch (match->definition->mandatory_prefix) { case ZYDIS_MANDATORY_PREFIX_NONE: break; case ZYDIS_MANDATORY_PREFIX_66: instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE; break; case ZYDIS_MANDATORY_PREFIX_F2: instruction->attributes |= ZYDIS_ATTRIB_HAS_REPNE; break; case ZYDIS_MANDATORY_PREFIX_F3: instruction->attributes |= ZYDIS_ATTRIB_HAS_REP; break; default: ZYAN_UNREACHABLE; } const ZyanU8 mode_width = ZydisGetMachineModeWidth(match->request->machine_mode); if (match->easz != mode_width) { instruction->attributes |= ZYDIS_ATTRIB_HAS_ADDRESSSIZE; } if ((match->request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) && (match->base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_FORCE64)) { switch (match->eosz) { case 16: instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE; break; case 32: break; case 64: instruction->rex_w = match->base_definition->operand_size_map != ZYDIS_OPSIZE_MAP_DEFAULT64; break; default: ZYAN_UNREACHABLE; } } else { if (match->eosz != mode_width) { instruction->attributes |= ZYDIS_ATTRIB_HAS_OPERANDSIZE; } } for (ZyanU8 i = 0; i < match->request->operand_count; ++i) { const ZydisEncoderOperand *user_op = &match->request->operands[i]; const ZydisOperandDefinition *def_op = &match->operands[i]; switch (user_op->type) { case ZYDIS_OPERAND_TYPE_REGISTER: ZydisBuildRegisterOperand(user_op, def_op, instruction); break; case ZYDIS_OPERAND_TYPE_MEMORY: if (def_op->type != ZYDIS_SEMANTIC_OPTYPE_MOFFS) { ZydisBuildMemoryOperand(match, user_op, instruction); if ((match->cd8_scale) && (instruction->disp_size == 8)) { instruction->disp >>= match->cd8_scale; } } else { instruction->disp_size = match->disp_size; instruction->disp = (ZyanU64)user_op->mem.displacement; } break; case ZYDIS_OPERAND_TYPE_POINTER: instruction->disp_size = match->disp_size; instruction->disp = user_op->ptr.offset; instruction->imm_size = match->imm_size; instruction->imm = user_op->ptr.segment; break; case ZYDIS_OPERAND_TYPE_IMMEDIATE: if (def_op->type == ZYDIS_SEMANTIC_OPTYPE_IMPLICIT_IMM1) { break; } if (def_op->op.encoding != ZYDIS_OPERAND_ENCODING_IS4) { if (instruction->imm_size) { ZYAN_ASSERT(instruction->disp_size == 0); instruction->disp_size = match->disp_size; instruction->disp = instruction->imm; } instruction->imm_size = match->imm_size; instruction->imm = user_op->imm.u; } else { ZYAN_ASSERT(instruction->imm_size == 8); instruction->imm |= user_op->imm.u; } break; default: ZYAN_UNREACHABLE; } } return ZYAN_STATUS_SUCCESS; } /** * Performs a set of sanity checks that must be satisfied for every valid encoder request. * * @param request A pointer to `ZydisEncoderRequest` struct. * * @return A zyan status code. */ static ZyanStatus ZydisEncoderCheckRequestSanity(const ZydisEncoderRequest *request) { if (((ZyanUSize)request->machine_mode > ZYDIS_MACHINE_MODE_MAX_VALUE) || ((ZyanUSize)request->allowed_encodings > ZYDIS_ENCODABLE_ENCODING_MAX_VALUE) || ((ZyanUSize)request->mnemonic > ZYDIS_MNEMONIC_MAX_VALUE) || ((ZyanUSize)request->branch_type > ZYDIS_BRANCH_TYPE_MAX_VALUE) || ((ZyanUSize)request->branch_width > ZYDIS_BRANCH_WIDTH_MAX_VALUE) || ((ZyanUSize)request->address_size_hint > ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE) || ((ZyanUSize)request->operand_size_hint > ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE) || ((ZyanUSize)request->evex.broadcast > ZYDIS_BROADCAST_MODE_MAX_VALUE) || ((ZyanUSize)request->evex.rounding > ZYDIS_ROUNDING_MODE_MAX_VALUE) || ((ZyanUSize)request->mvex.broadcast > ZYDIS_BROADCAST_MODE_MAX_VALUE) || ((ZyanUSize)request->mvex.conversion > ZYDIS_CONVERSION_MODE_MAX_VALUE) || ((ZyanUSize)request->mvex.rounding > ZYDIS_ROUNDING_MODE_MAX_VALUE) || ((ZyanUSize)request->mvex.swizzle > ZYDIS_SWIZZLE_MODE_MAX_VALUE) || (request->operand_count > ZYDIS_ENCODER_MAX_OPERANDS) || (request->mnemonic == ZYDIS_MNEMONIC_INVALID) || (request->prefixes & ~ZYDIS_ENCODABLE_PREFIXES)) { return ZYAN_STATUS_INVALID_ARGUMENT; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT) { if ((request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) && (request->prefixes & ZYDIS_LEGACY_SEGMENTS)) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZyanU8 seg_override_count = 0; if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_CS) { ++seg_override_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_SS) { ++seg_override_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_DS) { ++seg_override_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_ES) { ++seg_override_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_FS) { ++seg_override_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_GS) { ++seg_override_count; } if (seg_override_count != 1) { return ZYAN_STATUS_INVALID_ARGUMENT; } } ZyanU8 rep_family_count = 0; if (request->prefixes & ZYDIS_ATTRIB_HAS_REP) { ++rep_family_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_REPE) { ++rep_family_count; } if (request->prefixes & ZYDIS_ATTRIB_HAS_REPNE) { ++rep_family_count; } if (rep_family_count > 1) { return ZYAN_STATUS_INVALID_ARGUMENT; } if ((request->prefixes & ZYDIS_ATTRIB_HAS_XACQUIRE) && (request->prefixes & ZYDIS_ATTRIB_HAS_XRELEASE)) { return ZYAN_STATUS_INVALID_ARGUMENT; } if ((request->prefixes & ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN) && (request->prefixes & ZYDIS_ATTRIB_HAS_BRANCH_TAKEN)) { return ZYAN_STATUS_INVALID_ARGUMENT; } if ((request->prefixes & ZYDIS_ATTRIB_HAS_NOTRACK) && (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT)) { return ZYAN_STATUS_INVALID_ARGUMENT; } static const ZyanBool branch_lookup [ZYDIS_BRANCH_WIDTH_MAX_VALUE + 1][ZYDIS_BRANCH_TYPE_MAX_VALUE + 1] = { /* NONE */ { ZYAN_TRUE, ZYAN_TRUE, ZYAN_TRUE, ZYAN_TRUE }, /* 8 */ { ZYAN_TRUE, ZYAN_TRUE, ZYAN_FALSE, ZYAN_FALSE }, /* 16 */ { ZYAN_TRUE, ZYAN_FALSE, ZYAN_TRUE, ZYAN_TRUE }, /* 32 */ { ZYAN_TRUE, ZYAN_FALSE, ZYAN_TRUE, ZYAN_TRUE }, /* 64 */ { ZYAN_TRUE, ZYAN_FALSE, ZYAN_TRUE, ZYAN_TRUE }, }; if (!branch_lookup[request->branch_width][request->branch_type]) { return ZYAN_STATUS_INVALID_ARGUMENT; } if (request->machine_mode == ZYDIS_MACHINE_MODE_LONG_64) { if (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_16) { return ZYAN_STATUS_INVALID_ARGUMENT; } } else { if ((request->branch_width == ZYDIS_BRANCH_WIDTH_64) || (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_64) || (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_64)) { return ZYAN_STATUS_INVALID_ARGUMENT; } } for (ZyanU8 i = 0; i < request->operand_count; ++i) { const ZydisEncoderOperand *op = &request->operands[i]; if ((op->type == ZYDIS_OPERAND_TYPE_UNUSED) || ((ZyanUSize)op->type > ZYDIS_OPERAND_TYPE_MAX_VALUE)) { return ZYAN_STATUS_INVALID_ARGUMENT; } switch (op->type) { case ZYDIS_OPERAND_TYPE_REGISTER: if (op->reg.value > ZYDIS_REGISTER_MAX_VALUE) { return ZYAN_STATUS_INVALID_ARGUMENT; } break; case ZYDIS_OPERAND_TYPE_MEMORY: if (((ZyanUSize)op->mem.base > ZYDIS_REGISTER_MAX_VALUE) || ((ZyanUSize)op->mem.index > ZYDIS_REGISTER_MAX_VALUE) || !ZydisIsScaleValid(op->mem.scale)) { return ZYAN_STATUS_INVALID_ARGUMENT; } break; case ZYDIS_OPERAND_TYPE_POINTER: case ZYDIS_OPERAND_TYPE_IMMEDIATE: break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } } return ZYAN_STATUS_SUCCESS; } /** * Encodes instruction with semantics specified in encoder request structure. * * @param request A pointer to the `ZydisEncoderRequest` struct. Must be validated before * calling this function. * @param buffer A pointer to the output buffer receiving encoded instruction. * @param length A pointer to the variable containing length of the output buffer. Upon * successful return this variable receives length of the encoded instruction. * @param instruction Internal state of the encoder. * * @return A zyan status code. */ static ZyanStatus ZydisEncoderEncodeInstructionInternal(const ZydisEncoderRequest *request, void *buffer, ZyanUSize *length, ZydisEncoderInstruction *instruction) { ZydisEncoderInstructionMatch match; ZYAN_CHECK(ZydisFindMatchingDefinition(request, &match)); ZydisEncoderBuffer output; output.buffer = (ZyanU8 *)buffer; output.size = *length; output.offset = 0; ZYAN_CHECK(ZydisBuildInstruction(&match, instruction)); ZYAN_CHECK(ZydisEmitInstruction(instruction, &output)); *length = output.offset; return ZYAN_STATUS_SUCCESS; } /* ============================================================================================== */ /* Exported functions */ /* ============================================================================================== */ ZYDIS_EXPORT ZyanStatus ZydisEncoderEncodeInstruction(const ZydisEncoderRequest *request, void *buffer, ZyanUSize *length) { if (!request || !buffer || !length) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZYAN_CHECK(ZydisEncoderCheckRequestSanity(request)); ZydisEncoderInstruction instruction; return ZydisEncoderEncodeInstructionInternal(request, buffer, length, &instruction); } ZYDIS_EXPORT ZyanStatus ZydisEncoderEncodeInstructionAbsolute(ZydisEncoderRequest *request, void *buffer, ZyanUSize *length, ZyanU64 runtime_address) { if (!request || !buffer || !length) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZYAN_CHECK(ZydisEncoderCheckRequestSanity(request)); const ZydisEncoderRelInfo *rel_info = ZydisGetRelInfo(request->mnemonic); ZydisEncoderOperand *op_rip_rel = ZYAN_NULL; ZyanBool adjusted_rel = ZYAN_FALSE; ZyanU64 absolute_address = 0; ZyanU8 mode_index = ZydisGetMachineModeWidth(request->machine_mode) >> 5; for (ZyanU8 i = 0; i < request->operand_count; ++i) { ZydisEncoderOperand *op = &request->operands[i]; if ((op->type == ZYDIS_OPERAND_TYPE_IMMEDIATE) && rel_info) { if (adjusted_rel) { return ZYAN_STATUS_INVALID_ARGUMENT; } switch (rel_info->accepts_scaling_hints) { case ZYDIS_SIZE_HINT_NONE: case ZYDIS_SIZE_HINT_OSZ: { static const ZyanI8 asz_priority[3][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 0, 2, -1 }, }; static const ZyanI8 osz_priority[3][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 0, 2, 1 }, }; ZyanI8 forced_priority_row[3] = { -1, -1, -1 }; ZyanI8 *priority_row = ZYAN_NULL; ZyanU8 extra_length = 0; ZyanU8 start_offset = 0; if (rel_info->accepts_scaling_hints == ZYDIS_SIZE_HINT_NONE) { if ((request->branch_type == ZYDIS_BRANCH_TYPE_FAR) || (request->branch_width == ZYDIS_BRANCH_WIDTH_64)) { return ZYAN_STATUS_INVALID_ARGUMENT; } if ((rel_info->accepts_branch_hints) && (request->prefixes & (ZYDIS_ATTRIB_HAS_BRANCH_NOT_TAKEN | ZYDIS_ATTRIB_HAS_BRANCH_TAKEN))) { extra_length = 1; } if (request->branch_width == ZYDIS_BRANCH_WIDTH_NONE) { if (request->branch_type == ZYDIS_BRANCH_TYPE_NEAR) { start_offset = 1; } priority_row = (ZyanI8 *)&asz_priority[mode_index]; } else { forced_priority_row[0] = (ZyanI8)(request->branch_width - 1); priority_row = (ZyanI8 *)&forced_priority_row; } } else { if (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_NONE) { priority_row = (ZyanI8 *)&osz_priority[mode_index]; } else { if (request->operand_size_hint == ZYDIS_OPERAND_SIZE_HINT_64) { extra_length = 1; forced_priority_row[0] = 2; } else { forced_priority_row[0] = (ZyanI8)(request->operand_size_hint - 1); } priority_row = (ZyanI8 *)&forced_priority_row; } } ZYAN_ASSERT(ZYAN_ARRAY_LENGTH(asz_priority[0]) == ZYAN_ARRAY_LENGTH(osz_priority[0])); for (ZyanU8 j = start_offset; j < ZYAN_ARRAY_LENGTH(asz_priority[0]); ++j) { ZyanI8 size_index = priority_row[j]; if (size_index < 0) { break; } ZyanU8 base_size = rel_info->size[mode_index][size_index]; if (base_size == 0) { continue; } ZyanU8 predicted_size = base_size + extra_length; if (runtime_address > ZYAN_UINT64_MAX - predicted_size + 1) { continue; } ZyanI64 rel = (ZyanI64)(op->imm.u - (runtime_address + predicted_size)); ZyanU8 rel_size = ZydisGetSignedImmSize(rel); if (rel_size > (8 << size_index)) { continue; } op->imm.s = rel; adjusted_rel = ZYAN_TRUE; break; } break; } case ZYDIS_SIZE_HINT_ASZ: { static const ZyanI8 asz_prefix_lookup[3][ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE + 1] = { { 0, 0, 1, -1 }, { 0, 1, 0, -1 }, { 0, -1, 1, 0 }, }; ZyanI8 extra_length = asz_prefix_lookup[mode_index][request->address_size_hint]; if (extra_length < 0) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZyanU8 asz_index = (request->address_size_hint == ZYDIS_ADDRESS_SIZE_HINT_NONE) ? mode_index : ZydisGetAszFromHint(request->address_size_hint) >> 5; ZYAN_ASSERT((rel_info->size[asz_index][0] != 0) && (rel_info->size[asz_index][1] == 0) && (rel_info->size[asz_index][2] == 0) && !rel_info->accepts_branch_hints); ZyanU8 predicted_size = rel_info->size[asz_index][0] + extra_length; if (runtime_address > ZYAN_UINT64_MAX - predicted_size + 1) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZyanI64 rel = (ZyanI64)(op->imm.u - (runtime_address + predicted_size)); ZyanU8 rel_size = ZydisGetSignedImmSize(rel); if (rel_size > 8) { return ZYAN_STATUS_INVALID_ARGUMENT; } op->imm.s = rel; adjusted_rel = ZYAN_TRUE; break; } default: ZYAN_UNREACHABLE; } if (!adjusted_rel) { return ZYAN_STATUS_INVALID_ARGUMENT; } } else if ((op->type == ZYDIS_OPERAND_TYPE_MEMORY) && ((op->mem.base == ZYDIS_REGISTER_EIP) || (op->mem.base == ZYDIS_REGISTER_RIP))) { if (op_rip_rel) { return ZYAN_STATUS_INVALID_ARGUMENT; } absolute_address = op->mem.displacement; op->mem.displacement = 0; op_rip_rel = op; } } ZydisEncoderInstruction instruction; ZYAN_CHECK(ZydisEncoderEncodeInstructionInternal(request, buffer, length, &instruction)); if (op_rip_rel) { ZyanUSize instruction_size = *length; if (runtime_address > ZYAN_UINT64_MAX - instruction_size + 1) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZyanI64 rip_rel = (ZyanI64)(absolute_address - (runtime_address + instruction_size)); if (ZydisGetSignedImmSize(rip_rel) > 32) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZYAN_ASSERT(instruction.disp_size != 0); ZyanU8 disp_offset = (instruction.disp_size >> 3) + (instruction.imm_size >> 3); ZYAN_ASSERT(instruction_size > disp_offset); ZYAN_MEMCPY((ZyanU8 *)buffer + instruction_size - disp_offset, &rip_rel, sizeof(ZyanI32)); op_rip_rel->mem.displacement = rip_rel; } return ZYAN_STATUS_SUCCESS; } ZYDIS_EXPORT ZyanStatus ZydisEncoderDecodedInstructionToEncoderRequest( const ZydisDecodedInstruction *instruction, const ZydisDecodedOperand* operands, ZyanU8 operand_count, ZydisEncoderRequest *request) { if (!instruction || !request || (operand_count && !operands)) { return ZYAN_STATUS_INVALID_ARGUMENT; } ZYAN_MEMSET(request, 0, sizeof(ZydisEncoderRequest)); request->machine_mode = instruction->machine_mode; request->mnemonic = instruction->mnemonic; request->prefixes = instruction->attributes & ZYDIS_ENCODABLE_PREFIXES; request->branch_type = instruction->meta.branch_type; if (!(instruction->attributes & ZYDIS_ATTRIB_ACCEPTS_SEGMENT)) { request->prefixes &= ~ZYDIS_ATTRIB_HAS_SEGMENT; } switch (instruction->address_width) { case 16: request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_16; break; case 32: request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_32; break; case 64: request->address_size_hint = ZYDIS_ADDRESS_SIZE_HINT_64; break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } switch (instruction->operand_width) { case 8: request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_8; break; case 16: request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_16; break; case 32: request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_32; break; case 64: request->operand_size_hint = ZYDIS_OPERAND_SIZE_HINT_64; break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } switch (request->branch_type) { case ZYDIS_BRANCH_TYPE_NONE: request->branch_width = ZYDIS_BRANCH_WIDTH_NONE; break; case ZYDIS_BRANCH_TYPE_SHORT: request->branch_width = ZYDIS_BRANCH_WIDTH_8; break; case ZYDIS_BRANCH_TYPE_NEAR: case ZYDIS_BRANCH_TYPE_FAR: switch (instruction->operand_width) { case 16: request->branch_width = ZYDIS_BRANCH_WIDTH_16; break; case 32: request->branch_width = ZYDIS_BRANCH_WIDTH_32; break; case 64: request->branch_width = ZYDIS_BRANCH_WIDTH_64; break; default: ZYAN_UNREACHABLE; } break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } switch (instruction->encoding) { case ZYDIS_INSTRUCTION_ENCODING_LEGACY: case ZYDIS_INSTRUCTION_ENCODING_3DNOW: case ZYDIS_INSTRUCTION_ENCODING_XOP: case ZYDIS_INSTRUCTION_ENCODING_VEX: break; case ZYDIS_INSTRUCTION_ENCODING_EVEX: request->evex.broadcast = !instruction->avx.broadcast.is_static ? instruction->avx.broadcast.mode : ZYDIS_BROADCAST_MODE_INVALID; request->evex.rounding = instruction->avx.rounding.mode; request->evex.sae = instruction->avx.has_sae; request->evex.zeroing_mask = (instruction->avx.mask.mode == ZYDIS_MASK_MODE_ZEROING || instruction->avx.mask.mode == ZYDIS_MASK_MODE_CONTROL_ZEROING) && (instruction->raw.evex.z) ? ZYAN_TRUE : ZYAN_FALSE; break; case ZYDIS_INSTRUCTION_ENCODING_MVEX: request->mvex.broadcast = !instruction->avx.broadcast.is_static ? instruction->avx.broadcast.mode : ZYDIS_BROADCAST_MODE_INVALID; request->mvex.conversion = instruction->avx.conversion.mode; request->mvex.rounding = instruction->avx.rounding.mode; request->mvex.swizzle = instruction->avx.swizzle.mode; request->mvex.sae = instruction->avx.has_sae; request->mvex.eviction_hint = instruction->avx.has_eviction_hint; break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } request->allowed_encodings = 1 << instruction->encoding; if ((operand_count > ZYDIS_ENCODER_MAX_OPERANDS) || (operand_count > instruction->operand_count_visible)) { return ZYAN_STATUS_INVALID_ARGUMENT; } request->operand_count = operand_count; for (ZyanU8 i = 0; i < operand_count; ++i) { const ZydisDecodedOperand *dec_op = &operands[i]; ZydisEncoderOperand *enc_op = &request->operands[i]; enc_op->type = dec_op->type; switch (dec_op->type) { case ZYDIS_OPERAND_TYPE_REGISTER: enc_op->reg.value = dec_op->reg.value; enc_op->reg.is4 = dec_op->encoding == ZYDIS_OPERAND_ENCODING_IS4; break; case ZYDIS_OPERAND_TYPE_MEMORY: 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 { enc_op->mem.displacement = dec_op->mem.disp.has_displacement ? dec_op->mem.disp.value : 0; } enc_op->mem.size = dec_op->size / 8; break; case ZYDIS_OPERAND_TYPE_POINTER: enc_op->ptr.segment = dec_op->ptr.segment; enc_op->ptr.offset = dec_op->ptr.offset; break; case ZYDIS_OPERAND_TYPE_IMMEDIATE: enc_op->imm.u = dec_op->imm.value.u; // `XBEGIN` is an ISA-wide unique instruction because it's not a branching instruction // but it has a relative operand which behaves differently from all other relatives // (no truncating behavior in 16-bit mode). Encoder treats it as non-branching // instruction that scales with hidden operand size. if ((dec_op->imm.is_relative) && (instruction->mnemonic != ZYDIS_MNEMONIC_XBEGIN)) { switch (instruction->raw.imm->size) { case 8: request->branch_width = ZYDIS_BRANCH_WIDTH_8; break; case 16: request->branch_width = ZYDIS_BRANCH_WIDTH_16; break; case 32: request->branch_width = ZYDIS_BRANCH_WIDTH_32; break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } } break; default: return ZYAN_STATUS_INVALID_ARGUMENT; } } return ZYAN_STATUS_SUCCESS; } ZYDIS_EXPORT ZyanStatus ZydisEncoderNopFill(void *buffer, ZyanUSize length) { if (!buffer) { return ZYAN_STATUS_INVALID_ARGUMENT; } // Intel SDM Vol. 2B "Recommended Multi-Byte Sequence of NOP Instruction" static const ZyanU8 nops[9][9] = { { 0x90 }, { 0x66, 0x90 }, { 0x0F, 0x1F, 0x00 }, { 0x0F, 0x1F, 0x40, 0x00 }, { 0x0F, 0x1F, 0x44, 0x00, 0x00 }, { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 }, { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 }, { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }, }; ZyanU8 *output = (ZyanU8 *)buffer; while (length) { ZyanUSize nop_size = (length > 9) ? 9 : length; ZYAN_MEMCPY(output, nops[nop_size - 1], nop_size); output += nop_size; length -= nop_size; } return ZYAN_STATUS_SUCCESS; } /* ============================================================================================== */