Skip to content

Commit ada8e41

Browse files
jxorsRot127
andauthored
x86: correctly handle XACQUIRE/XRELEASE (capstone-engine#2882)
* x86: correctly handle XACQUIRE/XRELEASE This patch changes XACQUIRE/XRELEASE decoding to happen only after all prefixes have been read. This is necessary to handle all possible orderings of prefixes. For example, both F3F0 and F0F3 should be treated as XRELEASE. The last REP prefix is taken to distinguish between XACQUIRE and XRELEASE. So F2F3F0 = XRELEASE, F3F2F0 = XACQUIRE. This behavior is specified in the Intel SDM Section 6.1 - XACQUIRE/XRELEASE. Additionally, this patch changes the disassembly printing of LOCKs and XACQUIRE/XRELEASE to reflect the actual prefixes used. Previously, XCHG instructions would always be printed without LOCK and everything else with LOCK. For this, an extra field is added to MCInst. * x86: add tests for REP on string instruction Co-authored-by: Rot127 <45763064+Rot127@users.noreply.github.com>
1 parent 7b1002d commit ada8e41

5 files changed

Lines changed: 219 additions & 132 deletions

File tree

MCInst.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct MCInst {
133133
cs_wasm_op wasm_data; // for WASM operand
134134
MCRegisterInfo *MRI;
135135
uint8_t xAcquireRelease; // X86 xacquire/xrelease
136+
uint8_t x86Lock; // Set when the X86 LOCK prefix is present
136137
bool isAliasInstr; // Flag if this MCInst is an alias.
137138
bool fillDetailOps; // If set, detail->operands gets filled.
138139
hppa_ext hppa_ext; ///< for HPPA operand. Contains info about modifiers and their effect on the instruction

arch/X86/X86Disassembler.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,7 @@ bool X86_getInstruction(csh ud, const uint8_t *code, size_t code_len,
13741374
instr->x86_prefix[2] = insn.prefix2;
13751375
instr->x86_prefix[3] = insn.prefix3;
13761376
instr->xAcquireRelease = insn.xAcquireRelease;
1377+
instr->x86Lock = insn.hasLockPrefix;
13771378

13781379
if (handle->detail_opt) {
13791380
update_pub_insn(instr->flat_insn, &insn);

arch/X86/X86DisassemblerDecoder.c

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,11 @@ static void setGroup0Prefix(struct InternalInstruction *insn, uint8_t prefix)
363363
switch (prefix) {
364364
case 0xf0: // LOCK
365365
insn->hasLockPrefix = true;
366-
insn->repeatPrefix = 0;
367366
break;
368367

369368
case 0xf2: // REPNE/REPNZ
370369
case 0xf3: // REP or REPE/REPZ
371370
insn->repeatPrefix = prefix;
372-
insn->hasLockPrefix = false;
373371
break;
374372
}
375373
}
@@ -483,31 +481,6 @@ static int readPrefixes(struct InternalInstruction *insn)
483481
if (lookAtByte(insn, &nextByte))
484482
return -1;
485483

486-
/*
487-
* If the byte is 0xf2 or 0xf3, and any of the following conditions are
488-
* met:
489-
* - it is followed by a LOCK (0xf0) prefix
490-
* - it is followed by an xchg instruction
491-
* then it should be disassembled as a xacquire/xrelease not repne/rep.
492-
*/
493-
if (((nextByte == 0xf0) ||
494-
((nextByte & 0xfe) == 0x86 ||
495-
(nextByte & 0xf8) == 0x90))) {
496-
insn->xAcquireRelease = byte;
497-
}
498-
499-
/*
500-
* Also if the byte is 0xf3, and the following condition is met:
501-
* - it is followed by a "mov mem, reg" (opcode 0x88/0x89) or
502-
* "mov mem, imm" (opcode 0xc6/0xc7) instructions.
503-
* then it should be disassembled as an xrelease not rep.
504-
*/
505-
if (byte == 0xf3 &&
506-
(nextByte == 0x88 || nextByte == 0x89 ||
507-
nextByte == 0xc6 || nextByte == 0xc7)) {
508-
insn->xAcquireRelease = byte;
509-
}
510-
511484
if (isREX(insn, nextByte)) {
512485
uint8_t nnextByte;
513486

@@ -772,6 +745,36 @@ static int readPrefixes(struct InternalInstruction *insn)
772745
} else
773746
unconsumeByte(insn);
774747

748+
if (insn->repeatPrefix != 0) {
749+
if (lookAtByte(insn, &nextByte))
750+
return -1;
751+
752+
/*
753+
* REP prefix is present, and any of the following conditions are
754+
* met:
755+
* - it is followed by a LOCK (0xf0) prefix
756+
* - it is followed by an xchg instruction (except for 0x90 - NOP/PAUSE)
757+
* then it should be disassembled as a xacquire/xrelease not repne/rep.
758+
*/
759+
if ((insn->hasLockPrefix || ((nextByte & 0xfe) == 0x86 ||
760+
(nextByte & 0xf8) == 0x90)) &&
761+
nextByte != 0x90) {
762+
insn->xAcquireRelease = insn->repeatPrefix;
763+
}
764+
765+
/*
766+
* Also if the REP prefix is 0xf3, and the following condition is met:
767+
* - it is followed by a "mov mem, reg" (opcode 0x88/0x89) or
768+
* "mov mem, imm" (opcode 0xc6/0xc7) instructions.
769+
* then it should be disassembled as an xrelease not rep.
770+
*/
771+
if (insn->repeatPrefix == 0xf3 &&
772+
(nextByte == 0x88 || nextByte == 0x89 || nextByte == 0xc6 ||
773+
nextByte == 0xc7)) {
774+
insn->xAcquireRelease = insn->repeatPrefix;
775+
}
776+
}
777+
775778
if (insn->mode == MODE_16BIT) {
776779
insn->registerSize = (insn->hasOpSize ? 4 : 2);
777780
insn->addressSize = (insn->hasAdSize ? 4 : 2);

arch/X86/X86Mapping.c

Lines changed: 64 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,20 +1929,6 @@ static bool valid_bnd(cs_struct *h, unsigned int opcode)
19291929
// not found
19301930
return false;
19311931
}
1932-
1933-
// return true if the opcode is XCHG [mem]
1934-
static bool xchg_mem(unsigned int opcode)
1935-
{
1936-
switch (opcode) {
1937-
default:
1938-
return false;
1939-
case X86_XCHG8rm:
1940-
case X86_XCHG16rm:
1941-
case X86_XCHG32rm:
1942-
case X86_XCHG64rm:
1943-
return true;
1944-
}
1945-
}
19461932
#endif
19471933

19481934
// given MCInst's id, find out if this insn is valid for REP prefix
@@ -2112,106 +2098,79 @@ bool X86_lockrep(MCInst *MI, SStream *O)
21122098
unsigned int opcode;
21132099
bool res = false;
21142100

2115-
switch (MI->x86_prefix[0]) {
2101+
#ifndef CAPSTONE_DIET
2102+
switch (MI->xAcquireRelease) {
2103+
case 0xF2:
2104+
SStream_concat(O, "xacquire|");
2105+
break;
2106+
case 0xF3:
2107+
SStream_concat(O, "xrelease|");
2108+
break;
21162109
default:
21172110
break;
2118-
case 0xf0:
2119-
#ifndef CAPSTONE_DIET
2120-
if (MI->xAcquireRelease == 0xf2)
2121-
SStream_concat(O, "xacquire|lock|");
2122-
else if (MI->xAcquireRelease == 0xf3)
2123-
SStream_concat(O, "xrelease|lock|");
2124-
else
2125-
SStream_concat(O, "lock|");
2111+
}
21262112
#endif
2127-
break;
2128-
case 0xf2: // repne
2129-
opcode = MCInst_getOpcode(MI);
21302113

2131-
#ifndef CAPSTONE_DIET // only care about memonic in standard (non-diet) mode
2132-
if (xchg_mem(opcode) && MI->xAcquireRelease) {
2133-
SStream_concat(O, "xacquire|");
2134-
} else if (valid_repne(MI->csh, opcode)) {
2135-
SStream_concat(O, "repne|");
2136-
add_cx(MI);
2137-
} else if (valid_bnd(MI->csh, opcode)) {
2138-
SStream_concat(O, "bnd|");
2139-
} else {
2140-
// invalid prefix
2141-
MI->x86_prefix[0] = 0;
2142-
2143-
// handle special cases
2144-
#ifndef CAPSTONE_X86_REDUCE
2145-
#if 0
2146-
if (opcode == X86_MULPDrr) {
2147-
MCInst_setOpcode(MI, X86_MULSDrr);
2148-
SStream_concat0(O, "mulsd\t");
2149-
res = true;
2150-
}
2151-
#endif
2152-
#endif
2153-
}
2154-
#else // diet mode -> only patch opcode in special cases
2155-
if (!valid_repne(MI->csh, opcode)) {
2156-
MI->x86_prefix[0] = 0;
2114+
if (MI->xAcquireRelease) {
2115+
if (MI->x86Lock) {
2116+
// Force LOCK prefix as group 0 prefix for XACQUIRE and XRELEASE if a LOCK is also present.
2117+
// This is an arbitrary choice, since there are effectively two group 0 prefixes present.
2118+
// The Intel SDM is not clear on how we should interpret group 0 in this case. It states:
2119+
// "it is only useful to include up to one prefix code from each of the four groups"
2120+
// ...and then defines instructions where both an F2/F3 and F0 are useful anyway.
2121+
MI->x86_prefix[0] = 0xF0;
21572122
}
2158-
#ifndef CAPSTONE_X86_REDUCE
2159-
#if 0
2160-
// handle special cases
2161-
if (opcode == X86_MULPDrr) {
2162-
MCInst_setOpcode(MI, X86_MULSDrr);
2123+
} else {
2124+
switch (MI->x86_prefix[0]) {
2125+
case 0xF2:
2126+
opcode = MCInst_getOpcode(MI);
2127+
#ifndef CAPSTONE_DIET
2128+
if (valid_repne(MI->csh, opcode)) {
2129+
SStream_concat(O, "repne|");
2130+
add_cx(MI);
2131+
} else if (valid_bnd(MI->csh, opcode)) {
2132+
SStream_concat(O, "bnd|");
2133+
} else {
2134+
// invalid prefix
2135+
MI->x86_prefix[0] = 0;
21632136
}
2164-
#endif
2165-
#endif
2166-
#endif
2167-
break;
2168-
2169-
case 0xf3:
2170-
opcode = MCInst_getOpcode(MI);
2171-
2172-
#ifndef CAPSTONE_DIET // only care about memonic in standard (non-diet) mode
2173-
if (xchg_mem(opcode) && MI->xAcquireRelease) {
2174-
SStream_concat(O, "xrelease|");
2175-
} else if (valid_rep(MI->csh, opcode)) {
2176-
SStream_concat(O, "rep|");
2177-
add_cx(MI);
2178-
} else if (valid_repe(MI->csh, opcode)) {
2179-
SStream_concat(O, "repe|");
2180-
add_cx(MI);
2181-
} else if (valid_ret_repz(MI->csh, opcode)) {
2182-
SStream_concat(O, "repz|");
2183-
} else {
2184-
// invalid prefix
2185-
MI->x86_prefix[0] = 0;
2186-
2187-
// handle special cases
2188-
#ifndef CAPSTONE_X86_REDUCE
2189-
#if 0
2190-
// FIXME: remove this special case?
2191-
if (opcode == X86_MULPDrr) {
2192-
MCInst_setOpcode(MI, X86_MULSSrr);
2193-
SStream_concat0(O, "mulss\t");
2194-
res = true;
2195-
}
2196-
#endif
2197-
#endif
2198-
}
2199-
#else // diet mode -> only patch opcode in special cases
2200-
if (!valid_rep(MI->csh, opcode) &&
2201-
!valid_repe(MI->csh, opcode)) {
2202-
MI->x86_prefix[0] = 0;
2203-
}
2204-
#ifndef CAPSTONE_X86_REDUCE
2205-
#if 0
2206-
// handle special cases
2207-
// FIXME: remove this special case?
2208-
if (opcode == X86_MULPDrr) {
2209-
MCInst_setOpcode(MI, X86_MULSSrr);
2137+
#else
2138+
if (!valid_repne(MI->csh, opcode)) {
2139+
MI->x86_prefix[0] = 0;
22102140
}
22112141
#endif
2142+
break;
2143+
case 0xF3:
2144+
opcode = MCInst_getOpcode(MI);
2145+
#ifndef CAPSTONE_DIET
2146+
if (valid_rep(MI->csh, opcode)) {
2147+
SStream_concat(O, "rep|");
2148+
add_cx(MI);
2149+
} else if (valid_repe(MI->csh, opcode)) {
2150+
SStream_concat(O, "repe|");
2151+
add_cx(MI);
2152+
} else if (valid_ret_repz(MI->csh, opcode)) {
2153+
SStream_concat(O, "repz|");
2154+
} else {
2155+
// invalid prefix
2156+
MI->x86_prefix[0] = 0;
2157+
}
2158+
#else
2159+
if (!valid_rep(MI->csh, opcode) &&
2160+
!valid_repe(MI->csh, opcode)) {
2161+
MI->x86_prefix[0] = 0;
2162+
}
22122163
#endif
2213-
#endif
2214-
break;
2164+
break;
2165+
default:
2166+
break;
2167+
}
2168+
}
2169+
2170+
// LOCK and F2/F3 may both be present (for XACQUIRE/XRELEASE).
2171+
// There are also XRELEASEs that can be LOCKless.
2172+
if (MI->x86Lock) {
2173+
SStream_concat(O, "lock|");
22152174
}
22162175

22172176
switch (MI->x86_prefix[1]) {

0 commit comments

Comments
 (0)