| |
| #include "fadec.h" |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #ifdef __GNUC__ |
| #define LIKELY(x) __builtin_expect((x), 1) |
| #define UNLIKELY(x) __builtin_expect((x), 0) |
| #if __has_builtin(__builtin_assume) |
| #define ASSUME(x) __builtin_assume(x) |
| #else |
| #define ASSUME(x) (void)((x) ? (void)0 : __builtin_unreachable()) |
| #endif |
| #else |
| #define LIKELY(x) (x) |
| #define UNLIKELY(x) (x) |
| #define ASSUME(x) ((void)0) |
| #endif |
| |
| // Defines FD_TABLE_OFFSET_32 and FD_TABLE_OFFSET_64, if available |
| #define FD_DECODE_TABLE_DEFINES |
| #include <fadec-decode-private.inc> |
| #undef FD_DECODE_TABLE_DEFINES |
| |
| enum DecodeMode { |
| DECODE_64 = 0, |
| DECODE_32 = 1, |
| }; |
| |
| typedef enum DecodeMode DecodeMode; |
| |
| #define ENTRY_NONE 0 |
| #define ENTRY_INSTR 1 |
| #define ENTRY_TABLE256 2 |
| #define ENTRY_TABLE16 3 |
| #define ENTRY_TABLE8E 4 |
| #define ENTRY_TABLE_PREFIX 5 |
| #define ENTRY_TABLE_VEX 6 |
| #define ENTRY_TABLE_ROOT 8 |
| #define ENTRY_MASK 7 |
| |
| static uint16_t table_lookup(unsigned cur_idx, unsigned entry_idx) { |
| static _Alignas(16) const uint16_t _decode_table[] = { |
| #define FD_DECODE_TABLE_DATA |
| #include <fadec-decode-private.inc> |
| #undef FD_DECODE_TABLE_DATA |
| }; |
| return _decode_table[cur_idx + entry_idx]; |
| } |
| |
| static unsigned table_walk(unsigned table_entry, unsigned entry_idx) { |
| return table_lookup(table_entry & ~0x3, entry_idx); |
| } |
| |
| #define LOAD_LE_1(buf) ((uint64_t)*(const uint8_t*)(buf)) |
| #define LOAD_LE_2(buf) \ |
| (LOAD_LE_1(buf) | LOAD_LE_1((const uint8_t*)(buf) + 1) << 8) |
| #define LOAD_LE_3(buf) \ |
| (LOAD_LE_2(buf) | LOAD_LE_1((const uint8_t*)(buf) + 2) << 16) |
| #define LOAD_LE_4(buf) \ |
| (LOAD_LE_2(buf) | LOAD_LE_2((const uint8_t*)(buf) + 2) << 16) |
| #define LOAD_LE_8(buf) \ |
| (LOAD_LE_4(buf) | LOAD_LE_4((const uint8_t*)(buf) + 4) << 32) |
| |
| enum { |
| PREFIX_REXB = 0x01, |
| PREFIX_REXX = 0x02, |
| PREFIX_REXR = 0x04, |
| PREFIX_REXW = 0x08, |
| PREFIX_REX = 0x40, |
| PREFIX_REXRR = 0x10, |
| PREFIX_VEX = 0x20, |
| }; |
| |
| struct InstrDesc { |
| uint16_t type; |
| uint16_t operand_indices; |
| uint16_t operand_sizes; |
| uint16_t reg_types; |
| }; |
| |
| #define DESC_HAS_MODRM(desc) (((desc)->operand_indices & (3 << 0)) != 0) |
| #define DESC_MODRM_IDX(desc) ((((desc)->operand_indices >> 0) & 3) ^ 3) |
| #define DESC_HAS_MODREG(desc) (((desc)->operand_indices & (3 << 2)) != 0) |
| #define DESC_MODREG_IDX(desc) ((((desc)->operand_indices >> 2) & 3) ^ 3) |
| #define DESC_HAS_VEXREG(desc) (((desc)->operand_indices & (3 << 4)) != 0) |
| #define DESC_VEXREG_IDX(desc) ((((desc)->operand_indices >> 4) & 3) ^ 3) |
| #define DESC_IMM_CONTROL(desc) (((desc)->operand_indices >> 12) & 0x7) |
| #define DESC_IMM_IDX(desc) ((((desc)->operand_indices >> 6) & 3) ^ 3) |
| #define DESC_EVEX_BCST(desc) (((desc)->operand_indices >> 8) & 1) |
| #define DESC_EVEX_MASK(desc) (((desc)->operand_indices >> 9) & 1) |
| #define DESC_ZEROREG_VAL(desc) (((desc)->operand_indices >> 10) & 1) |
| #define DESC_LOCK(desc) (((desc)->operand_indices >> 11) & 1) |
| #define DESC_VSIB(desc) (((desc)->operand_indices >> 15) & 1) |
| #define DESC_OPSIZE(desc) (((desc)->reg_types >> 11) & 7) |
| #define DESC_MODRM_SIZE(desc) (((desc)->operand_sizes >> 0) & 3) |
| #define DESC_MODREG_SIZE(desc) (((desc)->operand_sizes >> 2) & 3) |
| #define DESC_VEXREG_SIZE(desc) (((desc)->operand_sizes >> 4) & 3) |
| #define DESC_IMM_SIZE(desc) (((desc)->operand_sizes >> 6) & 3) |
| #define DESC_LEGACY(desc) (((desc)->operand_sizes >> 8) & 1) |
| #define DESC_SIZE_FIX1(desc) (((desc)->operand_sizes >> 10) & 7) |
| #define DESC_SIZE_FIX2(desc) (((desc)->operand_sizes >> 13) & 3) |
| #define DESC_INSTR_WIDTH(desc) (((desc)->operand_sizes >> 15) & 1) |
| #define DESC_MODRM(desc) (((desc)->reg_types >> 14) & 1) |
| #define DESC_IGN66(desc) (((desc)->reg_types >> 15) & 1) |
| #define DESC_EVEX_SAE(desc) (((desc)->reg_types >> 8) & 1) |
| #define DESC_EVEX_ER(desc) (((desc)->reg_types >> 9) & 1) |
| #define DESC_EVEX_BCST16(desc) (((desc)->reg_types >> 10) & 1) |
| #define DESC_REGTY_MODRM(desc) (((desc)->reg_types >> 0) & 7) |
| #define DESC_REGTY_MODREG(desc) (((desc)->reg_types >> 3) & 7) |
| #define DESC_REGTY_VEXREG(desc) (((desc)->reg_types >> 6) & 3) |
| |
| int fd_decode(const uint8_t* buffer, size_t len_sz, int mode_int, |
| uintptr_t address, FdInstr* instr) { |
| int len = len_sz > 15 ? 15 : len_sz; |
| |
| // Ensure that we can actually handle the decode request |
| DecodeMode mode; |
| unsigned table_root_idx; |
| switch (mode_int) { |
| #if defined(FD_TABLE_OFFSET_32) |
| case 32: table_root_idx = FD_TABLE_OFFSET_32, mode = DECODE_32; break; |
| #endif |
| #if defined(FD_TABLE_OFFSET_64) |
| case 64: table_root_idx = FD_TABLE_OFFSET_64, mode = DECODE_64; break; |
| #endif |
| default: return FD_ERR_INTERNAL; |
| } |
| |
| int off = 0; |
| uint8_t vex_operand = 0; |
| |
| uint8_t addr_size = mode == DECODE_64 ? 3 : 2; |
| unsigned prefix_rex = 0; |
| uint8_t prefix_rep = 0; |
| unsigned vexl = 0; |
| unsigned prefix_evex = 0; |
| instr->segment = FD_REG_NONE; |
| |
| // Values must match prefixes in parseinstrs.py. |
| enum { |
| // Null prefix. 64-bit: 26/2e/36 |
| PF_SEG1 = 0xfff8 - 0xfff8, |
| // Segment override. 32-bit: 26/2e/36/3e/64/65; 64-bit: 64/65 |
| PF_SEG2 = 0xfff9 - 0xfff8, |
| // 3e prefix in 64-bit mode for notrack/bht. |
| PF_SEG3 = 0xfffa - 0xfff8, |
| PF_66 = 0xfffb - 0xfff8, |
| PF_67 = 0xfffc - 0xfff8, |
| PF_LOCK = 0xfffd - 0xfff8, |
| // Repeat prefix: f2/f3 |
| PF_REP = 0xfffe - 0xfff8, |
| PF_REX = 0xffff - 0xfff8, |
| }; |
| |
| uint8_t prefixes[8] = {0}; |
| unsigned table_entry = 0; |
| while (true) { |
| if (UNLIKELY(off >= len)) |
| return FD_ERR_PARTIAL; |
| uint8_t prefix = buffer[off]; |
| table_entry = table_lookup(table_root_idx, prefix); |
| if (LIKELY(table_entry - 0xfff8 >= 8)) |
| break; |
| prefixes[PF_REX] = 0; |
| prefixes[table_entry - 0xfff8] = prefix; |
| off++; |
| } |
| if (off) { |
| if (UNLIKELY(prefixes[PF_SEG2] || prefixes[PF_SEG3])) { |
| if (prefixes[PF_SEG2] & 0x02) { |
| ASSUME(mode == DECODE_32); // 26/2e/36/3e, 32-bit mode only. |
| instr->segment = prefixes[PF_SEG2] >> 3 & 3; |
| if (instr->segment == FD_REG_DS) |
| instr->segment |= 0x40; // Set 3e bit. |
| } else if (prefixes[PF_SEG2]) { |
| instr->segment = prefixes[PF_SEG2] & 7; |
| } else { |
| ASSUME(mode == DECODE_64); // 3e, 64-bit mode only. |
| instr->segment = 0x40 | FD_REG_NONE; // Set 3e bit. |
| } |
| } |
| if (UNLIKELY(prefixes[PF_67])) |
| addr_size--; |
| prefix_rex = prefixes[PF_REX]; |
| prefix_rep = prefixes[PF_REP]; |
| } |
| |
| // table_entry kinds: INSTR(0), T16(1), ESCAPE_A(2), ESCAPE_B(3) |
| if (LIKELY(!(table_entry & 2))) { |
| off++; |
| |
| // Then, walk through ModR/M-encoded opcode extensions. |
| if (table_entry & 1) { |
| if (UNLIKELY(off >= len)) |
| return FD_ERR_PARTIAL; |
| unsigned isreg = buffer[off] >= 0xc0; |
| table_entry = table_walk(table_entry, ((buffer[off] >> 2) & 0xe) | isreg); |
| // table_entry kinds: INSTR(0), T8E(1) |
| if (table_entry & 1) |
| table_entry = table_walk(table_entry, buffer[off] & 7); |
| } |
| |
| // table_entry kinds: INSTR(0) |
| goto direct; |
| } |
| |
| if (UNLIKELY(off + 1 >= len)) |
| return FD_ERR_PARTIAL; |
| |
| unsigned opcode_escape = 0; |
| uint8_t mandatory_prefix = 0; // without escape/VEX/EVEX, this is ignored. |
| if (buffer[off] == 0x0f) { |
| if (buffer[off + 1] == 0x38) |
| opcode_escape = 2; |
| else if (buffer[off + 1] == 0x3a) |
| opcode_escape = 3; |
| else |
| opcode_escape = 1; |
| off += opcode_escape >= 2 ? 2 : 1; |
| |
| // If there is no REP/REPNZ prefix offer 66h as mandatory prefix. If |
| // there is a REP prefix, then the 66h prefix is ignored here. |
| mandatory_prefix = prefix_rep ? prefix_rep ^ 0xf1 : !!prefixes[PF_66]; |
| } else { |
| unsigned vex_prefix = buffer[off]; |
| // VEX (C4/C5) or EVEX (62) |
| if (UNLIKELY(mode == DECODE_32 && buffer[off + 1] < 0xc0)) { |
| off++; |
| table_entry = table_walk(table_entry, 0); |
| // table_entry kinds: INSTR(0) |
| goto direct; |
| } |
| |
| // VEX/EVEX + 66/F3/F2/REX will #UD. |
| // Note: REX is also here only respected if it immediately precedes the |
| // opcode, in this case the VEX/EVEX "prefix". |
| if (prefixes[PF_66] || prefixes[PF_REP] || prefix_rex) |
| return FD_ERR_UD; |
| |
| uint8_t byte = buffer[off + 1]; |
| if (vex_prefix == 0xc5) { // 2-byte VEX |
| opcode_escape = 1; |
| prefix_rex = byte & 0x80 ? 0 : PREFIX_REXR; |
| } else { // 3-byte VEX or EVEX |
| // SDM Vol 2A 2-15 (Dec. 2016): Ignored in 32-bit mode |
| if (mode == DECODE_64) |
| prefix_rex = byte >> 5 ^ 0x7; |
| if (vex_prefix == 0x62) { // EVEX |
| if (byte & 0x08) |
| return FD_ERR_UD; // Bit 3 of opcode_escape must be clear. |
| _Static_assert(PREFIX_REXRR == 0x10, "wrong REXRR value"); |
| if (mode == DECODE_64) |
| prefix_rex |= (byte & PREFIX_REXRR) ^ PREFIX_REXRR; |
| } else { // 3-byte VEX |
| if (byte & 0x18) |
| return FD_ERR_UD; // Bits 4:3 of opcode_escape must be clear. |
| } |
| |
| opcode_escape = (byte & 0x07); |
| if (UNLIKELY(opcode_escape == 0)) { |
| int prefix_len = vex_prefix == 0x62 ? 4 : 3; |
| // Pretend to decode the prefix plus one opcode byte. |
| return off + prefix_len >= len ? FD_ERR_PARTIAL : FD_ERR_UD; |
| } |
| |
| // Load third byte of VEX prefix |
| if (UNLIKELY(off + 2 >= len)) |
| return FD_ERR_PARTIAL; |
| byte = buffer[off + 2]; |
| prefix_rex |= byte & 0x80 ? PREFIX_REXW : 0; |
| } |
| |
| mandatory_prefix = byte & 3; |
| vex_operand = ((byte & 0x78) >> 3) ^ 0xf; |
| prefix_rex |= PREFIX_VEX; |
| |
| if (vex_prefix == 0x62) { // EVEX |
| if (!(byte & 0x04)) |
| return FD_ERR_UD; // Bit 10 must be 1. |
| if (UNLIKELY(off + 3 >= len)) |
| return FD_ERR_PARTIAL; |
| byte = buffer[off + 3]; |
| // prefix_evex is z:L'L/RC:b:V':aaa |
| vexl = (byte >> 5) & 3; |
| prefix_evex = byte | 0x100; // Ensure that prefix_evex is non-zero. |
| if (mode == DECODE_64) |
| vex_operand |= byte & 0x08 ? 0 : 0x10; // V' |
| else if (!(byte & 0x08)) |
| return FD_ERR_UD; // V' causes UD in 32-bit mode |
| off += 4; |
| } else { // VEX |
| vexl = byte & 0x04 ? 1 : 0; |
| off += 0xc7 - vex_prefix; // 3 for c4, 2 for c5 |
| } |
| } |
| |
| table_entry = table_walk(table_entry, opcode_escape); |
| // table_entry kinds: INSTR(0) [only for invalid], T256(2) |
| if (UNLIKELY(!table_entry)) |
| return FD_ERR_UD; |
| if (UNLIKELY(off >= len)) |
| return FD_ERR_PARTIAL; |
| table_entry = table_walk(table_entry, buffer[off++]); |
| // table_entry kinds: INSTR(0), T16(1), TVEX(2), TPREFIX(3) |
| |
| // Handle mandatory prefixes (which behave like an opcode ext.). |
| if ((table_entry & 3) == 3) |
| table_entry = table_walk(table_entry, mandatory_prefix); |
| // table_entry kinds: INSTR(0), T16(1), TVEX(2) |
| |
| // Then, walk through ModR/M-encoded opcode extensions. |
| if (table_entry & 1) { |
| if (UNLIKELY(off >= len)) |
| return FD_ERR_PARTIAL; |
| unsigned isreg = buffer[off] >= 0xc0; |
| table_entry = table_walk(table_entry, ((buffer[off] >> 2) & 0xe) | isreg); |
| // table_entry kinds: INSTR(0), T8E(1), TVEX(2) |
| if (table_entry & 1) |
| table_entry = table_walk(table_entry, buffer[off] & 7); |
| } |
| // table_entry kinds: INSTR(0), TVEX(2) |
| |
| // For VEX prefix, we have to distinguish between VEX.W and VEX.L which may |
| // be part of the opcode. |
| if (UNLIKELY(table_entry & 2)) { |
| uint8_t index = 0; |
| index |= prefix_rex & PREFIX_REXW ? (1 << 0) : 0; |
| // When EVEX.L'L is the rounding mode, the instruction must not have |
| // L'L constraints. |
| index |= vexl << 1; |
| table_entry = table_walk(table_entry, index); |
| } |
| // table_entry kinds: INSTR(0) |
| |
| direct: |
| // table_entry kinds: INSTR(0) |
| if (UNLIKELY(!table_entry)) |
| return FD_ERR_UD; |
| |
| static _Alignas(16) const struct InstrDesc descs[] = { |
| #define FD_DECODE_TABLE_DESCS |
| #include <fadec-decode-private.inc> |
| #undef FD_DECODE_TABLE_DESCS |
| }; |
| const struct InstrDesc* desc = &descs[table_entry >> 2]; |
| |
| instr->type = desc->type; |
| instr->addrsz = addr_size; |
| instr->operandsz = 0; |
| instr->flags = ((prefix_rep + 1) & 6) + (mode == DECODE_64 ? FD_FLAG_64 : 0); |
| instr->address = address; |
| |
| for (unsigned i = 0; i < sizeof(instr->operands) / sizeof(FdOp); i++) |
| instr->operands[i] = (FdOp){0}; |
| |
| if (DESC_MODRM(desc) && UNLIKELY(off++ >= len)) |
| return FD_ERR_PARTIAL; |
| unsigned op_byte = buffer[off - 1] | (!DESC_MODRM(desc) ? 0xc0 : 0); |
| |
| if (UNLIKELY(prefix_evex)) { |
| // VSIB inst (gather/scatter) without mask register or w/EVEX.z is UD |
| if (DESC_VSIB(desc) && (!(prefix_evex & 0x07) || (prefix_evex & 0x80))) |
| return FD_ERR_UD; |
| // Inst doesn't support masking, so EVEX.z or EVEX.aaa is UD |
| if (!DESC_EVEX_MASK(desc) && (prefix_evex & 0x87)) |
| return FD_ERR_UD; |
| // EVEX.z without EVEX.aaa is UD. The Intel SDM is rather unprecise |
| // about this, but real hardware doesn't accept this. |
| if ((prefix_evex & 0x87) == 0x80) |
| return FD_ERR_UD; |
| |
| // Cases for SAE/RC (reg operands only): |
| // - ER supported -> all ok |
| // - SAE supported -> assume L'L is RC, but ignored (undocumented) |
| // - Neither supported -> b == 0 |
| if ((prefix_evex & 0x10) && (op_byte & 0xc0) == 0xc0) { // EVEX.b+reg |
| if (!DESC_EVEX_SAE(desc)) |
| return FD_ERR_UD; |
| vexl = 2; |
| if (DESC_EVEX_ER(desc)) |
| instr->evex = prefix_evex; |
| else |
| instr->evex = (prefix_evex & 0x87) | 0x60; // set RC, clear B |
| } else { |
| if (UNLIKELY(vexl == 3)) // EVEX.L'L == 11b is UD |
| return FD_ERR_UD; |
| instr->evex = prefix_evex & 0x87; // clear RC, clear B |
| } |
| |
| if (DESC_VSIB(desc)) |
| vex_operand &= 0xf; // EVEX.V' is used as index extension instead. |
| } else { |
| instr->evex = 0; |
| } |
| |
| unsigned op_size; |
| unsigned op_size_alt = 0; |
| if (!(DESC_OPSIZE(desc) & 4)) { |
| if (mode == DECODE_64) |
| op_size = ((prefix_rex & PREFIX_REXW) || DESC_OPSIZE(desc) == 3) ? 4 |
| : UNLIKELY(prefixes[PF_66] && !DESC_IGN66(desc)) ? 2 |
| : DESC_OPSIZE(desc) ? 4 |
| : 3; |
| else |
| op_size = UNLIKELY(prefixes[PF_66] && !DESC_IGN66(desc)) ? 2 : 3; |
| } else { |
| op_size = 5 + vexl; |
| op_size_alt = op_size - (DESC_OPSIZE(desc) & 3); |
| } |
| |
| uint8_t operand_sizes[4] = {DESC_SIZE_FIX1(desc), DESC_SIZE_FIX2(desc) + 1, |
| op_size, op_size_alt}; |
| |
| if (UNLIKELY(instr->type == FDI_MOV_CR || instr->type == FDI_MOV_DR)) { |
| unsigned modreg = (op_byte >> 3) & 0x7; |
| unsigned modrm = op_byte & 0x7; |
| |
| FdOp* op_modreg = &instr->operands[DESC_MODREG_IDX(desc)]; |
| op_modreg->type = FD_OT_REG; |
| op_modreg->size = op_size; |
| op_modreg->reg = modreg | (prefix_rex & PREFIX_REXR ? 8 : 0); |
| op_modreg->misc = instr->type == FDI_MOV_CR ? FD_RT_CR : FD_RT_DR; |
| if (instr->type == FDI_MOV_CR && (~0x011d >> op_modreg->reg) & 1) |
| return FD_ERR_UD; |
| else if (instr->type == FDI_MOV_DR && prefix_rex & PREFIX_REXR) |
| return FD_ERR_UD; |
| |
| FdOp* op_modrm = &instr->operands[DESC_MODRM_IDX(desc)]; |
| op_modrm->type = FD_OT_REG; |
| op_modrm->size = op_size; |
| op_modrm->reg = modrm | (prefix_rex & PREFIX_REXB ? 8 : 0); |
| op_modrm->misc = FD_RT_GPL; |
| goto skip_modrm; |
| } |
| |
| if (DESC_HAS_MODREG(desc)) { |
| FdOp* op_modreg = &instr->operands[DESC_MODREG_IDX(desc)]; |
| unsigned reg_idx = (op_byte & 0x38) >> 3; |
| unsigned reg_ty = DESC_REGTY_MODREG(desc); |
| op_modreg->misc = reg_ty; |
| if (LIKELY(reg_ty < 2)) |
| reg_idx += prefix_rex & PREFIX_REXR ? 8 : 0; |
| else if (reg_ty == 7 && (prefix_rex & PREFIX_REXR || prefix_evex & 0x80)) |
| return FD_ERR_UD; // REXR in 64-bit mode or EVEX.z with mask as dest |
| if (UNLIKELY(reg_ty == FD_RT_VEC)) // REXRR ignored above in 32-bit mode |
| reg_idx += prefix_rex & PREFIX_REXRR ? 16 : 0; |
| else if (UNLIKELY(prefix_rex & PREFIX_REXRR)) |
| return FD_ERR_UD; |
| op_modreg->type = FD_OT_REG; |
| op_modreg->size = operand_sizes[DESC_MODREG_SIZE(desc)]; |
| op_modreg->reg = reg_idx; |
| } |
| |
| if (DESC_HAS_MODRM(desc)) { |
| FdOp* op_modrm = &instr->operands[DESC_MODRM_IDX(desc)]; |
| op_modrm->size = operand_sizes[DESC_MODRM_SIZE(desc)]; |
| |
| unsigned rm = op_byte & 0x07; |
| if (op_byte >= 0xc0) { |
| uint8_t reg_idx = rm; |
| unsigned reg_ty = DESC_REGTY_MODRM(desc); |
| op_modrm->misc = reg_ty; |
| if (LIKELY(reg_ty < 2)) |
| reg_idx += prefix_rex & PREFIX_REXB ? 8 : 0; |
| if (prefix_evex && reg_ty == 0) // vector registers only |
| reg_idx += prefix_rex & PREFIX_REXX ? 16 : 0; |
| op_modrm->type = FD_OT_REG; |
| op_modrm->reg = reg_idx; |
| } else { |
| unsigned dispscale = 0; |
| |
| if (UNLIKELY(prefix_evex)) { |
| // EVEX.z for memory destination operand is UD. |
| if (UNLIKELY(prefix_evex & 0x80) && DESC_MODRM_IDX(desc) == 0) |
| return FD_ERR_UD; |
| |
| // EVEX.b for memory-operand without broadcast support is UD. |
| if (UNLIKELY(prefix_evex & 0x10)) { |
| if (UNLIKELY(!DESC_EVEX_BCST(desc))) |
| return FD_ERR_UD; |
| if (UNLIKELY(DESC_EVEX_BCST16(desc))) |
| dispscale = 1; |
| else |
| dispscale = prefix_rex & PREFIX_REXW ? 3 : 2; |
| instr->operandsz = dispscale; // Store broadcast size. |
| op_modrm->type = FD_OT_MEMBCST; |
| } else { |
| dispscale = op_modrm->size - 1; |
| op_modrm->type = FD_OT_MEM; |
| } |
| } else { |
| op_modrm->type = FD_OT_MEM; |
| } |
| |
| // 16-bit address size implies different ModRM encoding |
| if (UNLIKELY(addr_size == 1)) { |
| ASSUME(mode == DECODE_32); |
| if (UNLIKELY(DESC_VSIB(desc))) // 16-bit addr size + VSIB is UD |
| return FD_ERR_UD; |
| if (rm < 6) |
| op_modrm->misc = rm & 1 ? FD_REG_DI : FD_REG_SI; |
| else |
| op_modrm->misc = FD_REG_NONE; |
| |
| if (rm < 4) |
| op_modrm->reg = rm & 2 ? FD_REG_BP : FD_REG_BX; |
| else if (rm < 6 || (op_byte & 0xc7) == 0x06) |
| op_modrm->reg = FD_REG_NONE; |
| else |
| op_modrm->reg = rm == 6 ? FD_REG_BP : FD_REG_BX; |
| |
| const uint8_t* dispbase = &buffer[off]; |
| if (op_byte & 0x40) { |
| if (UNLIKELY((off += 1) > len)) |
| return FD_ERR_PARTIAL; |
| instr->disp = (int8_t)LOAD_LE_1(dispbase) * (1 << dispscale); |
| } else if (op_byte & 0x80 || (op_byte & 0xc7) == 0x06) { |
| if (UNLIKELY((off += 2) > len)) |
| return FD_ERR_PARTIAL; |
| instr->disp = (int16_t)LOAD_LE_2(dispbase); |
| } else { |
| instr->disp = 0; |
| } |
| goto end_modrm; |
| } |
| |
| // SIB byte |
| uint8_t base = rm; |
| if (rm == 4) { |
| if (UNLIKELY(off >= len)) |
| return FD_ERR_PARTIAL; |
| uint8_t sib = buffer[off++]; |
| unsigned scale = sib & 0xc0; |
| unsigned idx = (sib & 0x38) >> 3; |
| idx += prefix_rex & PREFIX_REXX ? 8 : 0; |
| base = sib & 0x07; |
| if (idx == 4) |
| idx = FD_REG_NONE; |
| op_modrm->misc = scale | idx; |
| } else { |
| op_modrm->misc = FD_REG_NONE; |
| } |
| |
| if (UNLIKELY(DESC_VSIB(desc))) { |
| // VSIB must have a memory operand with SIB byte. |
| if (rm != 4) |
| return FD_ERR_UD; |
| _Static_assert(FD_REG_NONE == 0x3f, "unexpected FD_REG_NONE"); |
| // idx 4 is valid for VSIB |
| if ((op_modrm->misc & 0x3f) == FD_REG_NONE) |
| op_modrm->misc &= 0xc4; |
| if (prefix_evex) // EVEX.V':EVEX.X:SIB.idx |
| op_modrm->misc |= prefix_evex & 0x8 ? 0 : 0x10; |
| } |
| |
| // RIP-relative addressing only if SIB-byte is absent |
| if (op_byte < 0x40 && rm == 5 && mode == DECODE_64) |
| op_modrm->reg = FD_REG_IP; |
| else if (op_byte < 0x40 && base == 5) |
| op_modrm->reg = FD_REG_NONE; |
| else |
| op_modrm->reg = base + (prefix_rex & PREFIX_REXB ? 8 : 0); |
| |
| const uint8_t* dispbase = &buffer[off]; |
| if (op_byte & 0x40) { |
| if (UNLIKELY((off += 1) > len)) |
| return FD_ERR_PARTIAL; |
| instr->disp = (int8_t)LOAD_LE_1(dispbase) * (1 << dispscale); |
| } else if (op_byte & 0x80 || base == 5) { |
| if (UNLIKELY((off += 4) > len)) |
| return FD_ERR_PARTIAL; |
| instr->disp = (int32_t)LOAD_LE_4(dispbase); |
| } else { |
| instr->disp = 0; |
| } |
| end_modrm:; |
| } |
| } |
| |
| if (UNLIKELY(DESC_HAS_VEXREG(desc))) { |
| FdOp* operand = &instr->operands[DESC_VEXREG_IDX(desc)]; |
| if (DESC_ZEROREG_VAL(desc)) { |
| operand->type = FD_OT_REG; |
| operand->size = 1; |
| operand->reg = FD_REG_CL; |
| operand->misc = FD_RT_GPL; |
| } else { |
| operand->type = FD_OT_REG; |
| // Without VEX prefix, this encodes an implicit register |
| operand->size = operand_sizes[DESC_VEXREG_SIZE(desc)]; |
| if (mode == DECODE_32) |
| vex_operand &= 0x7; |
| // Note: 32-bit will never UD here. EVEX.V' is caught above already. |
| // Note: UD if > 16 for non-VEC. No EVEX-encoded instruction uses |
| // EVEX.vvvv to refer to non-vector registers. Verified in parseinstrs. |
| operand->reg = vex_operand; |
| |
| unsigned reg_ty = DESC_REGTY_VEXREG(desc); // VEC GPL MSK FPU/TMM |
| if (prefix_rex & PREFIX_VEX) { // TMM with VEX, FPU otherwise |
| // In 64-bit mode: UD if FD_RT_MASK and vex_operand&8 != 0 |
| if (reg_ty == 2 && vex_operand >= 8) |
| return FD_ERR_UD; |
| if (reg_ty == 1 && vex_operand >= 16) |
| return FD_ERR_UD; |
| if (UNLIKELY(reg_ty == 3)) // TMM |
| operand->reg &= 0x7; // TODO: verify |
| operand->misc = (06710 >> (3 * reg_ty)) & 0x7; |
| } else { |
| operand->misc = (04710 >> (3 * reg_ty)) & 0x7; |
| } |
| } |
| } else if (vex_operand != 0) { |
| // TODO: bit 3 ignored in 32-bit mode? unverified |
| return FD_ERR_UD; |
| } |
| |
| uint32_t imm_control = UNLIKELY(DESC_IMM_CONTROL(desc)); |
| if (LIKELY(!imm_control)) { |
| } else if (UNLIKELY(imm_control == 1)) { |
| // 1 = immediate constant 1, used for shifts |
| FdOp* operand = &instr->operands[DESC_IMM_IDX(desc)]; |
| operand->type = FD_OT_IMM; |
| operand->size = 1; |
| instr->imm = 1; |
| } else if (UNLIKELY(imm_control == 2)) { |
| // 2 = memory, address-sized, used for mov with moffs operand |
| FdOp* operand = &instr->operands[DESC_IMM_IDX(desc)]; |
| operand->type = FD_OT_MEM; |
| operand->size = operand_sizes[DESC_IMM_SIZE(desc)]; |
| operand->reg = FD_REG_NONE; |
| operand->misc = FD_REG_NONE; |
| |
| int moffsz = 1 << addr_size; |
| if (UNLIKELY(off + moffsz > len)) |
| return FD_ERR_PARTIAL; |
| if (moffsz == 2) |
| instr->disp = LOAD_LE_2(&buffer[off]); |
| if (moffsz == 4) |
| instr->disp = LOAD_LE_4(&buffer[off]); |
| if (LIKELY(moffsz == 8)) |
| instr->disp = LOAD_LE_8(&buffer[off]); |
| off += moffsz; |
| } else if (UNLIKELY(imm_control == 3)) { |
| // 3 = register in imm8[7:4], used for RVMR encoding with VBLENDVP[SD] |
| FdOp* operand = &instr->operands[DESC_IMM_IDX(desc)]; |
| operand->type = FD_OT_REG; |
| operand->size = op_size; |
| operand->misc = FD_RT_VEC; |
| |
| if (UNLIKELY(off + 1 > len)) |
| return FD_ERR_PARTIAL; |
| uint8_t reg = (uint8_t)LOAD_LE_1(&buffer[off]); |
| off += 1; |
| |
| if (mode == DECODE_32) |
| reg &= 0x7f; |
| operand->reg = reg >> 4; |
| instr->imm = reg & 0x0f; |
| } else { |
| // 4/5 = immediate, operand-sized/8 bit |
| // 6/7 = offset, operand-sized/8 bit (used for jumps/calls) |
| int imm_byte = imm_control & 1; |
| int imm_offset = imm_control & 2; |
| |
| FdOp* operand = &instr->operands[DESC_IMM_IDX(desc)]; |
| operand->type = FD_OT_IMM; |
| |
| if (imm_byte) { |
| if (UNLIKELY(off + 1 > len)) |
| return FD_ERR_PARTIAL; |
| instr->imm = (int8_t)LOAD_LE_1(&buffer[off++]); |
| operand->size = DESC_IMM_SIZE(desc) & 1 ? 1 : op_size; |
| } else { |
| operand->size = operand_sizes[DESC_IMM_SIZE(desc)]; |
| |
| uint8_t imm_size; |
| if (UNLIKELY(instr->type == FDI_RET || instr->type == FDI_RETF || |
| instr->type == FDI_SSE_EXTRQ || |
| instr->type == FDI_SSE_INSERTQ)) |
| imm_size = 2; |
| else if (UNLIKELY(instr->type == FDI_JMPF || instr->type == FDI_CALLF)) |
| imm_size = (1 << op_size >> 1) + 2; |
| else if (UNLIKELY(instr->type == FDI_ENTER)) |
| imm_size = 3; |
| else if (instr->type == FDI_MOVABS) |
| imm_size = (1 << op_size >> 1); |
| else |
| imm_size = op_size == 2 ? 2 : 4; |
| |
| if (UNLIKELY(off + imm_size > len)) |
| return FD_ERR_PARTIAL; |
| |
| if (imm_size == 2) |
| instr->imm = (int16_t)LOAD_LE_2(&buffer[off]); |
| else if (imm_size == 3) |
| instr->imm = LOAD_LE_3(&buffer[off]); |
| else if (imm_size == 4) |
| instr->imm = (int32_t)LOAD_LE_4(&buffer[off]); |
| else if (imm_size == 6) |
| instr->imm = LOAD_LE_4(&buffer[off]) | LOAD_LE_2(&buffer[off + 4]) |
| << 32; |
| else |
| instr->imm = (int64_t)LOAD_LE_8(&buffer[off]); |
| off += imm_size; |
| } |
| |
| if (imm_offset) { |
| if (instr->address != 0) |
| instr->imm += instr->address + off; |
| else |
| operand->type = FD_OT_OFF; |
| } |
| } |
| |
| skip_modrm: |
| if (UNLIKELY(prefixes[PF_LOCK])) { |
| if (!DESC_LOCK(desc) || instr->operands[0].type != FD_OT_MEM) |
| return FD_ERR_UD; |
| instr->flags |= FD_FLAG_LOCK; |
| } |
| |
| if (UNLIKELY(DESC_LEGACY(desc))) { |
| // Without REX prefix, convert one-byte GP regs to high-byte regs |
| // This actually only applies to SZ8/MOVSX/MOVZX; but no VEX-encoded |
| // instructions have a byte-sized GP register in the first two operands. |
| if (!(prefix_rex & PREFIX_REX)) { |
| for (int i = 0; i < 2; i++) { |
| FdOp* operand = &instr->operands[i]; |
| if (operand->type == FD_OT_NONE) |
| break; |
| if (operand->type == FD_OT_REG && operand->misc == FD_RT_GPL && |
| operand->size == 1 && operand->reg >= 4) |
| operand->misc = FD_RT_GPH; |
| } |
| } |
| |
| if (instr->type == FDI_XCHG_NOP) { |
| // Only 4890, 90, and 6690 are true NOPs. |
| if (instr->operands[0].reg == 0) { |
| instr->operands[0].type = FD_OT_NONE; |
| instr->operands[1].type = FD_OT_NONE; |
| instr->type = FD_HAS_REP(instr) ? FDI_PAUSE : FDI_NOP; |
| } else if ((instr->operands[0].reg & 7) == 0 && FD_HAS_REP(instr)) { |
| // On Intel, REX.B is ignored for F3.90. |
| instr->operands[0].type = FD_OT_NONE; |
| instr->operands[1].type = FD_OT_NONE; |
| instr->type = FDI_PAUSE; |
| } else { |
| instr->type = FDI_XCHG; |
| } |
| } |
| |
| if (UNLIKELY(instr->type == FDI_3DNOW)) { |
| unsigned opc3dn = instr->imm; |
| if (opc3dn & 0x40) |
| return FD_ERR_UD; |
| uint64_t msk = opc3dn & 0x80 ? 0x88d144d144d14400 : 0x30003000; |
| if (!(msk >> (opc3dn & 0x3f) & 1)) |
| return FD_ERR_UD; |
| } |
| |
| // Instructions with the legacy bit have no broadcast, so ok to overwrite. |
| instr->operandsz = UNLIKELY(DESC_INSTR_WIDTH(desc)) ? op_size - 1 : 0; |
| } |
| |
| instr->size = off; |
| |
| return off; |
| } |