Skip to content

x86 AAM/AAD: SF and ZF are computed from all of EAX, not from AL #1567

Closed as duplicate

Description

@JeroenRobben

Found by comparing the semantics described in the Intel SDM with how Miasm implements them. An AI agent found this bug, reproduced it with Miasm 3671fc2 and drafted this report. A second agent tried to refute it and searched upstream for duplicates. The methodology is described here.

What went wrong: Wrong result (flags).

Version: miasm 0.1.5 (tag v0.1.5), Python 3.12. Also reproduces at upstream master 3671fc2 (2026-09-21).

Preconditions: 16/32-bit x86 (Machine("x86_32")); AAM/AAD are #UD in 64-bit mode.

aam and aad in miasm/arch/x86/sem.py call update_flag_arith(newEAX) on the full 32-bit register. ZF becomes EAX == 0 and SF becomes EAX bit 31. The SDM says: "The SF, ZF, and PF flags are set according to the resulting binary value in the AL register" (Vol. 2A, AAM and AAD, Flags Affected, order no. 325462-084US). EAX itself is correct; PF is correct.

Input

D4 0A (aam) with EAX=0x00000a0a: AX=0x0100, AL=0, so ZF=1, PF=1. D5 0A (aad) with EAX=0x00000c0c: AL=0x84, so SF=1, PF=1. D4 0A with EAX=0x80000101: AL=1, so SF=ZF=PF=0.

Command

./run.sh (needs gcc -m32; PYTHON selects the interpreter with miasm installed). It runs AAM/AAD with imm 0x0a and 0x07 on the host CPU and in miasm's SymbolicExecutionEngine from the same concrete state, 3072 cases, and compares EAX, SF, ZF, PF.

Output

DIFF aam 0a eax_in=00000a0a native eax=00000100 SF/ZF/PF=044 miasm eax=00000100 SF/ZF/PF=004
DIFF aad 0a eax_in=00000c0c native eax=00000084 SF/ZF/PF=084 miasm eax=00000084 SF/ZF/PF=004
DIFF aam 0a eax_in=80000101 native eax=80000001 SF/ZF/PF=000 miasm eax=80000001 SF/ZF/PF=080
total diffs 1413 {('aam', 'flags'): 636, ('aad', 'flags'): 777}
RESULT: BUG PRESENT (exit 1)

Expected

The flags in the native column, i.e. SF/ZF/PF derived from AL. A fix is to pass newEAX[:8] to the flag update in both functions.

Impact

A jz/js after aam/aad is decided on the wrong flag in miasm's emulation and symbolic path conditions. Nothing beyond the wrong flag value is shown.

Reproducer files

aam32.c

/* 32-bit native oracle for AAM imm8 / AAD imm8 (imm 10 and 7): op imm eax_in -> eax_out eflags_out&0x8d5 */
#include <stdio.h>
#define RUN(NAME, INSN) \
static unsigned NAME(unsigned eax, unsigned fl, unsigned *fout) { unsigned out, f; \
  __asm__ volatile("pushl %3\n\tpopfl\n\t" INSN "\n\tpushfl\n\tpopl %1" : "=a"(out), "=r"(f) : "a"(eax), "r"(fl) : "cc"); \
  *fout = f; return out; }
RUN(aam10, ".byte 0xd4, 0x0a") RUN(aam7, ".byte 0xd4, 0x07") RUN(aad10, ".byte 0xd5, 0x0a") RUN(aad7, ".byte 0xd5, 0x07")
int main(void) {
  unsigned hi[3] = {0x00000000, 0x80000000, 0x12340000};
  for (int h = 0; h < 3; h++) for (unsigned ax = 0; ax < 0x10000; ax += 0x101 /* sample */) {
    unsigned in = hi[h] | ax, fo, r;
    r = aam10(in, 0x202, &fo); printf("aam 0a %08x %08x %03x\n", in, r, fo & 0x8d5);
    r = aam7(in, 0x202, &fo);  printf("aam 07 %08x %08x %03x\n", in, r, fo & 0x8d5);
    r = aad10(in, 0x202, &fo); printf("aad 0a %08x %08x %03x\n", in, r, fo & 0x8d5);
    r = aad7(in, 0x202, &fo);  printf("aad 07 %08x %08x %03x\n", in, r, fo & 0x8d5);
  }
  return 0; }

miasm_aam.py

"""Compare Miasm x86_32 AAM/AAD (SymbolicExecutionEngine, concrete state) with native results.
Defined per SDM: EAX, SF, ZF, PF (OF, AF, CF undefined)."""
import sys
from collections import Counter
from miasm.analysis.machine import Machine
from miasm.core.locationdb import LocationDB
from miasm.ir.symbexec import SymbolicExecutionEngine
from miasm.expression.expression import ExprInt
import miasm.arch.x86.regs as X
m = Machine("x86_32"); DEF = 0x0c4; bad = 0; per = Counter(); shown = Counter()
for line in open(sys.argv[1]):
    op, imm, ein, r, fo = line.split(); ein = int(ein, 16); r = int(r, 16); fo = int(fo, 16)
    code = bytes([0xd4 if op == "aam" else 0xd5, int(imm, 16)])
    loc_db = LocationDB(); lifter = m.lifter(loc_db)
    ins = m.mn.dis(code, 32); ins.offset = 0x1000
    ircfg = lifter.new_ircfg(); lifter.add_instr_to_ircfg(ins, ircfg)
    init = {X.EAX: ExprInt(ein, 32)}
    for f in (X.cf, X.af, X.zf, X.nf, X.pf, X.of): init[f] = ExprInt(0, 1)
    sb = SymbolicExecutionEngine(lifter, init); sb.run_at(ircfg, 0x1000)
    e = int(sb.eval_expr(X.EAX)); fl = 0
    for bit, f in ((0, X.cf), (2, X.pf), (4, X.af), (6, X.zf), (7, X.nf), (11, X.of)):
        fl |= int(sb.eval_expr(f)) << bit
    if e != r or (fl ^ fo) & DEF:
        bad += 1; k = (op, imm, ein >> 16, e != r); per[(op, "eax" if e != r else "flags")] += 1
        if shown[k] < 2:
            shown[k] += 1
            print("DIFF %s %s eax_in=%08x native eax=%08x SF/ZF/PF=%03x miasm eax=%08x SF/ZF/PF=%03x" % (op, imm, ein, r, fo & DEF, e, fl & DEF))
print("total diffs", bad, dict(per))
sys.exit(1 if bad else 0)

run.sh

#!/usr/bin/env bash
# AAM/AAD: SF, ZF, PF are set from the result in AL (SDM Vol.2A AAD p.3-21, AAM p.3-23 "Flags Affected").
# Oracle: host CPU in a 32-bit process (gcc -m32). exit 1 = bug present, 0 = conforms, 2 = harness error
D="$(cd "$(dirname "$0")" && pwd)"
PY="${PYTHON:-python3}"
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
"$PY" -I -c "import sys, importlib.metadata as m; print('miasm', m.version('miasm'), sys.prefix)" || exit 2
gcc -m32 -O1 -o "$T/aam32" "$D/aam32.c" || exit 2
"$T/aam32" > "$T/native.txt" || exit 2
echo "native 32-bit oracle: $(wc -l < "$T/native.txt") cases (AAM/AAD imm 0x0a,0x07; AX sampled every 0x101; EAX[31:16] in {0000,8000,1234})"
echo "native lines for EAX=0x00000a0a and 0x12340000 (op imm eax_in eax_out eflags&0x8d5):"
grep ' 00000a0a \| 12340000 ' "$T/native.txt"
"$PY" -I -W ignore "$D/miasm_aam.py" "$T/native.txt"; rc=$?
case $rc in 0) echo "RESULT: conforms (exit 0)"; exit 0;; 1) echo "RESULT: BUG PRESENT (exit 1)"; exit 1;; *) exit 2;; esac

If this is intended behaviour or a duplicate, feel free to close it, and I'll use that as feedback to improve future reports. For questions, or to have this re-checked on a newer version of Miasm, mention @JeroenRobben or email jeroen@robben.io.

Activity

  1. Redhpm commented on Oct 28, 2026

    @Redhpm
    Contributor

    Superseded by the semantic tracking issue.
    See #1584

    Closed as duplicate as a consequence, because we need to fix semantic issues more systematically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions