A C-hosted unit-testing framework for assembly language. Write assembly routines, call them from C test cases through the real ABI, and assert on the results — return values, CPU registers, flags, and memory. Tests are auto-discovered and reported TAP-style.
Every capability documented here is implemented and exercised in CI. The core — capture, the assertion library, differential testing, the runner, and the Unicorn emulator — runs on any supported host. The native-trace tiers layer on top: DynamoRIO in-process tracing needs Linux x86-64 (and a DynamoRIO install), and the hardware-assisted backends (Intel PT, AMD LBR, ARM CoreSight) plus the eBPF code-image detector compile everywhere but self-skip where their hardware, permissions, or (for CoreSight) a live trace decoder are unavailable. See DESIGN.md for the full design and roadmap.
- Auto-discovered
TEST(...)cases with a provided runner: per-testfork()isolation, timeouts, crash/hang containment, filtering, shuffling, sharding, parallelism (-jN), and TAP or JUnit output. Per-suiteSETUP/TEARDOWN,SKIP(reason). - A full assertion library — signed/unsigned comparisons, strings, memory (hexdump diff), floating-point (ULP-aware), and SIMD lanes.
- Register, flag, and ABI-preservation capture through a real call
(
ASM_CALLn,ASSERT_ABI_PRESERVED,ASSERT_FLAG_SET/CLEAR), plus the full System V call model: arbitrary arity, struct return and by-value, FP (ASM_FCALLn), and 128/256/512-bit vectors (ASM_VCALLn). - Differential / property testing against a C reference model over fuzzed
inputs (
ASSERT_MATCHES_REFn), with reproducible, overridable seeds. - Benchmark mode — auto-calibrated cycles per call, text or JSON output.
- Guard-page buffers and crash handling that turn overruns and fatal signals into reported failures instead of killing the run.
- An optional emulator tier (Unicorn): x86-64, AArch64, RISC-V, and ARM32 guests on any host — full register file, precise faults, instruction traces, block coverage, and the Windows x64 ABI on a System V host.
- Optional in-line assembly (Keystone: run routines from strings) and disassembly in diagnostics (Capstone).
- Native runtime tracing: in-process DynamoRIO, hardware trace (Intel PT,
AMD LBR, single-step, an ARM CoreSight scaffold), out-of-process
ptrace, and a statistical AMD IBS-Op lane (sampled hot control-flow edges, out of band and unprivileged, any Zen) — plusasmspy, an ncurses tool that attaches to a live process out of band and shows its syscalls-with-data, a chosen function's live assembly + call-graph, whole-process call graph/tree and instruction stream, or IBS-sampled hot edges (safe on a live JIT). - A native Win64 tier (cross-compile + Wine, or the
ms_abilane) and ten language bindings (Python, .NET, Go, Rust, C++, Zig, Node, Java, Ruby, Lua). - Portable across x86-64, AArch64, and RISC-V (rv64), Linux and macOS, with GAS and NASM assembler backends.
The complete capability list, per-platform, lives in Features & support matrix.
Full documentation lives in docs/ (also built on Read the Docs). Where to start:
- Getting started — Quick start · Installation · Writing tests · Examples
- Guides — ABI capture · Assertions · Floating-point & SIMD · The runner · Benchmarks · Property testing · Emulator · Disassembly · Windows x64 tier · CI integration · Teaching with asm-test
- Language bindings — overview for C++, Rust, Zig, Go, Node, Python, Ruby, Lua, Java, and .NET.
- Tracing tiers — the tracing hub:
emulator traces, in-process
DynamoRIO, the
hardware backends (Intel PT, AMD
LBR, ARM CoreSight, single-step, plus the statistical AMD IBS-Op lane), and
asmspy, the interactive process tracer. - Reference — API reference · Integration · vs. alternatives · Packaging · CI & Docker · Troubleshooting & FAQ
make test # build and run the example suites
make demo-fail # see how a failing assertion is reported
make demo-robust # see a hang and a crash contained & reported
make bench # time the BENCH cases (cycles/call)
make bench-report # cross-system report: real cycles + per-ISA counts + features
make ASM_SYNTAX=nasm test # same suites via the NASM backend (x86-64)
make asm-test # run routines from in-line assembly strings (Keystone)Assembly routine (examples/add.s, System V AMD64 ABI, GAS syntax). ASM_FUNC
handles the ELF/Mach-O symbol differences so it builds on Linux and macOS:
#include "asm.h"
ASM_FUNC add_signed
movq %rdi, %rax
addq %rsi, %rax
ret
ASM_ENDFUNC add_signedC test (examples/test_arith.c):
#include "asmtest.h"
extern long add_signed(long a, long b);
TEST(arith, adds_two_positives) {
ASSERT_EQ(add_signed(2, 3), 5);
}The framework provides main(), discovers every TEST(...), runs them, and
exits nonzero if any fail. Suites are auto-discovered too: drop an
examples/foo.s + examples/test_foo.c pair in and make test builds and runs
it. Each suite binary takes a small CLI (--list, --filter=GLOB, --shuffle,
--timeout=SEC, -jN, --format=junit, …) — see
the runner.
Continue with the Quick start to build a suite of your own, and Examples for the use-case tour (correctness & ABI, differential testing, benchmarking, crash containment, SIMD, cross-ISA).
The static library + pkg-config is the primary path; a single-header
amalgamation (make amalgamate) is the lightweight alternative:
make install # headers + libasmtest.a + asmtest.pc (PREFIX=/usr/local)
cc $(pkg-config --cflags asmtest) -x assembler-with-cpp -c my_routine.s -o my_routine.o
cc $(pkg-config --cflags asmtest) -c my_tests.c -o my_tests.o
cc my_tests.o my_routine.o $(pkg-config --libs asmtest) -o my_testsSee Using asm-test in your project for the
details (staged installs, the asmtest-emu module, version macros, caveats) and
Using asm-test in your CI for the GitHub Action
and GitLab template.
make <lang>-package builds a self-contained package per binding — the superset
native lib plus vendored Unicorn/Keystone/Capstone, so a fresh install runs the
emulator, in-line assembly, and disassembly with no system libraries. The
packages build and install-smoke in the release pipeline but are not published
to public registries yet — today you consume the bindings from a checkout (see
the bindings overview). Because they convey the
GPL-2.0 engines as binaries, the packages are effectively GPL-2.0 as
distributed; asm-test's own source stays MIT (see LICENSE and
Packaging).
x86-64, AArch64, or RISC-V (rv64 / RV64GC / LP64D), Linux or macOS, with make
and a C compiler (cc — gcc or clang), which also assembles the GAS-syntax .s
sources. The core build needs nothing else. The optional tools (nasm, pkg-config, libunicorn,
libkeystone, libcapstone, clang-tidy, valgrind) install cross-platform
with:
make deps # full dev setup, via the system package manager
make deps DEPS_ARGS=--emu # what `make emu-test` needs (unicorn + capstone)
make deps DEPS_ARGS=--dry-run # preview the commands without running themSee Installation for the full requirements table.
The Linux half of the CI matrix reproduces in a container — make docker-test, docker-emu, docker-asm, docker-sanitize, docker-ci (the
whole x86-64 matrix), docker-shell, and more; pass
DOCKER_PLATFORM=linux/arm64 to emulate the arm64 runner. See
CI & Docker for the full job-to-target mapping, and
make help for the complete target list.