Fix the x86 mul, div and movbe esil and model the bit counters ##esil

This commit is contained in:
phix33 2026-08-21 21:58:39 +10:00 committed by pancake
parent 323fe03d2b
commit 3e2a8f4cfa
3 changed files with 477 additions and 61 deletions

View file

@ -355,6 +355,65 @@ static void zext_prefix(struct Getarg *gop, const char *reg, char *out, size_t s
}
}
// the implicit operands of the one-operand mul, imul, div and idiv
static void muldiv_regs(int width, const char **quot, const char **rema) {
*quot = (width == 1)? "al": (width == 2)? "ax": (width == 4)? "eax": "rax";
*rema = (width == 1)? "ah": (width == 2)? "dx": (width == 4)? "edx": "rdx";
}
// the byte form divides ax alone, the wider ones the rema:quot pair
static char *muldiv_dividend(int width, const char *quot, const char *rema, bool sign) {
char *num = (width == 1)
? strdup ("ax")
: r_str_newf ("%d,%s,<<,%s,+", width * 8, rema, quot);
if (sign && width < 4) {
// the dividend is signed at twice the operand width
char *snum = r_str_newf ("%d,%s,~", width * 16, num);
free (num);
return snum;
}
return num;
}
// counts the set bits of a register in place, by the usual halving sums
static void popcnt_esil(RStrBuf *sb, const char *reg, int bits) {
const int sh = 64 - bits;
const ut64 m1 = 0x5555555555555555ULL >> sh;
const ut64 m2 = 0x3333333333333333ULL >> sh;
const ut64 m4 = 0x0f0f0f0f0f0f0f0fULL >> sh;
const ut64 m8 = 0x0101010101010101ULL >> sh;
r_strbuf_appendf (sb, ",1,%s,>>,0x%"PFMT64x",&,%s,-,%s,=", reg, m1, reg, reg);
r_strbuf_appendf (sb, ",0x%"PFMT64x",%s,&,2,%s,>>,0x%"PFMT64x",&,+,%s,=",
m2, reg, reg, m2, reg);
r_strbuf_appendf (sb, ",4,%s,>>,%s,+,0x%"PFMT64x",&,%s,=", reg, reg, m4, reg);
// the byte sums land in the top byte, which can carry out of bits
r_strbuf_appendf (sb, ",%d,0x%"PFMT64x",%s,*,>>,0x7f,&,%s,=",
bits - 8, m8, reg, reg);
}
// esil for the byte-reversed value of an expression of the given byte width
static char *byteswap_expr(const char *v, int bytes) {
RStrBuf *sb = r_strbuf_new ("");
int i;
for (i = 0; i < bytes; i++) {
const int shr = 8 * i;
const int shl = 8 * (bytes - 1 - i);
if (i) {
r_strbuf_append (sb, ",");
}
if (shl) {
r_strbuf_appendf (sb, "%d,", shl);
}
if (shr) {
r_strbuf_appendf (sb, "%d,%s,>>,0xff,&", shr, v);
} else {
r_strbuf_appendf (sb, "0xff,%s,&", v);
}
r_strbuf_appendf (sb, "%s%s", shl? ",<<": "", i? ",|": "");
}
return r_strbuf_drain (sb);
}
static char *getarg(struct Getarg* gop, int n, int set, char *setop, ut32 *bitsize) {
const char *setarg = r_str_get (setop);
cs_insn *insn = gop->insn;
@ -1093,7 +1152,6 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
case X86_INS_MOVHPS:
case X86_INS_MOVLPD:
case X86_INS_MOVLPS:
case X86_INS_MOVBE:
case X86_INS_MOVSX:
case X86_INS_MOVSXD:
case X86_INS_MOVQ:
@ -2039,22 +2097,76 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
{
dst = getarg (&gop, 0, 0, NULL, NULL);
zext_opnd (&gop, 0);
if (INSOP(0).size == 4) {
esilprintf (op, "0xff000000,24,%s,NUM,<<,&,24,%s,NUM,>>,|,"
"8,0x00ff0000,%s,NUM,&,>>,|,"
"8,0x0000ff00,%s,NUM,&,<<,|,"
"%s,=", dst, dst, dst, dst, dst);
} else {
esilprintf (op, "0xff00000000000000,56,%s,NUM,<<,&,"
"56,%s,NUM,>>,|,40,0xff000000000000,%s,NUM,&,>>,|,"
"40,0xff00,%s,NUM,&,<<,|,24,0xff0000000000,%s,NUM,&,>>,|,"
"24,0xff0000,%s,NUM,&,<<,|,8,0xff00000000,%s,NUM,&,>>,|,"
"8,0xff000000,%s,NUM,&,<<,|,"
"%s,=", dst, dst, dst, dst, dst, dst, dst, dst, dst);
}
char *sw = byteswap_expr (dst, (INSOP (0).size == 4)? 4: 8);
esilprintf (op, "%s,%s,=", sw, dst);
free (sw);
R_FREE (dst);
}
break;
case X86_INS_POPCNT:
case X86_INS_LZCNT:
case X86_INS_TZCNT:
{
const int dst_idx = norm_op (0, gop.syntax, INSOPS);
const char *reg = (INSOP (dst_idx).type == X86_OP_REG)
? cs_reg_name (handle, INSOP (dst_idx).reg): NULL;
src = getarg (&gop, 1, 0, NULL, NULL);
if (reg && src) {
const int id = insn->id;
const int bits = INSOP (dst_idx).size * 8;
RStrBuf *sb = r_strbuf_new ("");
zext_reg (&gop, reg);
r_strbuf_appendf (sb, "%s,%s,=", src, reg);
if (id != X86_INS_POPCNT) {
r_strbuf_appendf (sb, ",%s,!,cf,:=", reg);
}
if (id == X86_INS_TZCNT) {
// tzcnt = popcount(~x & (x - 1))
r_strbuf_appendf (sb, ",0x%"PFMT64x",%s,^,1,%s,-,&,%s,=",
UT64_MAX >> (64 - bits), reg, reg, reg);
} else if (id == X86_INS_LZCNT) {
int i;
// smear: one bit per non-leading zero
for (i = 1; i < bits; i *= 2) {
r_strbuf_appendf (sb, ",%d,%s,>>,%s,|,%s,=", i, reg, reg, reg);
}
}
popcnt_esil (sb, reg, bits);
if (id == X86_INS_LZCNT) {
r_strbuf_appendf (sb, ",%s,%d,-,%s,=", reg, bits, reg);
} else if (id == X86_INS_POPCNT) {
r_strbuf_append (sb, ",0,cf,:=,0,of,:=,0,sf,:=,0,af,:=,0,pf,:=");
}
r_strbuf_appendf (sb, ",%s,!,zf,:=", reg);
esilprintf (op, "%s", r_strbuf_get (sb));
r_strbuf_free (sb);
}
free (src);
}
break;
case X86_INS_MOVBE:
{
const int dst_idx = norm_op (0, gop.syntax, INSOPS);
const int src_idx = norm_op (1, gop.syntax, INSOPS);
src = getarg (&gop, 1, 0, NULL, NULL);
dst = getarg (&gop, 0, 1, NULL, NULL);
if (src && dst) {
// swapping in place reads memory once
const bool to_reg = INSOP (dst_idx).type == X86_OP_REG;
const char *val = to_reg
? cs_reg_name (handle, INSOP (dst_idx).reg): src;
char *sw = byteswap_expr (val, INSOP (src_idx).size);
if (to_reg) {
esilprintf (op, "%s,%s,%s,%s", src, dst, sw, dst);
} else {
esilprintf (op, "%s,%s", sw, dst);
}
free (sw);
}
free (src);
free (dst);
}
break;
case X86_INS_OR:
// The OF and CF flags are cleared; the SF, ZF, and PF flags are
// set according to the result. The state of the AF flag is
@ -2249,26 +2361,17 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
// IDIV does not change flags
op->sign = true;
if (!arg2 && !arg1) {
// TODO: IDIV rbx not implemented. this is just a workaround
//
// https://www.tptp.cc/mirrors/siyobik.info/instruction/IDIV.html
// Divides (signed) the value in the AX, DX:AX, or EDX:EAX registers (dividend) by the source operand (divisor) and stores the result in the AX (AH:AL), DX:AX, or EDX:EAX registers. The source operand can be a general-purpose register or a memory location. The action of this instruction depends on the operand size (dividend/divisor), as shown in the following table:
// IDIV RBX == RDX:RAX /= RBX
//
// TODO: 64-bit idiv needs a 128-bit dividend
if (arg0) {
int width = INSOP(0).size;
const char *r_quot = (width == 1)?"al": (width == 2)?"ax": (width == 4)?"eax":"rax";
const char *r_rema = (width == 1)?"ah": (width == 2)?"dx": (width == 4)?"edx":"rdx";
const char *r_nume = (width == 1)?"ax": r_quot;
const char *r_quot, *r_rema;
muldiv_regs (width, &r_quot, &r_rema);
char *num = muldiv_dividend (width, r_quot, r_rema, true);
zext_reg (&gop, r_quot);
zext_reg (&gop, r_rema);
esilprintf (op, "%d,%s,~,%d,%s,<<,%s,+,~%%,%d,%s,~,%d,%s,<<,%s,+,~/,%s,=,%s,=",
width*8, arg0, width*8, r_rema, r_nume, width*8, arg0, width*8, r_rema, r_nume, r_quot, r_rema);
}
else {
/* should never happen */
esilprintf (op, "%d,%s,~,%s,~%%,%d,%s,~,%s,~/,%s,=,%s,=",
width*8, arg0, num, width*8, arg0, num, r_quot, r_rema);
free (num);
}
} else {
// does this instruction even exist?
@ -2282,17 +2385,18 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
break;
case X86_INS_DIV:
{
// DIV does not change flags and is unsigned
// TODO: 64-bit div needs a 128-bit dividend, so rdx is ignored there
int width = INSOP(0).size;
dst = getarg (&gop, 0, 0, NULL, NULL);
const char *r_quot = (width == 1)?"al": (width == 2)?"ax": (width == 4)?"eax":"rax";
const char *r_rema = (width == 1)?"ah": (width == 2)?"dx": (width == 4)?"edx":"rdx";
const char *r_nume = (width == 1)?"ax": r_quot;
// DIV does not change flags and is unsigned
const char *r_quot, *r_rema;
muldiv_regs (width, &r_quot, &r_rema);
char *num = muldiv_dividend (width, r_quot, r_rema, false);
zext_reg (&gop, r_quot);
zext_reg (&gop, r_rema);
esilprintf (op, "%s,%d,%s,<<,%s,+,%%,%s,%d,%s,<<,%s,+,/,%s,=,%s,=",
dst, width*8, r_rema, r_nume, dst, width*8, r_rema, r_nume, r_quot, r_rema);
esilprintf (op, "%s,%s,%%,%s,%s,/,%s,=,%s,=",
dst, num, dst, num, r_quot, r_rema);
free (num);
free (dst);
}
break;
@ -2312,21 +2416,19 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
zext_opnd (&gop, 0);
esilprintf (op, "%d,%s,~,%d,%s,~,*,DUP,%s,=,%d,%s,~,-,!,!,DUP,cf,:=,of,:=",
width*8, multiplier, width*8, arg1, arg0, width*8, arg0);
} else {
if (arg0) {
const char *r_quot = (width == 1)?"al": (width==2)?"ax": (width==4)?"eax":"rax";
const char *r_rema = (width == 1)?"ah": (width==2)?"dx": (width==4)?"edx":"rdx";
const char *r_nume = (width == 1)?"ax": r_quot;
zext_reg (&gop, r_nume);
zext_reg (&gop, r_rema);
if (width == 8) { // TODO still needs to be fixed to handle correct signed 128 bit value
esilprintf (op, "%s,%s,L*,%s,=,DUP,%s,=,!,!,DUP,cf,:=,of,:=", // flags will be sometimes wrong
arg0, r_nume, r_nume, r_rema);
} else {
esilprintf (op, "%d,%s,~,%d,%s,~,*,DUP,DUP,%s,=,%d,SWAP,>>,%s,=,%d,%s,~,-,!,!,DUP,cf,:=,of,:=",
width*8, arg0, width*8, r_nume, r_nume, width*8, r_rema, width*8, r_nume);
}
} else if (arg0) {
const char *r_quot, *r_rema;
muldiv_regs (width, &r_quot, &r_rema);
zext_reg (&gop, r_quot);
zext_reg (&gop, r_rema);
if (width == 8) {
// L* is unsigned, so fix the high half; the source is
// staged in rema first, to read a memory one once
esilprintf (op, "%s,%s,=,63,%s,>>,%s,*,63,%s,>>,%s,*,+,%s,%s,L*,%s,=,-,DUP,%s,=,63,%s,>>,0,-,^,!,!,DUP,cf,:=,of,:=",
arg0, r_rema, r_quot, r_rema, r_rema, r_quot, r_rema, r_quot, r_quot, r_rema, r_quot);
} else {
esilprintf (op, "%d,%s,~,%d,%s,~,*,DUP,DUP,%s,=,%d,SWAP,>>,%s,=,%d,%s,~,-,!,!,DUP,cf,:=,of,:=",
width*8, arg0, width*8, r_quot, r_quot, width*8, r_rema, width*8, r_quot);
}
}
free (arg0);
@ -2338,19 +2440,18 @@ static void anop_esil(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf,
{
src = getarg (&gop, 0, 0, NULL, NULL);
if (src) {
// mul multiplies al, not ax, at a byte width
int width = INSOP(0).size;
const char *r_quot = (width == 1)?"al": (width == 2)?"ax": (width == 4)?"eax":"rax";
const char *r_rema = (width == 1)?"ah": (width == 2)?"dx": (width == 4)?"edx":"rdx";
const char *r_nume = (width == 1)?"ax": r_quot;
zext_reg (&gop, r_nume);
const char *r_quot, *r_rema;
muldiv_regs (width, &r_quot, &r_rema);
zext_reg (&gop, r_quot);
zext_reg (&gop, r_rema);
if (width == 8 ) {
if (width == 8) {
esilprintf (op, "%s,%s,L*,%s,=,DUP,%s,=,!,!,DUP,cf,:=,of,:=",
src, r_nume, r_nume, r_rema);
src, r_quot, r_quot, r_rema);
} else {
esilprintf (op, "%s,%s,*,DUP,%s,=,%d,SWAP,>>,DUP,%s,=,!,!,DUP,cf,:=,of,:=",
src, r_nume, r_nume, width*8, r_rema); // this should be ok for width == 1 also
src, r_quot, r_quot, width*8, r_rema);
}
free (src);
}

View file

@ -2616,3 +2616,31 @@ EXPECT=<<EOF
0x00000001
EOF
RUN
NAME=byte mul and idiv use al and ax in 32-bit mode too
FILE=malloc://0x200
ARGS=-a x86 -b 32
CMDS=<<EOF
wx f6e1
aei
ar eax=0x112233ff
ar ecx=0x99aabbff
s 0
aeip
aes
ar ax
wx f6f9
ar eax=0x1122fff9
ar ecx=0x99aabb02
s 0
aeip
aes
ar al
ar ah
EOF
EXPECT=<<EOF
0x0000fe01
0x000000fd
0x000000ff
EOF
RUN

View file

@ -39,7 +39,7 @@ e asm.bits=64
EOF
EXPECT=<<EOF
imul r9
0x00000000 r9,rax,L*,rax,=,DUP,rdx,=,!,!,DUP,cf,:=,of,:=
0x00000000 r9,rdx,=,63,rax,>>,rdx,*,63,rdx,>>,rax,*,+,rdx,rax,L*,rax,=,-,DUP,rdx,=,63,rax,>>,0,-,^,!,!,DUP,cf,:=,of,:=
EOF
RUN
@ -1617,3 +1617,290 @@ EXPECT=<<EOF
0x00000000
EOF
RUN
NAME=byte mul div and idiv use al and ax, not the whole eax
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx f6e1
aei
ar rax=0x112233ff
ar rcx=0x99aabbff
s 0
aeip
aes
ar ax
ar cf
wx f6f1
ar rax=0x112233ff
s 0
aeip
aes
ar al
ar ah
wx f6f9
ar rax=0x1122fff9
ar rcx=0x99aabb02
s 0
aeip
aes
ar al
ar ah
EOF
EXPECT=<<EOF
0x0000fe01
0x00000001
0x00000034
0x00000033
0x000000fd
0x000000ff
EOF
RUN
NAME=idiv sign-extends the dx:ax dividend
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx 66f7f9
aei
ar rax=0x1122fff9
ar rdx=0x9999ffff
ar rcx=0x77770002
s 0
aeip
aes
ar ax
ar dx
EOF
EXPECT=<<EOF
0x0000fffd
0x0000ffff
EOF
RUN
NAME=imul multiplies the whole signed product at 64 bits
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx 48f7e9
aei
ar rax=-1
ar rcx=2
s 0
aeip
aes
ar rdx
ar rax
ar cf
ar rax=0x8000000000000000
ar rcx=2
s 0
aeip
aes
ar rdx
ar rax
ar cf
EOF
EXPECT=<<EOF
0xffffffffffffffff
0xfffffffffffffffe
0x00000000
0xffffffffffffffff
0x00000000
0x00000001
EOF
RUN
NAME=movbe swaps the bytes it loads and stores
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx 1122334455667788 @ 0x40
aei
aeim
ar rsi=0x40
wx 480f38f00e @ 0
s 0
aeip
aes
ar rcx
wx 660f38f00e @ 0
ar rcx=0xaabbccdd
s 0
aeip
aes
ar rcx
wx 480f38f10e @ 0
wx a5a5a5a5a5a5a5a5 @ 0x40
ar rcx=0x1122334455667788
s 0
aeip
aes
p8 8 @ 0x40
EOF
EXPECT=<<EOF
0x1122334455667788
0xaabb1122
1122334455667788
EOF
RUN
NAME=popcnt lzcnt and tzcnt count bits and flag a zero source
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx f30fb8c8
aei
ar rax=0x80000100
s 0
aeip
aes
ar rcx
ar zf
wx f30fbdc8
s 0
aeip
aes
ar rcx
wx f30fbcc8
s 0
aeip
aes
ar rcx
ar cf
ar rax=0
s 0
aeip
aes
ar rcx
ar cf
wx f30fb8c8
ar rax=0
s 0
aeip
aes
ar rcx
ar zf
EOF
EXPECT=<<EOF
0x00000002
0x00000000
0x00000000
0x00000008
0x00000000
0x00000020
0x00000001
0x00000000
0x00000001
EOF
RUN
NAME=att syntax keeps the popcnt and movbe operand order
FILE=malloc://0x200
ARGS=-a x86 -b 64 -e asm.syntax=att
CMDS=<<EOF
wx f30fb8c3
aei
ar rax=0
ar rbx=0x80000100
s 0
aeip
aes
ar rax
wx 1122334455667788 @ 0x40
aeim
ar rsi=0x40
wx 0f38f006 @ 0
s 0
aeip
aes
ar rax
EOF
EXPECT=<<EOF
0x00000002
0x11223344
EOF
RUN
NAME=the 64-bit imul reads a memory source once
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx 48f72e
ao 1~esilcost
wx 0102030405060708 @ 0x40
aei
aeim
ar rsi=0x40
ar rax=-2
s 0
aeip
aes
ar rdx
ar rax
EOF
EXPECT=<<EOF
esilcost: 8
0xffffffffffffffff
0xeff1f3f5f7f9fbfe
EOF
RUN
NAME=popcnt lzcnt and tzcnt at the other operand widths
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx f3480fb8c8
aei
ar rax=0x8000000000000100
ar rcx=0
s 0
aeip
aes
ar rcx
wx f3480fbdc8
s 0
aeip
aes
ar rcx
wx f3480fbcc8
s 0
aeip
aes
ar rcx
wx 66f30fb8c8
ar rax=0x11228100
ar rcx=0x99aabbcc
s 0
aeip
aes
ar rcx
EOF
EXPECT=<<EOF
0x00000002
0x00000000
0x00000008
0x99aa0002
EOF
RUN
NAME=unsigned div composes the dx:ax dividend
FILE=malloc://0x200
ARGS=-a x86 -b 64
CMDS=<<EOF
wx 66f7f1
aei
ar rax=0x11220010
ar rdx=0x99990001
ar rcx=0x77770003
s 0
aeip
aes
ar ax
ar dx
EOF
EXPECT=<<EOF
0x0000555a
0x00000002
EOF
RUN