Repository navigation
Description
Found by comparing the semantics described in the Arm Architecture Reference Manual 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
Version: miasm 0.1.5 (tag v0.1.5, ef52b54d). Also reproduces at master 3671fc2 (2026-09-21).
Preconditions: none, default Machine("aarch64l") lifter. The code under analysis contains CASP/CASPA/CASPL/CASPAL (ARMv8.1 LSE), for example clang -march=armv8.1-a output for a 128-bit __atomic_compare_exchange_n.
Input
repro.py runs casp x0, x1, x2, x3, [x4] (0x48207c82), caspal (0x4860fc82) and casp w0, w1, w2, w3, [x4] (0x08207c82). Memory at x4 = {0x1111..., 0x2222...} and {x2, x3} = {0xaaaa..., 0xbbbb...}. Each instruction runs once with {x0, x1} equal to memory and once not equal. Every result is checked against the Arm pseudocode values (spec lines). qemu-aarch64 10.2.1 -cpu max gives the same values.
Command
PYTHON=python3 ./run.sh # engines: symb python (add gcc for the gcc jitter)
Output
casp x equal spec : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb
casp x equal symb : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222 WRONG
casp x equal python : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222 WRONG
casp x not-equal spec : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222
casp x not-equal symb : s0=0xaaaaaaaaaaaaaaaa s1=0xbbbbbbbbbbbbbbbb mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb WRONG
casp x not-equal python : s0=0xaaaaaaaaaaaaaaaa s1=0xbbbbbbbbbbbbbbbb mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb WRONG
caspal x equal spec : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb
caspal x equal symb : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222 WRONG
caspal x equal python : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222 WRONG
caspal x not-equal spec : s0=0x1111111111111111 s1=0x2222222222222222 mem0=0x1111111111111111 mem1=0x2222222222222222
caspal x not-equal symb : s0=0xaaaaaaaaaaaaaaaa s1=0xbbbbbbbbbbbbbbbb mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb WRONG
caspal x not-equal python : s0=0xaaaaaaaaaaaaaaaa s1=0xbbbbbbbbbbbbbbbb mem0=0xaaaaaaaaaaaaaaaa mem1=0xbbbbbbbbbbbbbbbb WRONG
casp w equal spec : s0=0x11111111 s1=0x22222222 mem0=0xaaaaaaaa mem1=0xbbbbbbbb
casp w equal symb : s0=0x11111111 s1=0x22222222 mem0=0x11111111 mem1=0x22222222 WRONG
casp w equal python : s0=0x11111111 s1=0x22222222 mem0=0x11111111 mem1=0x22222222 WRONG
casp w not-equal spec : s0=0x11111111 s1=0x22222222 mem0=0x11111111 mem1=0x22222222
casp w not-equal symb : s0=0xaaaaaaaa s1=0xbbbbbbbb mem0=0xaaaaaaaa mem1=0xbbbbbbbb WRONG
casp w not-equal python : s0=0xaaaaaaaa s1=0xbbbbbbbb mem0=0xaaaaaaaa mem1=0xbbbbbbbb WRONG
RESULT: CASP differs from the Arm ARM (exit 1)
./run.sh symb python gcc shows the gcc jitter giving the same values. In code that uses CASP for a 128-bit compare-and-swap, the emulated CAS therefore reports success with the object unchanged. It reports failure after the object has been overwritten with the desired value.
Expected
Arm A64 ISA 2026-03, CASP: "If the comparison is equal, the values in the second pair of registers are written to memory." In the shared pseudocode CASCompare, regresult starts as oldvalue. The if oldvalue == comparevalue branch sets memresult = newvalue, and the else branch sets memresult = oldvalue. A failed compare may only write back the value it read. Either way the first register pair receives the old memory value.
Location: casp in miasm/arch/aarch64/sem.py:2093 (v0.1.5). ExprCond(FLAG_EQ_CMP(data, comp_value), loc_do, loc_store) sends the equal case past the store. loc_do also reads data back after the store. A fix must swap the targets and read the old value before the store.
Reproducer files
repro.py
"""AArch64 CASP/CASPAL in miasm vs the Arm ARM (MemAtomic/CASCompare), little-endian.
Each case: memory pair at P = {m0, m1}, compare pair {Xs, Xs+1}, new pair {Xt, Xt+1} = {A, B}.
Arm ARM: equal -> memory = {A, B}; not equal -> memory unchanged. Either way {Xs, Xs+1} = old memory."""
import sys, struct, logging
from miasm.analysis.machine import Machine
from miasm.core.locationdb import LocationDB
from miasm.jitter.csts import PAGE_READ, PAGE_WRITE, PAGE_EXEC
CODE, DATA, P = 0x400000, 0x30000000, 0x30000800
M0, M1, A, B = 0x1111111111111111, 0x2222222222222222, 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb
CASES = [ # name, insn word, element size, s0, s1
("casp x equal", 0x48207c82, 64, M0, M1), # casp x0, x1, x2, x3, [x4]
("casp x not-equal", 0x48207c82, 64, 5, M1),
("caspal x equal", 0x4860fc82, 64, M0, M1), # caspal x0, x1, x2, x3, [x4]
("caspal x not-equal", 0x4860fc82, 64, M0, 6),
("casp w equal", 0x08207c82, 32, M0 & 0xffffffff, M1 & 0xffffffff), # casp w0, w1, w2, w3, [x4]
("casp w not-equal", 0x08207c82, 32, M0 & 0xffffffff, 5),
]
RET = 0xd65f03c0 # ret
def run_symb(word, size, s0, s1, m0, m1, a, b):
from miasm.ir.symbexec import SymbolicExecutionEngine
from miasm.expression.expression import ExprInt, ExprMem
logging.getLogger("asmblock").setLevel(logging.ERROR) # only the CASP word is mapped
mach, ldb = Machine("aarch64l"), LocationDB()
block = mach.dis_engine(struct.pack("<I", word), loc_db=ldb).dis_block(0)
lifter = mach.lifter_model_call(ldb); ircfg = lifter.new_ircfg(); lifter.add_asmblock_to_ircfg(block, ircfg)
r, pair = mach.mn.regs, ExprMem(ExprInt(P, 64), 2 * size)
sb = SymbolicExecutionEngine(lifter, {r.X0: ExprInt(s0, 64), r.X1: ExprInt(s1, 64), r.X2: ExprInt(a, 64),
r.X3: ExprInt(b, 64), r.X4: ExprInt(P, 64),
pair: ExprInt(m0 | (m1 << size), 2 * size)})
pc = sb.run_at(ircfg, 0)
while int(pc) != 4:
pc = sb.run_at(ircfg, pc)
mem = int(sb.eval_expr(pair))
mask = (1 << size) - 1
return (int(sb.eval_expr(r.X0)) & mask, int(sb.eval_expr(r.X1)) & mask, mem & mask, mem >> size)
def run(mode, word, size, s0, s1):
mask = (1 << size) - 1
m0, m1, a, b = M0 & mask, M1 & mask, A & mask, B & mask
if mode == "symb":
return run_symb(word, size, s0, s1, m0, m1, a, b)
j = Machine("aarch64l").jitter(LocationDB(), mode)
j.vm.add_memory_page(CODE, PAGE_READ | PAGE_WRITE | PAGE_EXEC, struct.pack("<II", word, RET) + b"\0" * 0xff8)
j.vm.add_memory_page(DATA, PAGE_READ | PAGE_WRITE, b"\0" * 0x1000)
fmt = "<QQ" if size == 64 else "<II"
j.vm.set_mem(P, struct.pack(fmt, m0, m1))
j.cpu.X0, j.cpu.X1, j.cpu.X2, j.cpu.X3, j.cpu.X4 = s0, s1, a, b, P
j.cpu.SP = DATA + 0xf00; j.cpu.LR = CODE + 0x800
j.add_breakpoint(CODE + 0x800, lambda jt: False)
j.run(CODE)
mem = struct.unpack(fmt, j.vm.get_mem(P, 2 * size // 8))
return (j.cpu.X0 & mask, j.cpu.X1 & mask) + mem
modes = sys.argv[1:] or ["symb", "python"]
bad = 0
for name, word, size, s0, s1 in CASES:
mask = (1 << size) - 1
m0, m1 = M0 & mask, M1 & mask
equal = (s0, s1) == (m0, m1)
spec = (m0, m1) + (((A & mask, B & mask)) if equal else (m0, m1))
line = "%-19s spec : s0=%#x s1=%#x mem0=%#x mem1=%#x" % ((name,) + spec)
print(line)
for mode in modes:
got = run(mode, word, size, s0, s1)
ok = got == spec
bad |= not ok
print("%-19s %-7s: s0=%#x s1=%#x mem0=%#x mem1=%#x %s" % ((name, mode) + got + ("OK" if ok else "WRONG",)))
print("RESULT: %s" % ("CASP differs from the Arm ARM (exit 1)" if bad else "conforms (exit 0)"))
sys.exit(1 if bad else 0)run.sh
#!/usr/bin/env bash
# exit 1 = miasm's AArch64 CASP differs from the Arm ARM, 0 = conforms, 2 = miasm not found
# Usage: PYTHON=python3 ./run.sh [symb] [python] [gcc] (engines; default symb python)
PYTHON=${PYTHON:-python3}
D="$(cd "$(dirname "$0")" && pwd)"
"$PYTHON" -c 'import miasm' 2>/dev/null || { echo "tool not found: miasm (set PYTHON to an interpreter with miasm installed)"; exit 2; }
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
TMPDIR="$T" "$PYTHON" -W ignore "$D/repro.py" "$@" # fresh TMPDIR: the gcc jitter caches compiled blocks thereIf 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
Redhpm commentedon Oct 28, 2026
Superseded by the semantic tracking issue.
See #1584
Closed as duplicate as a consequence, because we need to fix semantic issues more systematically.