Implemented encoding for XOP, VEX and EVEX

This commit is contained in:
Joel Höner 2017-01-20 21:18:13 +01:00
parent 46077709f8
commit 0a50bb9daa
2 changed files with 173 additions and 79 deletions

View File

@ -58,6 +58,17 @@ enum ZydisModeConstraints
ZYDIS_MODE_CONSTR_REQUIRE64, ZYDIS_MODE_CONSTR_REQUIRE64,
}; };
typedef uint8_t ZydisPrefixBit;
enum ZydisPrefixBits
{
// TODO: Use defines instead?
ZYDIS_PREFBIT_VEX_L = 0x01,
ZYDIS_PREFBIT_REX_W = 0x02,
ZYDIS_PREFBIT_EVEX_L2 = 0x04,
ZYDIS_PREFBIT_EVEX_B = 0x08,
};
typedef struct ZydisEncoderTableEntry_ typedef struct ZydisEncoderTableEntry_
{ {
uint16_t mnemonic; uint16_t mnemonic;
@ -69,6 +80,7 @@ typedef struct ZydisEncoderTableEntry_
ZydisInstructionAttributes mandatoryAttribs; ZydisInstructionAttributes mandatoryAttribs;
ZydisModRMMod modRmMod; ZydisModRMMod modRmMod;
ZydisModeConstraint modeConstraint; ZydisModeConstraint modeConstraint;
ZydisPrefixBit prefixBits;
} ZydisEncoderTableEntry; } ZydisEncoderTableEntry;
#include <Zydis/Internal/EncoderTable.inc> #include <Zydis/Internal/EncoderTable.inc>
@ -87,6 +99,8 @@ typedef struct ZydisEncoderContext_
uint64_t disp; uint64_t disp;
uint8_t immBitSizes[2]; uint8_t immBitSizes[2];
uint64_t imms[2]; uint64_t imms[2];
uint8_t opcodeMapPrefixLen;
uint8_t opcodeMapPrefix[3];
} ZydisEncoderContext; } ZydisEncoderContext;
/* ============================================================================================== */ /* ============================================================================================== */
@ -199,40 +213,39 @@ static ZydisStatus ZydisEmitVEX(ZydisEncoderContext* ctx)
{ {
ZYDIS_ASSERT(ctx); ZYDIS_ASSERT(ctx);
// Write opcode. // Can we use short 2-byte VEX encoding?
uint8_t opcode = ctx->info->details.vex.data[0]; if (ctx->info->details.vex.X == 1 &&
ZYDIS_CHECK(ZydisEmitByte(ctx, opcode)); ctx->info->details.vex.B == 1 &&
ctx->info->details.vex.W == 0 &&
// Write prefix' "operands". ctx->info->details.vex.m_mmmm == 1)
switch (opcode)
{ {
case 0xC4: ZYDIS_CHECK(ZydisEmitByte(ctx, 0xC5));
ZYDIS_CHECK(ZydisEmitByte( ZYDIS_CHECK(ZydisEmitByte(
ctx, ctx,
(ctx->info->details.vex.R & 0x01) << 7 | (~ctx->info->details.vex.R & 0x01) << 7 |
(ctx->info->details.vex.X & 0x01) << 6 | (~ctx->info->details.vex.vvvv & 0x0F) << 3 |
(ctx->info->details.vex.B & 0x01) << 5 | ( ctx->info->details.vex.L & 0x01) << 2 |
(ctx->info->details.vex.m_mmmm & 0x1F) << 0 ( ctx->info->details.vex.pp & 0x03) << 0
));
}
// Nope, use 3-byte VEX.
else
{
ZYDIS_CHECK(ZydisEmitByte(ctx, 0xC4));
ZYDIS_CHECK(ZydisEmitByte(
ctx,
(~ctx->info->details.vex.R & 0x01) << 7 |
(~ctx->info->details.vex.X & 0x01) << 6 |
(~ctx->info->details.vex.B & 0x01) << 5 |
( ctx->info->details.vex.m_mmmm & 0x1F) << 0
)); ));
ZYDIS_CHECK(ZydisEmitByte( ZYDIS_CHECK(ZydisEmitByte(
ctx, ctx,
(ctx->info->details.vex.W & 0x01) << 7 | ( ctx->info->details.vex.W & 0x01) << 7 |
(ctx->info->details.vex.vvvv & 0x0F) << 3 | (~ctx->info->details.vex.vvvv & 0x0F) << 3 |
(ctx->info->details.vex.L & 0x01) << 2 | ( ctx->info->details.vex.L & 0x01) << 2 |
(ctx->info->details.vex.pp & 0x03) << 0 ( ctx->info->details.vex.pp & 0x03) << 0
)); ));
break;
case 0xC5:
ZYDIS_CHECK(ZydisEmitByte(
ctx,
(ctx->info->details.vex.R & 0x01) << 7 |
(ctx->info->details.vex.vvvv & 0x0F) << 3 |
(ctx->info->details.vex.L & 0x01) << 2 |
(ctx->info->details.vex.pp & 0x03) << 0
));
break;
default:
ZYDIS_UNREACHABLE; // TODO: return error instead
} }
return ZYDIS_STATUS_SUCCESS; return ZYDIS_STATUS_SUCCESS;
@ -313,39 +326,53 @@ static ZydisStatus ZydisEmitSIB(ZydisEncoderContext* ctx)
return ZYDIS_STATUS_SUCCESS; return ZYDIS_STATUS_SUCCESS;
} }
static ZydisStatus ZydisEmitOpcode(ZydisEncoderContext* ctx) static ZydisStatus ZydisPrepareOpcode(ZydisEncoderContext* ctx)
{ {
ZYDIS_ASSERT(ctx); ZYDIS_ASSERT(ctx);
ZYDIS_ASSERT(ctx->matchingEntry); ZYDIS_ASSERT(ctx->matchingEntry);
// Put opcode extension prefix(es), if required. // Put opcode map prefix(es), if required.
switch (ctx->matchingEntry->map) switch (ctx->info->encoding)
{ {
case ZYDIS_OPCODE_MAP_0F: case ZYDIS_INSTRUCTION_ENCODING_DEFAULT:
ZYDIS_CHECK(ZydisEmitByte(ctx, 0x0F)); case ZYDIS_INSTRUCTION_ENCODING_3DNOW:
switch (ctx->matchingEntry->map)
{
case ZYDIS_OPCODE_MAP_0F:
ctx->opcodeMapPrefix[ctx->opcodeMapPrefixLen++] = 0x0F;
break;
case ZYDIS_OPCODE_MAP_0F38:
ctx->opcodeMapPrefix[ctx->opcodeMapPrefixLen++] = 0x0F;
ctx->opcodeMapPrefix[ctx->opcodeMapPrefixLen++] = 0x38;
break;
case ZYDIS_OPCODE_MAP_0F3A:
ctx->opcodeMapPrefix[ctx->opcodeMapPrefixLen++] = 0x0F;
ctx->opcodeMapPrefix[ctx->opcodeMapPrefixLen++] = 0x3A;
break;
case ZYDIS_OPCODE_MAP_DEFAULT:
break; // Nothing to do.
default:
ZYDIS_UNREACHABLE;
}
break; break;
case ZYDIS_OPCODE_MAP_0F38: case ZYDIS_INSTRUCTION_ENCODING_VEX:
ZYDIS_CHECK(ZydisEmitByte(ctx, 0x0F)); ctx->info->details.vex.m_mmmm = ctx->matchingEntry->map;
ZYDIS_CHECK(ZydisEmitByte(ctx, 0x38)); ZYDIS_ASSERT(ctx->info->details.vex.m_mmmm <= 0x03);
break; break;
case ZYDIS_OPCODE_MAP_0F3A: case ZYDIS_INSTRUCTION_ENCODING_EVEX:
ZYDIS_CHECK(ZydisEmitByte(ctx, 0x0F)); ctx->info->details.evex.mm = ctx->matchingEntry->map;
ZYDIS_CHECK(ZydisEmitByte(ctx, 0x3A)); ZYDIS_ASSERT(ctx->info->details.evex.mm <= 0x03);
break; break;
case ZYDIS_OPCODE_MAP_XOP8: case ZYDIS_INSTRUCTION_ENCODING_XOP:
case ZYDIS_OPCODE_MAP_XOP9: ctx->info->details.xop.m_mmmm =
case ZYDIS_OPCODE_MAP_XOPA: ctx->matchingEntry->map - ZYDIS_OPCODE_MAP_XOP8 + 0x08;
return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; // TODO ZYDIS_ASSERT(ctx->info->details.xop.m_mmmm >= 0x08);
ZYDIS_ASSERT(ctx->info->details.xop.m_mmmm <= 0x0B);
break; break;
case ZYDIS_OPCODE_MAP_DEFAULT:
break; // Nothing to do.
default: default:
ZYDIS_UNREACHABLE; ZYDIS_UNREACHABLE;
} }
// Emit actual opcode.
ZYDIS_CHECK(ZydisEmitByte(ctx, ctx->info->opcode));
return ZYDIS_STATUS_SUCCESS; return ZYDIS_STATUS_SUCCESS;
} }
@ -395,9 +422,9 @@ static ZydisStatus ZydisPrepareRegOperand(ZydisEncoderContext* ctx,
switch (topBitLoc) switch (topBitLoc)
{ {
case 'B': ctx->info->details.modrm.rm = lowerBits; case 'B': ctx->info->details.modrm.rm = lowerBits; break;
case 'R': ctx->info->details.modrm.reg = lowerBits; case 'R': ctx->info->details.modrm.reg = lowerBits; break;
case 'X': ctx->info->details.sib.index = lowerBits; case 'X': ctx->info->details.sib.index = lowerBits; break;
default: ZYDIS_UNREACHABLE; default: ZYDIS_UNREACHABLE;
} }
@ -424,7 +451,6 @@ static ZydisStatus ZydisPrepareRegOperand(ZydisEncoderContext* ctx,
case 'X': topBitDst = &ctx->info->details.vex.X; break; case 'X': topBitDst = &ctx->info->details.vex.X; break;
default: ZYDIS_UNREACHABLE; default: ZYDIS_UNREACHABLE;
} }
topBit = ~topBit;
break; break;
case ZYDIS_INSTRUCTION_ENCODING_XOP: case ZYDIS_INSTRUCTION_ENCODING_XOP:
switch (topBitLoc) switch (topBitLoc)
@ -434,7 +460,6 @@ static ZydisStatus ZydisPrepareRegOperand(ZydisEncoderContext* ctx,
case 'X': topBitDst = &ctx->info->details.xop.X; break; case 'X': topBitDst = &ctx->info->details.xop.X; break;
default: ZYDIS_UNREACHABLE; default: ZYDIS_UNREACHABLE;
} }
topBit = ~topBit;
break; break;
case ZYDIS_INSTRUCTION_ENCODING_EVEX: case ZYDIS_INSTRUCTION_ENCODING_EVEX:
switch (topBitLoc) switch (topBitLoc)
@ -444,7 +469,6 @@ static ZydisStatus ZydisPrepareRegOperand(ZydisEncoderContext* ctx,
case 'X': topBitDst = &ctx->info->details.evex.X; break; case 'X': topBitDst = &ctx->info->details.evex.X; break;
default: ZYDIS_UNREACHABLE; default: ZYDIS_UNREACHABLE;
} }
topBit = ~topBit;
break; break;
default: default:
return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; // TODO return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; // TODO
@ -474,34 +498,56 @@ static ZydisStatus ZydisPrepareOperand(ZydisEncoderContext* ctx,
case ZYDIS_OPERAND_ENCODING_RM_CD32: case ZYDIS_OPERAND_ENCODING_RM_CD32:
case ZYDIS_OPERAND_ENCODING_RM_CD64: case ZYDIS_OPERAND_ENCODING_RM_CD64:
{ {
// TODO: MMX registers
// TODO: rBP
// TODO: RIP relative addressing
// Memory operand? // Memory operand?
if (operand->type == ZYDIS_OPERAND_TYPE_MEMORY) if (operand->type == ZYDIS_OPERAND_TYPE_MEMORY)
{ {
// Has base register? // Absolute memory access?
if (operand->mem.base != ZYDIS_REGISTER_NONE) if (operand->mem.base == ZYDIS_REGISTER_NONE)
{ {
ZYDIS_CHECK(ZydisPrepareRegOperand(ctx, operand->mem.base, 'B')); ctx->disp = operand->mem.disp.value.sdword;
} ctx->dispBitSize = 32;
else
{ // In 32 bit mode, ModRM allows for a shortcut here.
// TODO: Does rm=0x05 work with sbyte disps? if (ctx->info->mode == ZYDIS_DISASSEMBLER_MODE_32BIT)
ctx->info->details.modrm.rm = 0x05 /* memory */; {
ctx->info->details.modrm.mod = 0x00;
ctx->info->details.modrm.rm = 0x05 /* memory */;
}
// In AMD64 mode, we have to build a SIB.
else
{
ctx->info->details.modrm.mod = 0x00;
ctx->info->details.modrm.rm = 0x04 /* SIB */;
ctx->info->details.sib.index = 0x04 /* none */;
ctx->info->details.sib.scale = 0x00 /* * 1 */;
ctx->info->details.sib.base = 0x05;
ctx->info->attributes |= ZYDIS_ATTRIB_HAS_SIB;
}
ctx->info->attributes |= ZYDIS_ATTRIB_HAS_MODRM;
break;
} }
// Base register?
ZYDIS_CHECK(ZydisPrepareRegOperand(ctx, operand->mem.base, 'B'));
// SIB byte required? // SIB byte required?
if (operand->mem.index || operand->mem.scale) if (operand->mem.index || operand->mem.scale)
{ {
switch (operand->mem.scale) switch (operand->mem.scale)
{ {
case 1: ctx->info->details.sib.index = 0x01; break; case 1: ctx->info->details.sib.index = 0x00; break;
case 2: ctx->info->details.sib.index = 0x02; break; case 2: ctx->info->details.sib.index = 0x01; break;
case 4: ctx->info->details.sib.index = 0x03; break; case 4: ctx->info->details.sib.index = 0x02; break;
case 8: ctx->info->details.sib.index = 0x04; break; case 8: ctx->info->details.sib.index = 0x03; break;
default: return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; // TODO default: return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION; // TODO
} }
// Base & index register. // Base & index register.
ZYDIS_CHECK(ZydisPrepareRegOperand(ctx, operand->mem.base, 'B'));
ctx->info->details.sib.base = ctx->info->details.modrm.rm; ctx->info->details.sib.base = ctx->info->details.modrm.rm;
ZYDIS_CHECK(ZydisPrepareRegOperand(ctx, operand->mem.index, 'X')); ZYDIS_CHECK(ZydisPrepareRegOperand(ctx, operand->mem.index, 'X'));
@ -515,14 +561,14 @@ static ZydisStatus ZydisPrepareOperand(ZydisEncoderContext* ctx,
int32_t divisor = 1; int32_t divisor = 1;
switch (tableEntry->encoding) switch (tableEntry->encoding)
{ {
case ZYDIS_OPERAND_ENCODING_RM: break; case ZYDIS_OPERAND_ENCODING_RM: break;
case ZYDIS_OPERAND_ENCODING_RM_CD2: divisor = 2; break; case ZYDIS_OPERAND_ENCODING_RM_CD2: divisor = 2; break;
case ZYDIS_OPERAND_ENCODING_RM_CD4: divisor = 4; break; case ZYDIS_OPERAND_ENCODING_RM_CD4: divisor = 4; break;
case ZYDIS_OPERAND_ENCODING_RM_CD8: divisor = 8; break; case ZYDIS_OPERAND_ENCODING_RM_CD8: divisor = 8; break;
case ZYDIS_OPERAND_ENCODING_RM_CD16: divisor = 16; break; case ZYDIS_OPERAND_ENCODING_RM_CD16: divisor = 16; break;
case ZYDIS_OPERAND_ENCODING_RM_CD32: divisor = 32; break; case ZYDIS_OPERAND_ENCODING_RM_CD32: divisor = 32; break;
case ZYDIS_OPERAND_ENCODING_RM_CD64: divisor = 64; break; case ZYDIS_OPERAND_ENCODING_RM_CD64: divisor = 64; break;
default: ZYDIS_UNREACHABLE; default: ZYDIS_UNREACHABLE;
} }
// Has compressed disp encoding and is compression possible? // Has compressed disp encoding and is compression possible?
@ -577,8 +623,16 @@ static ZydisStatus ZydisPrepareOperand(ZydisEncoderContext* ctx,
break; break;
} }
case ZYDIS_OPERAND_ENCODING_VVVV: case ZYDIS_OPERAND_ENCODING_VVVV:
break; // TODO {
int16_t reg = ZydisRegisterGetId(operand->reg);
if (reg == -1) return ZYDIS_STATUS_INVALID_PARAMETER;
// TODO: Conditional assignment instead?
ctx->info->details.vex.vvvv = (reg & 0x0F);
ctx->info->details.evex.vvvv = (reg & 0x0F);
break;
}
case ZYDIS_OPERAND_ENCODING_AAA: case ZYDIS_OPERAND_ENCODING_AAA:
return ZYDIS_STATUS_IMPOSSIBLE_INSTRUCTION;
break; // TODO break; // TODO
case ZYDIS_OPERAND_ENCODING_IMM8_LO: case ZYDIS_OPERAND_ENCODING_IMM8_LO:
{ {
@ -627,7 +681,7 @@ static ZydisStatus ZydisFindMatchingDef(const ZydisInstructionInfo* info,
ZYDIS_ASSERT(matchingEntry); ZYDIS_ASSERT(matchingEntry);
// Locate entries with matching mnemonic. // Locate entries with matching mnemonic.
// TODO: do binary search / hash based lookup instead // TODO: Do binary search / hash based lookup instead.
for (size_t i = 0; i < ZYDIS_ARRAY_SIZE(kEncoderTable); ++i) for (size_t i = 0; i < ZYDIS_ARRAY_SIZE(kEncoderTable); ++i)
{ {
const ZydisEncoderTableEntry* curEntry = &kEncoderTable[i]; const ZydisEncoderTableEntry* curEntry = &kEncoderTable[i];
@ -645,6 +699,7 @@ static ZydisStatus ZydisFindMatchingDef(const ZydisInstructionInfo* info,
// Check operands. // Check operands.
for (size_t k = 0; k < curEntry->operandCount; ++k) for (size_t k = 0; k < curEntry->operandCount; ++k)
{ {
// TODO: Match operand size.
const ZydisEncoderTableOperand* curEncoderOp = &curEntry->operands[k]; const ZydisEncoderTableOperand* curEncoderOp = &curEntry->operands[k];
const ZydisOperandInfo* curReqOp = &info->operands[k]; const ZydisOperandInfo* curReqOp = &info->operands[k];
if (curReqOp->encoding != curEncoderOp->encoding) goto continueTopLevel; if (curReqOp->encoding != curEncoderOp->encoding) goto continueTopLevel;
@ -692,6 +747,32 @@ ZydisStatus ZydisEncoderEncodeInstruction(void* buffer, size_t* bufferLen,
info->opcode = ctx.matchingEntry->opcode; info->opcode = ctx.matchingEntry->opcode;
info->attributes |= ctx.matchingEntry->mandatoryAttribs; info->attributes |= ctx.matchingEntry->mandatoryAttribs;
// TODO: Check compatibility of requested prefixes to found instruction.
// Determine required prefixes.
switch (ctx.matchingEntry->encoding)
{
case ZYDIS_INSTRUCTION_ENCODING_EVEX:
info->attributes |= ZYDIS_ATTRIB_HAS_EVEX;
break;
case ZYDIS_INSTRUCTION_ENCODING_VEX:
info->attributes |= ZYDIS_ATTRIB_HAS_VEX;
break;
case ZYDIS_INSTRUCTION_ENCODING_XOP:
info->attributes |= ZYDIS_ATTRIB_HAS_XOP;
break;
}
// Prepare prefix bits.
ZydisPrefixBit pb = ctx.matchingEntry->prefixBits;
info->details.evex.B = (pb & ZYDIS_PREFBIT_EVEX_B ) ? 1 : 0;
info->details.evex.L2 = (pb & ZYDIS_PREFBIT_EVEX_L2) ? 1 : 0;
info->details.vex.L = (pb & ZYDIS_PREFBIT_VEX_L ) ? 1 : 0;
info->details.rex.W = (pb & ZYDIS_PREFBIT_REX_W ) ? 1 : 0;
// Prepare opcode.
ZYDIS_CHECK(ZydisPrepareOpcode(&ctx));
// Analyze and prepare operands. // Analyze and prepare operands.
if (info->operandCount > ZYDIS_ARRAY_SIZE(info->operands)) if (info->operandCount > ZYDIS_ARRAY_SIZE(info->operands))
{ {
@ -713,7 +794,14 @@ ZydisStatus ZydisEncoderEncodeInstruction(void* buffer, size_t* bufferLen,
if (info->attributes & ZYDIS_ATTRIB_HAS_VEX ) ZYDIS_CHECK(ZydisEmitVEX (&ctx)); if (info->attributes & ZYDIS_ATTRIB_HAS_VEX ) ZYDIS_CHECK(ZydisEmitVEX (&ctx));
if (info->attributes & ZYDIS_ATTRIB_HAS_EVEX ) ZYDIS_CHECK(ZydisEmitEVEX (&ctx)); if (info->attributes & ZYDIS_ATTRIB_HAS_EVEX ) ZYDIS_CHECK(ZydisEmitEVEX (&ctx));
if (info->attributes & ZYDIS_ATTRIB_HAS_XOP ) ZYDIS_CHECK(ZydisEmitXOP (&ctx)); if (info->attributes & ZYDIS_ATTRIB_HAS_XOP ) ZYDIS_CHECK(ZydisEmitXOP (&ctx));
ZYDIS_CHECK(ZydisEmitOpcode(&ctx));
for (uint8_t i = 0; i < ctx.opcodeMapPrefixLen; ++i)
{
ZYDIS_CHECK(ZydisEmitByte(&ctx, ctx.opcodeMapPrefix[i]));
}
ZYDIS_CHECK(ZydisEmitByte(&ctx, info->opcode));
if (info->attributes & ZYDIS_ATTRIB_HAS_MODRM) ZYDIS_CHECK(ZydisEmitModRM(&ctx)); if (info->attributes & ZYDIS_ATTRIB_HAS_MODRM) ZYDIS_CHECK(ZydisEmitModRM(&ctx));
if (info->attributes & ZYDIS_ATTRIB_HAS_SIB ) ZYDIS_CHECK(ZydisEmitSIB (&ctx)); if (info->attributes & ZYDIS_ATTRIB_HAS_SIB ) ZYDIS_CHECK(ZydisEmitSIB (&ctx));

View File

@ -96,6 +96,12 @@ int main(int argc, char** argv)
// TODO: Remove // TODO: Remove
// DEBUG CODE START // DEBUG CODE START
for (size_t i = 0; i < info.length; ++i)
{
printf("%02X ", *(readBuf + readOffs + i));
}
putchar('\n');
uint8_t encBuffer[15]; uint8_t encBuffer[15];
size_t encBufferSize = sizeof(encBuffer); size_t encBufferSize = sizeof(encBuffer);
ZydisStatus encStatus = ZydisEncoderEncodeInstruction( ZydisStatus encStatus = ZydisEncoderEncodeInstruction(