dynarmic/externals/zydis/src/Encoder.c
Alexandre Bouvier cbeed6396f externals: Update zydis to 4.0.0
Merge commit '6fa8d51479e9a5542c67bec715a1f68e7ed057ba'
2022-11-20 22:14:24 +01:00

4742 lines
152 KiB
C

/***************************************************************************************************
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 <Zycore/LibC.h>
#include <Zydis/Encoder.h>
#include <Zydis/Utils.h>
#include <Zydis/Internal/EncoderData.h>
#include <Zydis/Internal/SharedData.h>
/* ============================================================================================== */
/* 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;
}
/* ============================================================================================== */