Skip to content

Commit

Permalink
Add decode/encode support for Intel's direct store extensions. (#7097)
Browse files Browse the repository at this point in the history
Two new instructions (MOVDIRI and MOVDIR64B) are added. PREFIX_EXT is used for MOVDIR64B because the enqueue stores will also go there later.

MOVDIR64B is a strange instruction because the encoded destination operand is a register but it's really an offset relative to the ES segment register. Furthermore, the address prefix controls the size of that register operand, not the data prefix. To handle this a new operand type TYPE_G_ES_VAR_REG_SIZE is introduced. In the DR IR this is translated to/from a far base disp operand with DR_SEG_ES set, a null index register, and zero fixed displacement.

For internal processing of the base register for the far base disp a new operand size OPSZ_addr is introduced. This operand size is the processor word size, or half that size if the address prefix is present. OPSZ_addr can be reused in a later commit to replace the users of resolve_addr_size().

There are some additional shenangians in the disassembler to suppress address size prefixes and suffixes to match the binutils assembly syntax for MOVDIR64B.
  • Loading branch information
khuey authored Dec 6, 2024
1 parent 6002bb8 commit b0237d2
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 15 deletions.
2 changes: 2 additions & 0 deletions api/docs/release.dox
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ Further non-compatibility-affecting changes include:
supported only when fast FP save and restore is supported. And mixed mode is not
supported.
- Added the AArch64 FPMR register as DR_REG_FPMR.
- Added OPSZ_addr which chooses the appropriate operand size on X86 from 2/4/8 based
on the 32/64 bit mode and the presence or absence of the address size prefix.

**************************************************
<hr>
Expand Down
1 change: 1 addition & 0 deletions core/ir/decode_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const char *const size_names[] = {
"OPSZ_8x16",
"OPSZ_256",
"OPSZ_192",
"OPSZ_addr",
"OPSZ_1_of_4",
"OPSZ_2_of_4",
"OPSZ_1_of_8",
Expand Down
1 change: 1 addition & 0 deletions core/ir/disassemble_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ opnd_size_suffix_intel(opnd_t opnd)
case 12: return "";
case 16: return "oword";
case 32: return "yword";
case 64: return "zword";
}
return "";
}
Expand Down
1 change: 1 addition & 0 deletions core/ir/opnd_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ enum {
*/
OPSZ_256, /**< 256 bytes. Needed for RISC-V vector extension with LMUL. */
OPSZ_192, /**< 192 bytes. The size of 3 512-bit SVE Z registers. */
OPSZ_addr, /**< OPSZ_4x8 but varies by the address prefix, not the data prefix. */
/* Add new size here. Also update size_names[] in decode_shared.c along with
* the size routines in opnd_shared.c.
*/
Expand Down
2 changes: 2 additions & 0 deletions core/ir/opnd_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,7 @@ opnd_size_in_bytes(opnd_size_t size)
case OPSZ_4x8: /* default size */
case OPSZ_4x8_short2: /* default size */
case OPSZ_4x8_short2xi8: /* default size */
case OPSZ_addr: /* default size */
#endif
case OPSZ_4_short2xi4: /* default size */
case OPSZ_4_rex8_short2: /* default size */
Expand All @@ -1940,6 +1941,7 @@ opnd_size_in_bytes(opnd_size_t size)
case OPSZ_4x8: /* default size */
case OPSZ_4x8_short2: /* default size */
case OPSZ_4x8_short2xi8: /* default size */
case OPSZ_addr: /* default size */
#endif
case OPSZ_8_rex16: /* default size */
case OPSZ_8_rex16_short4: /* default size */
Expand Down
19 changes: 18 additions & 1 deletion core/ir/x86/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ is_variable_size(opnd_size_t sz)
case OPSZ_16_vex32:
case OPSZ_16_vex32_evex64:
case OPSZ_vex32_evex64:
case OPSZ_8x16: return true;
case OPSZ_8x16:
case OPSZ_addr: return true;
default: return false;
}
}
Expand Down Expand Up @@ -316,6 +317,9 @@ resolve_variable_size(decode_info_t *di /*IN: x86_mode, prefixes*/, opnd_size_t
? OPSZ_8
: (TEST(PREFIX_VEX_L, di->prefixes) ? OPSZ_4 : OPSZ_2));
case OPSZ_8x16: return IF_X64_ELSE(OPSZ_16, OPSZ_8);
case OPSZ_addr:
return (TEST(PREFIX_ADDR, di->prefixes) ? (X64_MODE(di) ? OPSZ_4 : OPSZ_2)
: (X64_MODE(di) ? OPSZ_8 : OPSZ_4));
}

return sz;
Expand Down Expand Up @@ -1585,6 +1589,10 @@ decode_reg(decode_reg_t which_reg, decode_info_t *di, byte optype, opnd_size_t o
case TYPE_FLOATMEM:
/* GPR: fall-through since variable subset of full register */
break;
case TYPE_G_ES_VAR_REG_SIZE: {
opsize = OPSZ_addr;
break;
}
default: CLIENT_ASSERT(false, "internal unknown reg error");
}

Expand Down Expand Up @@ -2214,6 +2222,15 @@ decode_operand(decode_info_t *di, byte optype, opnd_size_t opsize, opnd_t *opnd)
return true;
}
case TYPE_T_MODRM: return decode_modrm(di, optype, opsize, NULL, opnd);
case TYPE_G_ES_VAR_REG_SIZE: {
/* NB: we want the register size to match the address size, not opsize. */
if (!decode_modrm(di, optype, OPSZ_addr, opnd, NULL)) {
return false;
}
reg_id_t reg = opnd_get_reg(*opnd);
*opnd = opnd_create_far_base_disp(DR_SEG_ES, reg, REG_NULL, 0, 0, opsize);
return true;
}
default:
/* ok to assert, types coming only from instr_info_t */
CLIENT_ASSERT(false, "decode error: unknown operand type");
Expand Down
5 changes: 5 additions & 0 deletions core/ir/x86/decode_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,11 @@ enum {
* memory in 32-bit mode, or 16 bytes memory in 64-bit
* mode.
*/
TYPE_G_ES_VAR_REG_SIZE, /* modrm.reg selects register (like TYPE_G)
* containing an offset from ES, but (unlike TYPE_G)
* treated as a memory operand, with size controlled
* by PREFIX_ADDR.
*/
/* when adding new types, update type_names[] in encode.c */
TYPE_BEYOND_LAST_ENUM,
};
Expand Down
37 changes: 30 additions & 7 deletions core/ir/x86/decode_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,12 @@ const instr_info_t * const op_instr[] =

/* SERIALIZE */
/* OP_serialize */ &prefix_extensions[191][0],

/* MOVDIRI */
/* OP_movdiri */ &third_byte_38[173],

/* MOVDIR64B */
/* OP_movdir64b */ &prefix_extensions[192][2],
};


Expand Down Expand Up @@ -1858,7 +1864,8 @@ const instr_info_t * const op_instr[] =
#define My TYPE_M, OPSZ_4_rex8
#define Mw TYPE_M, OPSZ_2
#define Mm TYPE_M, OPSZ_lea
#define Moq TYPE_M, OPSZ_512
#define Moq TYPE_M, OPSZ_64
#define M512 TYPE_M, OPSZ_512
#define Mxsave TYPE_M, OPSZ_xsave
#define Mps TYPE_M, OPSZ_16
#define Mpd TYPE_M, OPSZ_16
Expand Down Expand Up @@ -1900,6 +1907,7 @@ const instr_info_t * const op_instr[] =
#define c1 TYPE_1, OPSZ_0
/* we pick the right constant based on the opcode */
#define cF TYPE_FLOATCONST, OPSZ_0
#define GesvS_oq TYPE_G_ES_VAR_REG_SIZE, OPSZ_64

/* registers that are base 32 but vary down or up */
#define eAX TYPE_VAR_REG, REG_EAX
Expand Down Expand Up @@ -5905,6 +5913,19 @@ const instr_info_t prefix_extensions[][12] = {
{INVALID, 0xf301e808, catUncategorized, "(bad)" , xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0x6601e808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf201e808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
},{ /* prefix extension 192 */
{INVALID, 0x38f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf338f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{OP_movdir64b, 0x6638f808, catMove, "movdir64b", GesvS_oq, xx, Moq, xx, xx, mrm, x, END_LIST},
{INVALID, 0xf238f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0x38f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf338f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0x6638f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf238f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0x38f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf338f808, catUncategorized, "(bad)" , xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0x6638f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
{INVALID, 0xf238f808, catUncategorized, "(bad)", xx, xx, xx, xx, xx, no, x, NA},
}
};
/****************************************************************************
Expand Down Expand Up @@ -7243,12 +7264,12 @@ const instr_info_t rex_b_extensions[][2] = {
*/
const instr_info_t rex_w_extensions[][2] = {
{ /* rex.w extension 0 */
{OP_fxsave32, 0x0fae30, catFP | catState, "fxsave", Moq, xx, xx, xx, xx, mrm, x, END_LIST},
{OP_fxsave64, 0x0fae30, catFP | catState, "fxsave64", Moq, xx, xx, xx, xx, mrm|rex, x, END_LIST},
{OP_fxsave32, 0x0fae30, catFP | catState, "fxsave", M512, xx, xx, xx, xx, mrm, x, END_LIST},
{OP_fxsave64, 0x0fae30, catFP | catState, "fxsave64", M512, xx, xx, xx, xx, mrm|rex, x, END_LIST},
},
{ /* rex.w extension 1 */
{OP_fxrstor32, 0x0fae31, catFP | catState, "fxrstor", xx, xx, Moq, xx, xx, mrm, x, END_LIST},
{OP_fxrstor64, 0x0fae31, catFP | catState, "fxrstor64", xx, xx, Moq, xx, xx, mrm|rex, o64, END_LIST},
{OP_fxrstor32, 0x0fae31, catFP | catState, "fxrstor", xx, xx, M512, xx, xx, mrm, x, END_LIST},
{OP_fxrstor64, 0x0fae31, catFP | catState, "fxrstor64", xx, xx, M512, xx, xx, mrm|rex, o64, END_LIST},
},
{ /* rex.w extension 2 */
{OP_xsave32, 0x0fae34, catFP | catState, "xsave", Mxsave, xx, edx, eax, xx, mrm, x, END_LIST},
Expand Down Expand Up @@ -7313,7 +7334,7 @@ const byte third_byte_38_index[256] = {
0, 0, 0, 0, 155, 0,163,164, 154,165,131,132, 152,153, 0, 0, /* C */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 52, 53, 54, 55, /* D */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E */
47, 48,100, 99, 0,101,102, 98, 0, 0, 0, 0, 0, 0, 0, 0 /* F */
47, 48,100, 99, 0,101,102, 98, 172,173, 0, 0, 0, 0, 0, 0 /* F */
};

const instr_info_t third_byte_38[] = {
Expand Down Expand Up @@ -7508,7 +7529,9 @@ const instr_info_t third_byte_38[] = {
{E_VEX_EXT, 0x66385308, catUncategorized, "(e_vex ext 152)", xx, xx, xx, xx, xx, mrm|evex|reqp, x, 152},/*169*/
{PREFIX_EXT, 0x387208, catUncategorized, "(prefix ext 190)", xx, xx, xx, xx, xx, mrm|evex, x, 190},/*170*/
/* AVX512 VPOPCNTDQ */
{EVEX_Wb_EXT, 0x66385518, catUncategorized, "(evex_Wb ext 274)", xx, xx, xx, xx, xx, mrm|evex|reqp, x, 274}/*171*/
{EVEX_Wb_EXT, 0x66385518, catUncategorized, "(evex_Wb ext 274)", xx, xx, xx, xx, xx, mrm|evex|reqp, x, 274},/*171*/
{PREFIX_EXT, 0x38f808, catUncategorized, "(prefix ext 192)", xx, xx, xx, xx, xx, mrm, x, 192},/*172*/
{OP_movdiri, 0x38f908, catMove, "movdiri", My, xx, Gy, xx, xx, mrm, x, END_LIST},/*173*/
};

/* N.B.: every 0x3a instr so far has an immediate. If a version w/o an immed
Expand Down
25 changes: 23 additions & 2 deletions core/ir/x86/disassemble.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ opnd_disassemble_noimplicit(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOU
print_to_buffer(buf, bufsz, sofar, ", ");
internal_opnd_disassemble(buf, bufsz, sofar, dcontext, opnd, false);
return true;
case TYPE_G_ES_VAR_REG_SIZE: {
if (prev)
print_to_buffer(buf, bufsz, sofar, ", ");
reg_id_t reg = opnd_get_base(opnd);
reg_disassemble(buf, bufsz, sofar, reg, 0, "", "");
return true;
}
case TYPE_X:
case TYPE_XLAT:
case TYPE_MASKMOVQ:
Expand Down Expand Up @@ -276,6 +283,18 @@ instr_opcode_name(instr_t *instr)
return NULL;
}

static bool
suppress_memory_size_annotations(instr_t *instr)
{
/* A more principled approach would be to examine all operands for the presence of
* TYPE_G_ES_VAR_REG_SIZE but this is sufficient for now.
*/
switch (instr_get_opcode(instr)) {
case OP_movdir64b: return true;
default: return false;
}
}

static const char *
instr_opcode_name_suffix(instr_t *instr)
{
Expand Down Expand Up @@ -336,7 +355,8 @@ instr_opcode_name_suffix(instr_t *instr)
* and then go back and add the suffix. This will do for now.
*/
if (instr_num_srcs(instr) > 0 && !opnd_is_reg(instr_get_src(instr, 0)) &&
instr_num_dsts(instr) > 0 && !opnd_is_reg(instr_get_dst(instr, 0))) {
instr_num_dsts(instr) > 0 && !opnd_is_reg(instr_get_dst(instr, 0)) &&
!suppress_memory_size_annotations(instr)) {
uint sz = instr_memory_reference_size(instr);
if (sz == 1)
return "b";
Expand Down Expand Up @@ -382,7 +402,8 @@ print_instr_prefixes(dcontext_t *dcontext, instr_t *instr, char *buf, size_t buf
if (!TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask))) {
if (TEST(PREFIX_DATA, instr->prefixes))
print_to_buffer(buf, bufsz, sofar, "data16 ");
if (TEST(PREFIX_ADDR, instr->prefixes)) {
if (TEST(PREFIX_ADDR, instr->prefixes) &&
!suppress_memory_size_annotations(instr)) {
print_to_buffer(buf, bufsz, sofar,
X64_MODE_DC(dcontext) ? "addr32 " : "addr16 ");
}
Expand Down
43 changes: 39 additions & 4 deletions core/ir/x86/encode.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const char *const type_names[] = {
"TYPE_K_EVEX",
"TYPE_T_REG",
"TYPE_T_MODRM",
"TYPE_G_ES_VAR_REG_SIZE",
};

/* order corresponds to enum of REG_ and SEG_ constants */
Expand Down Expand Up @@ -689,7 +690,7 @@ type_uses_evex_aaa_bits(int type)
}
}

/* Helper routine that sets/checks rex.w or data prefix, if necessary, for
/* Helper routine that sets/checks rex.w, data, or addr prefix, if necessary, for
* variable-sized OPSZ_ constants that the user asks for. We try to be flexible
* setting/checking only enough prefix flags to ensure that the final template size
* is one of the possible sizes in the request.
Expand Down Expand Up @@ -943,12 +944,12 @@ size_ok(decode_info_t *di /*prefixes field is IN/OUT; x86_mode is IN*/,
/* for OPSZ_4x8_short2, does the addr prefix select 4 instead of 2 bytes? */
bool addr_short4 = X64_MODE(di) && addr;
/* Assumption: the only addr-specified operands that can be short
* are OPSZ_4x8_short2 and OPSZ_4x8_short2xi8, or
* are OPSZ_4x8_short2, OPSZ_4x8_short2xi8 and OPSZ_addr, or
* OPSZ_4_short2 for x86 mode on x64.
* Stack memrefs can pass addr==true and OPSZ_4x8.
*/
CLIENT_ASSERT(!addr || size_template == OPSZ_4x8 ||
size_template == OPSZ_4x8_short2xi8 ||
size_template == OPSZ_4x8_short2xi8 || size_template == OPSZ_addr ||
size_template ==
OPSZ_4x8_short2 IF_X64(
|| (!X64_MODE(di) && size_template == OPSZ_4_short2)),
Expand Down Expand Up @@ -980,6 +981,10 @@ size_ok(decode_info_t *di /*prefixes field is IN/OUT; x86_mode is IN*/,
}
return false;
case OPSZ_2:
if (!X64_MODE(di) && size_template == OPSZ_addr) {
di->prefixes |= prefix_data_addr;
return true;
}
if (size_template == OPSZ_2_short1)
return !TEST(prefix_data_addr, di->prefixes);
if (size_template == OPSZ_4_short2 || size_template == OPSZ_8_short2) {
Expand All @@ -1000,6 +1005,12 @@ size_ok(decode_info_t *di /*prefixes field is IN/OUT; x86_mode is IN*/,
}
return false;
case OPSZ_4:
if (size_template == OPSZ_addr) {
if (!X64_MODE(di))
return !TEST(prefix_data_addr, di->prefixes);
di->prefixes |= prefix_data_addr;
return true;
}
if (size_template == OPSZ_4_short2)
return !TEST(prefix_data_addr, di->prefixes);
if (size_template == OPSZ_4_rex8_short2)
Expand Down Expand Up @@ -1053,7 +1064,8 @@ size_ok(decode_info_t *di /*prefixes field is IN/OUT; x86_mode is IN*/,
di->prefixes |= PREFIX_REX_W; /* rex.w trumps data prefix */
return true;
}
if (size_template == OPSZ_8_short4 || size_template == OPSZ_8_short2)
if ((X64_MODE(di) && size_template == OPSZ_addr) ||
size_template == OPSZ_8_short4 || size_template == OPSZ_8_short2)
return !TEST(prefix_data_addr, di->prefixes);
if (size_template == OPSZ_8_rex16 || size_template == OPSZ_8_rex16_short4)
return !TESTANY(prefix_data_addr | PREFIX_REX_W, di->prefixes);
Expand Down Expand Up @@ -1806,6 +1818,11 @@ opnd_type_ok(decode_info_t *di /*prefixes field is IN/OUT; x86_mode is IN*/, opn
return (opnd_is_reg(opnd) &&
reg_size_ok(di, opnd_get_reg(opnd), optype, opsize, false /*!addr*/) &&
reg_is_bnd(opnd_get_reg(opnd)));
case TYPE_G_ES_VAR_REG_SIZE:
return (opnd_is_far_base_disp(opnd) && opnd_get_segment(opnd) == DR_SEG_ES &&
opnd_get_disp(opnd) == 0 && opnd_get_index(opnd) == REG_NULL &&
reg_is_gpr(opnd_get_base(opnd)) &&
reg_size_ok(di, opnd_get_base(opnd), optype, OPSZ_addr, true /*addr*/));
default:
CLIENT_ASSERT(false, "encode error: type ok: unknown operand type");
return false;
Expand Down Expand Up @@ -2727,6 +2744,24 @@ encode_operand(decode_info_t *di, int optype, opnd_size_t opsize, opnd_t opnd)
di->evex_aaa = (byte)(reg - DR_REG_START_OPMASK);
return;
}
case TYPE_G_ES_VAR_REG_SIZE:
CLIENT_ASSERT(opnd_is_memory_reference(opnd),
"encode error: operand must be a memory reference");
CLIENT_ASSERT(opnd_is_far_base_disp(opnd),
"encode error: operand must be a far base disp");
/* NB: We don't actually set di->seg_override because the ES segment is
* inherent to the operand type.
*/
CLIENT_ASSERT(opnd_get_segment(opnd) == DR_SEG_ES,
"encode error: operand must be ES-relative");
CLIENT_ASSERT(opnd_get_disp(opnd) == 0, "encode error: operand must have 0 disp");
CLIENT_ASSERT(opnd_get_index(opnd) == REG_NULL,
"encode error: operand must not have an index");
reg_id_t reg = opnd_get_base(opnd);
CLIENT_ASSERT(reg_is_gpr(reg), "encode error: base reg must be GPR");
encode_reg_ext_prefixes(di, reg, PREFIX_REX_R);
di->reg = reg_get_bits(reg);
return;

default: CLIENT_ASSERT(false, "encode error: unknown operand type");
}
Expand Down
5 changes: 5 additions & 0 deletions core/ir/x86/instr_create_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,11 @@
#define INSTR_CREATE_bndmk(dc, d, s) instr_create_1dst_1src((dc), OP_bndmk, (d), (s))
#define INSTR_CREATE_bndldx(dc, d, s) instr_create_1dst_1src((dc), OP_bndldx, (d), (s))
#define INSTR_CREATE_bndstx(dc, d, s) instr_create_1dst_1src((dc), OP_bndstx, (d), (s))
/* MOVDIRI */
#define INSTR_CREATE_movdiri(dc, d, s) instr_create_1dst_1src((dc), OP_movdiri, (d), (s))
/* MOVDIR64B */
#define INSTR_CREATE_movdir64b(dc, d, s) \
instr_create_1dst_1src((dc), OP_movdir64b, (d), (s))
/** @} */ /* end doxygen group */

/* 1 destination, 1 implicit source */
Expand Down
6 changes: 6 additions & 0 deletions core/ir/x86/opcode_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,12 @@ enum {
/* SERIALIZE */
/* 1443 */ OP_serialize, /**< IA-32/AMD64 serialize opcode. */

/* MOVDIRI */
/* 1444 */ OP_movdiri, /**< IA-32/AMD64 movdiri opcode. */

/* MOVDIR64B */
/* 1445 */ OP_movdir64b, /**< IA-32/AMD64 movdir64b opcode. */

OP_AFTER_LAST,
OP_FIRST = OP_add, /**< First real opcode. */
OP_LAST = OP_AFTER_LAST - 1, /**< Last real opcode. */
Expand Down
Loading

0 comments on commit b0237d2

Please sign in to comment.