mirror of
https://github.com/capstone-engine/capstone.git
synced 2026-11-07 09:26:00 -10:00
x86: correctly handle XACQUIRE/XRELEASE (#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 <[email protected]>
This commit is contained in:
1 parent
7b1002db6b
commit
ada8e412c9
5 files changed
+221
-134
No files matched your search
@@ -133,6 +133,7 @@ struct MCInst {
|
||||
cs_wasm_op wasm_data; // for WASM operand
|
||||
MCRegisterInfo *MRI;
|
||||
uint8_t xAcquireRelease; // X86 xacquire/xrelease
|
||||
uint8_t x86Lock; // Set when the X86 LOCK prefix is present
|
||||
bool isAliasInstr; // Flag if this MCInst is an alias.
|
||||
bool fillDetailOps; // If set, detail->operands gets filled.
|
||||
hppa_ext hppa_ext; ///< for HPPA operand. Contains info about modifiers and their effect on the instruction
|
||||
|
||||
@@ -1374,6 +1374,7 @@ bool X86_getInstruction(csh ud, const uint8_t *code, size_t code_len,
|
||||
instr->x86_prefix[2] = insn.prefix2;
|
||||
instr->x86_prefix[3] = insn.prefix3;
|
||||
instr->xAcquireRelease = insn.xAcquireRelease;
|
||||
instr->x86Lock = insn.hasLockPrefix;
|
||||
|
||||
if (handle->detail_opt) {
|
||||
update_pub_insn(instr->flat_insn, &insn);
|
||||
|
||||
@@ -363,13 +363,11 @@ static void setGroup0Prefix(struct InternalInstruction *insn, uint8_t prefix)
|
||||
switch (prefix) {
|
||||
case 0xf0: // LOCK
|
||||
insn->hasLockPrefix = true;
|
||||
insn->repeatPrefix = 0;
|
||||
break;
|
||||
|
||||
case 0xf2: // REPNE/REPNZ
|
||||
case 0xf3: // REP or REPE/REPZ
|
||||
insn->repeatPrefix = prefix;
|
||||
insn->hasLockPrefix = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -483,31 +481,6 @@ static int readPrefixes(struct InternalInstruction *insn)
|
||||
if (lookAtByte(insn, &nextByte))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* If the byte is 0xf2 or 0xf3, and any of the following conditions are
|
||||
* met:
|
||||
* - it is followed by a LOCK (0xf0) prefix
|
||||
* - it is followed by an xchg instruction
|
||||
* then it should be disassembled as a xacquire/xrelease not repne/rep.
|
||||
*/
|
||||
if (((nextByte == 0xf0) ||
|
||||
((nextByte & 0xfe) == 0x86 ||
|
||||
(nextByte & 0xf8) == 0x90))) {
|
||||
insn->xAcquireRelease = byte;
|
||||
}
|
||||
|
||||
/*
|
||||
* Also if the byte is 0xf3, and the following condition is met:
|
||||
* - it is followed by a "mov mem, reg" (opcode 0x88/0x89) or
|
||||
* "mov mem, imm" (opcode 0xc6/0xc7) instructions.
|
||||
* then it should be disassembled as an xrelease not rep.
|
||||
*/
|
||||
if (byte == 0xf3 &&
|
||||
(nextByte == 0x88 || nextByte == 0x89 ||
|
||||
nextByte == 0xc6 || nextByte == 0xc7)) {
|
||||
insn->xAcquireRelease = byte;
|
||||
}
|
||||
|
||||
if (isREX(insn, nextByte)) {
|
||||
uint8_t nnextByte;
|
||||
|
||||
@@ -772,6 +745,36 @@ static int readPrefixes(struct InternalInstruction *insn)
|
||||
} else
|
||||
unconsumeByte(insn);
|
||||
|
||||
if (insn->repeatPrefix != 0) {
|
||||
if (lookAtByte(insn, &nextByte))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* REP prefix is present, and any of the following conditions are
|
||||
* met:
|
||||
* - it is followed by a LOCK (0xf0) prefix
|
||||
* - it is followed by an xchg instruction (except for 0x90 - NOP/PAUSE)
|
||||
* then it should be disassembled as a xacquire/xrelease not repne/rep.
|
||||
*/
|
||||
if ((insn->hasLockPrefix || ((nextByte & 0xfe) == 0x86 ||
|
||||
(nextByte & 0xf8) == 0x90)) &&
|
||||
nextByte != 0x90) {
|
||||
insn->xAcquireRelease = insn->repeatPrefix;
|
||||
}
|
||||
|
||||
/*
|
||||
* Also if the REP prefix is 0xf3, and the following condition is met:
|
||||
* - it is followed by a "mov mem, reg" (opcode 0x88/0x89) or
|
||||
* "mov mem, imm" (opcode 0xc6/0xc7) instructions.
|
||||
* then it should be disassembled as an xrelease not rep.
|
||||
*/
|
||||
if (insn->repeatPrefix == 0xf3 &&
|
||||
(nextByte == 0x88 || nextByte == 0x89 || nextByte == 0xc6 ||
|
||||
nextByte == 0xc7)) {
|
||||
insn->xAcquireRelease = insn->repeatPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
if (insn->mode == MODE_16BIT) {
|
||||
insn->registerSize = (insn->hasOpSize ? 4 : 2);
|
||||
insn->addressSize = (insn->hasAdSize ? 4 : 2);
|
||||
|
||||
+66
-107
@@ -1929,20 +1929,6 @@ static bool valid_bnd(cs_struct *h, unsigned int opcode)
|
||||
// not found
|
||||
return false;
|
||||
}
|
||||
|
||||
// return true if the opcode is XCHG [mem]
|
||||
static bool xchg_mem(unsigned int opcode)
|
||||
{
|
||||
switch (opcode) {
|
||||
default:
|
||||
return false;
|
||||
case X86_XCHG8rm:
|
||||
case X86_XCHG16rm:
|
||||
case X86_XCHG32rm:
|
||||
case X86_XCHG64rm:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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)
|
||||
unsigned int opcode;
|
||||
bool res = false;
|
||||
|
||||
switch (MI->x86_prefix[0]) {
|
||||
#ifndef CAPSTONE_DIET
|
||||
switch (MI->xAcquireRelease) {
|
||||
case 0xF2:
|
||||
SStream_concat(O, "xacquire|");
|
||||
break;
|
||||
case 0xF3:
|
||||
SStream_concat(O, "xrelease|");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 0xf0:
|
||||
}
|
||||
#endif
|
||||
|
||||
if (MI->xAcquireRelease) {
|
||||
if (MI->x86Lock) {
|
||||
// Force LOCK prefix as group 0 prefix for XACQUIRE and XRELEASE if a LOCK is also present.
|
||||
// This is an arbitrary choice, since there are effectively two group 0 prefixes present.
|
||||
// The Intel SDM is not clear on how we should interpret group 0 in this case. It states:
|
||||
// "it is only useful to include up to one prefix code from each of the four groups"
|
||||
// ...and then defines instructions where both an F2/F3 and F0 are useful anyway.
|
||||
MI->x86_prefix[0] = 0xF0;
|
||||
}
|
||||
} else {
|
||||
switch (MI->x86_prefix[0]) {
|
||||
case 0xF2:
|
||||
opcode = MCInst_getOpcode(MI);
|
||||
#ifndef CAPSTONE_DIET
|
||||
if (MI->xAcquireRelease == 0xf2)
|
||||
SStream_concat(O, "xacquire|lock|");
|
||||
else if (MI->xAcquireRelease == 0xf3)
|
||||
SStream_concat(O, "xrelease|lock|");
|
||||
else
|
||||
SStream_concat(O, "lock|");
|
||||
#endif
|
||||
break;
|
||||
case 0xf2: // repne
|
||||
opcode = MCInst_getOpcode(MI);
|
||||
|
||||
#ifndef CAPSTONE_DIET // only care about memonic in standard (non-diet) mode
|
||||
if (xchg_mem(opcode) && MI->xAcquireRelease) {
|
||||
SStream_concat(O, "xacquire|");
|
||||
} else if (valid_repne(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repne|");
|
||||
add_cx(MI);
|
||||
} else if (valid_bnd(MI->csh, opcode)) {
|
||||
SStream_concat(O, "bnd|");
|
||||
} else {
|
||||
// invalid prefix
|
||||
MI->x86_prefix[0] = 0;
|
||||
|
||||
// handle special cases
|
||||
#ifndef CAPSTONE_X86_REDUCE
|
||||
#if 0
|
||||
if (opcode == X86_MULPDrr) {
|
||||
MCInst_setOpcode(MI, X86_MULSDrr);
|
||||
SStream_concat0(O, "mulsd\t");
|
||||
res = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#else // diet mode -> only patch opcode in special cases
|
||||
if (!valid_repne(MI->csh, opcode)) {
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#ifndef CAPSTONE_X86_REDUCE
|
||||
#if 0
|
||||
// handle special cases
|
||||
if (opcode == X86_MULPDrr) {
|
||||
MCInst_setOpcode(MI, X86_MULSDrr);
|
||||
if (valid_repne(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repne|");
|
||||
add_cx(MI);
|
||||
} else if (valid_bnd(MI->csh, opcode)) {
|
||||
SStream_concat(O, "bnd|");
|
||||
} else {
|
||||
// invalid prefix
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#else
|
||||
if (!valid_repne(MI->csh, opcode)) {
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
|
||||
case 0xf3:
|
||||
opcode = MCInst_getOpcode(MI);
|
||||
|
||||
#ifndef CAPSTONE_DIET // only care about memonic in standard (non-diet) mode
|
||||
if (xchg_mem(opcode) && MI->xAcquireRelease) {
|
||||
SStream_concat(O, "xrelease|");
|
||||
} else if (valid_rep(MI->csh, opcode)) {
|
||||
SStream_concat(O, "rep|");
|
||||
add_cx(MI);
|
||||
} else if (valid_repe(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repe|");
|
||||
add_cx(MI);
|
||||
} else if (valid_ret_repz(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repz|");
|
||||
} else {
|
||||
// invalid prefix
|
||||
MI->x86_prefix[0] = 0;
|
||||
|
||||
// handle special cases
|
||||
#ifndef CAPSTONE_X86_REDUCE
|
||||
#if 0
|
||||
// FIXME: remove this special case?
|
||||
if (opcode == X86_MULPDrr) {
|
||||
MCInst_setOpcode(MI, X86_MULSSrr);
|
||||
SStream_concat0(O, "mulss\t");
|
||||
res = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#else // diet mode -> only patch opcode in special cases
|
||||
if (!valid_rep(MI->csh, opcode) &&
|
||||
!valid_repe(MI->csh, opcode)) {
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#ifndef CAPSTONE_X86_REDUCE
|
||||
#if 0
|
||||
// handle special cases
|
||||
// FIXME: remove this special case?
|
||||
if (opcode == X86_MULPDrr) {
|
||||
MCInst_setOpcode(MI, X86_MULSSrr);
|
||||
break;
|
||||
case 0xF3:
|
||||
opcode = MCInst_getOpcode(MI);
|
||||
#ifndef CAPSTONE_DIET
|
||||
if (valid_rep(MI->csh, opcode)) {
|
||||
SStream_concat(O, "rep|");
|
||||
add_cx(MI);
|
||||
} else if (valid_repe(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repe|");
|
||||
add_cx(MI);
|
||||
} else if (valid_ret_repz(MI->csh, opcode)) {
|
||||
SStream_concat(O, "repz|");
|
||||
} else {
|
||||
// invalid prefix
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#else
|
||||
if (!valid_rep(MI->csh, opcode) &&
|
||||
!valid_repe(MI->csh, opcode)) {
|
||||
MI->x86_prefix[0] = 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// LOCK and F2/F3 may both be present (for XACQUIRE/XRELEASE).
|
||||
// There are also XRELEASEs that can be LOCKless.
|
||||
if (MI->x86Lock) {
|
||||
SStream_concat(O, "lock|");
|
||||
}
|
||||
|
||||
switch (MI->x86_prefix[1]) {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
test_cases:
|
||||
# From the Intel SDM Section 6.1: XACQUIRE/XRELEASE.
|
||||
-
|
||||
input:
|
||||
name: "Rightmost prefix byte should determine HLE semantic"
|
||||
bytes: [ 0xF3, 0xF2, 0xC6, 0x00, 0x00 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "mov byte ptr [rax], 0"
|
||||
-
|
||||
input:
|
||||
name: "Rightmost prefix byte should determine HLE semantic"
|
||||
bytes: [ 0xF2, 0xF3, 0xC6, 0x00, 0x00 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease mov byte ptr [rax], 0"
|
||||
-
|
||||
input:
|
||||
name: "Rightmost prefix byte should determine HLE semantic"
|
||||
bytes: [ 0xF2, 0xF3, 0xF0, 0x00, 0x00 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease lock add byte ptr [rax], al"
|
||||
|
||||
# Prefixes may be ordered freely.
|
||||
-
|
||||
input:
|
||||
name: "Rightmost prefix byte should determine HLE semantic"
|
||||
bytes: [ 0xF0, 0xF2, 0xF3, 0x00, 0x00 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease lock add byte ptr [rax], al"
|
||||
-
|
||||
input:
|
||||
name: "Rightmost prefix byte should determine HLE semantic"
|
||||
bytes: [ 0xF2, 0xF0, 0xF3, 0x00, 0x00 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease lock add byte ptr [rax], al"
|
||||
# Special cases: XCHG
|
||||
-
|
||||
input:
|
||||
name: "special case: xacquire without lock on XCHG"
|
||||
bytes: [ 0xF3, 0xF2, 0xF3, 0xF3, 0xF2, 0x87, 0x18 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xacquire xchg dword ptr [rax], ebx"
|
||||
-
|
||||
input:
|
||||
name: "special case: xrelease without lock on XCHG"
|
||||
bytes: [ 0xF3, 0x87, 0x18 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease xchg dword ptr [rax], ebx"
|
||||
# Separate tests with REX prefix because it is parsed separately in the decoder
|
||||
-
|
||||
input:
|
||||
name: "xrelease with REX prefix"
|
||||
bytes: [ 0xF3, 0xF0, 0x48, 0x87, 0x18 ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "xrelease lock xchg qword ptr [rax], rbx"
|
||||
# Ensure REPs for string instructions are decoded correctly
|
||||
-
|
||||
input:
|
||||
name: "x86-64: multiple REPs on SCASB should decode to last rep"
|
||||
bytes: [ 0xF2, 0xF3, 0xAE ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "repe scasb al, byte ptr [rdi]"
|
||||
-
|
||||
input:
|
||||
name: "x86-64: LOCK+REP on string operation is invalid"
|
||||
bytes: [ 0xF0, 0xF2, 0xF3, 0xAE ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_64 ]
|
||||
expected:
|
||||
insns: []
|
||||
-
|
||||
input:
|
||||
name: "x86-32: multiple REPs on SCASB should decode to last rep"
|
||||
bytes: [ 0xF2, 0xF3, 0xAE ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_32 ]
|
||||
expected:
|
||||
insns:
|
||||
-
|
||||
asm_text: "repe scasb al, byte ptr es:[edi]"
|
||||
-
|
||||
input:
|
||||
name: "x86-32: LOCK+REP on string operation is invalid"
|
||||
bytes: [ 0xF0, 0xF2, 0xF3, 0xAE ]
|
||||
arch: "CS_ARCH_X86"
|
||||
options: [ CS_OPT_DETAIL, CS_MODE_32 ]
|
||||
expected:
|
||||
insns: []
|
||||
Reference in new issue
Block a user