diff --git a/api/docs/release.dox b/api/docs/release.dox
index 9552c2e9e5d..5359fe8d8d3 100644
--- a/api/docs/release.dox
+++ b/api/docs/release.dox
@@ -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.
**************************************************
diff --git a/core/ir/decode_shared.c b/core/ir/decode_shared.c
index 8379562188c..5380d80fd36 100644
--- a/core/ir/decode_shared.c
+++ b/core/ir/decode_shared.c
@@ -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",
diff --git a/core/ir/disassemble_shared.c b/core/ir/disassemble_shared.c
index a7d0d11cbb9..8293ed2d91c 100644
--- a/core/ir/disassemble_shared.c
+++ b/core/ir/disassemble_shared.c
@@ -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 "";
}
diff --git a/core/ir/opnd_api.h b/core/ir/opnd_api.h
index aeb75c3fc7a..d9e61a848be 100644
--- a/core/ir/opnd_api.h
+++ b/core/ir/opnd_api.h
@@ -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.
*/
diff --git a/core/ir/opnd_shared.c b/core/ir/opnd_shared.c
index f526e196026..11ae3eb0dc7 100644
--- a/core/ir/opnd_shared.c
+++ b/core/ir/opnd_shared.c
@@ -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 */
@@ -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 */
diff --git a/core/ir/x86/decode.c b/core/ir/x86/decode.c
index 7a6b0dc155f..efc1bbb1cd9 100644
--- a/core/ir/x86/decode.c
+++ b/core/ir/x86/decode.c
@@ -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;
}
}
@@ -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;
@@ -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");
}
@@ -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");
diff --git a/core/ir/x86/decode_private.h b/core/ir/x86/decode_private.h
index 1f037c4e83f..5c959d78cfd 100644
--- a/core/ir/x86/decode_private.h
+++ b/core/ir/x86/decode_private.h
@@ -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,
};
diff --git a/core/ir/x86/decode_table.c b/core/ir/x86/decode_table.c
index d169597fb2e..dc1ca5b24ca 100644
--- a/core/ir/x86/decode_table.c
+++ b/core/ir/x86/decode_table.c
@@ -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],
};
@@ -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
@@ -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
@@ -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},
}
};
/****************************************************************************
@@ -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},
@@ -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[] = {
@@ -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
diff --git a/core/ir/x86/disassemble.c b/core/ir/x86/disassemble.c
index 63918db5b6a..58ddb818060 100644
--- a/core/ir/x86/disassemble.c
+++ b/core/ir/x86/disassemble.c
@@ -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:
@@ -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)
{
@@ -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";
@@ -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 ");
}
diff --git a/core/ir/x86/encode.c b/core/ir/x86/encode.c
index a30b5284a9b..b8387d37788 100644
--- a/core/ir/x86/encode.c
+++ b/core/ir/x86/encode.c
@@ -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 */
@@ -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.
@@ -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)),
@@ -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) {
@@ -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)
@@ -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);
@@ -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;
@@ -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");
}
diff --git a/core/ir/x86/instr_create_api.h b/core/ir/x86/instr_create_api.h
index c83e848f8a4..7320eebc4c9 100644
--- a/core/ir/x86/instr_create_api.h
+++ b/core/ir/x86/instr_create_api.h
@@ -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 */
diff --git a/core/ir/x86/opcode_api.h b/core/ir/x86/opcode_api.h
index cdb4ac48b4e..4d824aaa5de 100644
--- a/core/ir/x86/opcode_api.h
+++ b/core/ir/x86/opcode_api.h
@@ -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. */
diff --git a/suite/tests/api/ir_x86_2args_mm.h b/suite/tests/api/ir_x86_2args_mm.h
index 56da1a943dd..0f1412b8eac 100644
--- a/suite/tests/api/ir_x86_2args_mm.h
+++ b/suite/tests/api/ir_x86_2args_mm.h
@@ -325,3 +325,31 @@ OPCODE(bndldx_b0ld, bndldx, bndldx, 0, REGARG(BND0), MEMARG(OPSZ_bnd))
OPCODE(bndldx_b3ld, bndldx, bndldx, 0, REGARG(BND3), MEMARG(OPSZ_bnd))
OPCODE(bndstx_b0st, bndstx, bndstx, 0, MEMARG(OPSZ_bnd), REGARG(BND0))
OPCODE(bndstx_b3st, bndstx, bndstx, 0, MEMARG(OPSZ_bnd), REGARG(BND3))
+
+/* MOVDIRI */
+OPCODE(movdiri32, movdiri, movdiri, 0, MEMARG(OPSZ_4), REGARG(EAX))
+OPCODE(movdiri64, movdiri, movdiri, X64_ONLY, MEMARG(OPSZ_8), REGARG(RAX))
+
+/* MOVDIR64B */
+/* NB: We can never use MEMARG for the dst because we need the segment selector. */
+/* NB: Can't use MEMARG for the src because it doesn't work with addr16 prefix. */
+OPCODE(movdir64b16, movdir64b, movdir64b, X86_ONLY,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_AX, DR_REG_NULL, 0, 0, OPSZ_64),
+ opnd_create_base_disp(DR_REG_SI, DR_REG_NULL, 0, memarg_disp, OPSZ_64))
+/* NB: Can't use MEMARG for the src because the base register needs to be the size of EAX.
+ */
+OPCODE(movdir64b32, movdir64b, movdir64b, 0,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_EAX, DR_REG_NULL, 0, 0, OPSZ_64),
+ opnd_create_base_disp(DR_REG_ECX, DR_REG_NULL, 0, memarg_disp, OPSZ_64))
+OPCODE(movdir64b32lohi, movdir64b, movdir64b, X64_ONLY,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_R8D, DR_REG_NULL, 0, 0, OPSZ_64),
+ opnd_create_base_disp(DR_REG_ECX, DR_REG_NULL, 0, memarg_disp, OPSZ_64))
+OPCODE(movdir64b32hilo, movdir64b, movdir64b, X64_ONLY,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_EAX, DR_REG_NULL, 0, 0, OPSZ_64),
+ opnd_create_base_disp(DR_REG_R9D, DR_REG_NULL, 0, memarg_disp, OPSZ_64))
+OPCODE(movdir64b64, movdir64b, movdir64b, X64_ONLY,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_RAX, DR_REG_NULL, 0, 0, OPSZ_64),
+ MEMARG(OPSZ_64))
+OPCODE(movdir64b64lohi, movdir64b, movdir64b, X64_ONLY,
+ opnd_create_far_base_disp(DR_SEG_ES, DR_REG_R9, DR_REG_NULL, 0, 0, OPSZ_64),
+ MEMARG(OPSZ_64))
diff --git a/third_party/binutils/test_decenc/drdecode_decenc_x86.expect b/third_party/binutils/test_decenc/drdecode_decenc_x86.expect
index 0bcc5bfe599..f199a399912 100644
--- a/third_party/binutils/test_decenc/drdecode_decenc_x86.expect
+++ b/third_party/binutils/test_decenc/drdecode_decenc_x86.expect
@@ -138821,7 +138821,6 @@ test_s:
90 nop
90 nop
90 nop
-
0f 01 ca clac
0f 01 cb stac
90 nop
@@ -138840,6 +138839,37 @@ test_s:
90 nop
90 nop
90 nop
+ 0f 38 f9 01 movdiri %eax, (%ecx)
+ 66 0f 38 f9 01 data16 movdiri %eax, (%ecx)
+ 67 66 0f 38 f8 04 movdir64b (%si), %ax
+ 67 66 0f 38 f8 0e 00 movdir64b 0x00, %cx
+ 00
+ 67 66 0f 38 f8 0e 34 movdir64b 0x1234, %cx
+ 12
+ 0f 38 f9 01 movdiri %eax, (%ecx)
+ 0f 38 f9 01 movdiri %eax, (%ecx)
+ 66 0f 38 f9 01 data16 movdiri %eax, (%ecx)
+ 67 66 0f 38 f8 04 movdir64b (%si), %ax
+ 67 66 0f 38 f8 0e 00 movdir64b 0x00, %cx
+ 00
+ 67 66 0f 38 f8 0e 34 movdir64b 0x1234, %cx
+ 12
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
+ 90 nop
90 nop
90 nop
90 nop
diff --git a/third_party/binutils/test_decenc/drdecode_decenc_x86_64.expect b/third_party/binutils/test_decenc/drdecode_decenc_x86_64.expect
index 51aaf58db02..ecc4c96c11b 100644
--- a/third_party/binutils/test_decenc/drdecode_decenc_x86_64.expect
+++ b/third_party/binutils/test_decenc/drdecode_decenc_x86_64.expect
@@ -102852,6 +102852,31 @@ test_x86_64_s:
49 0f c7 1c 00 xrstors64 (%r8,%rax)
4a 0f c7 1c 00 xrstors64 (%rax,%r8)
4b 0f c7 1c 38 xrstors64 (%r8,%r15)
+ 48 0f 38 f9 01 movdiri %rax, (%rcx)
+ 66 0f 38 f8 01 movdir64b (%rcx), %rax
+ 67 66 0f 38 f8 01 movdir64b (%ecx), %eax
+ 66 0f 38 f8 0d 00 00 movdir64b 0x0000000010070ac4, %rcx
+ 00 00
+ 67 66 0f 38 f8 0d 00 movdir64b 0x0000000010070ace, %ecx
+ 00 00 00
+ 67 66 0f 38 f8 0c 25 movdir64b 0x00, %ecx
+ 00 00 00 00
+ 67 66 0f 38 f8 0c 25 movdir64b 0x12345678, %ecx
+ 78 56 34 12
+ 0f 38 f9 01 movdiri %eax, (%rcx)
+ 48 0f 38 f9 01 movdiri %rax, (%rcx)
+ 0f 38 f9 01 movdiri %eax, (%rcx)
+ 48 0f 38 f9 01 movdiri %rax, (%rcx)
+ 66 0f 38 f8 01 movdir64b (%rcx), %rax
+ 67 66 0f 38 f8 01 movdir64b (%ecx), %eax
+ 66 0f 38 f8 0d 00 00 movdir64b 0x0000000010070b0a, %rcx
+ 00 00
+ 67 66 0f 38 f8 0d 00 movdir64b 0x0000000010070b14, %ecx
+ 00 00 00
+ 67 66 0f 38 f8 0c 25 movdir64b 0x00, %ecx
+ 00 00 00 00
+ 67 66 0f 38 f8 0c 25 movdir64b 0x12345678, %ecx
+ 78 56 34 12
0f 01 ca clac
0f 01 cb stac
90 nop
diff --git a/third_party/binutils/test_decenc/test_decenc_x86.asm b/third_party/binutils/test_decenc/test_decenc_x86.asm
index 7457a389868..3058afd345c 100644
--- a/third_party/binutils/test_decenc/test_decenc_x86.asm
+++ b/third_party/binutils/test_decenc/test_decenc_x86.asm
@@ -139896,5 +139896,19 @@ GLOBAL_LABEL(FUNCNAME:)
#endif /* DISABLED_UNTIL_BUG_3581_IS_FIXED */
+ /* movdir.s */
+ RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(04)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0e) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0e) RAW(34) RAW(12)
+ RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(04)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0e) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0e) RAW(34) RAW(12)
+ END_OF_SUBTEST_MARKER
+
END_OF_FUNCTION_MARKER
END_FUNC(FUNCNAME)
diff --git a/third_party/binutils/test_decenc/test_decenc_x86_64.asm b/third_party/binutils/test_decenc/test_decenc_x86_64.asm
index 644ea3c9e2e..323f4729e5c 100644
--- a/third_party/binutils/test_decenc/test_decenc_x86_64.asm
+++ b/third_party/binutils/test_decenc/test_decenc_x86_64.asm
@@ -106680,6 +106680,24 @@ GLOBAL_LABEL(FUNCNAME:)
RAW(4a) RAW(0f) RAW(c7) RAW(1c) RAW(00)
RAW(4b) RAW(0f) RAW(c7) RAW(1c) RAW(38)
+ /* x86_64_movdir.s */
+ RAW(48) RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(01)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0d) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0d) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0c) RAW(25) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0c) RAW(25) RAW(78) RAW(56) RAW(34) RAW(12)
+ RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(48) RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(48) RAW(0f) RAW(38) RAW(f9) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(01)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(01)
+ RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0d) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0d) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0c) RAW(25) RAW(00) RAW(00) RAW(00) RAW(00)
+ RAW(67) RAW(66) RAW(0f) RAW(38) RAW(f8) RAW(0c) RAW(25) RAW(78) RAW(56) RAW(34) RAW(12)
/* TODO i#5505: Move the following back under
* x86_64_arch_3.s in a separate PR to keep the huge