cbeed6396f
Merge commit '6fa8d51479e9a5542c67bec715a1f68e7ed057ba'
4742 lines
152 KiB
C
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;
|
|
}
|
|
|
|
/* ============================================================================================== */
|