AFL++ fuzzes a program in three steps: compile it with an AFL++ wrapper so it reports coverage, prepare a small seed folder, and run afl-fuzz. The status screen shows progress through counters like cycles done, total paths, unique crashes and stability. Here is what each step needs and what the numbers mean.
Step 1: instrument
The AFL++ documentation recommends LTO mode with clang 11 or newer, then LLVM mode, then the GCC plugin mode as fallbacks. Replace the compiler in your build:
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --disable-shared
make
Build dependencies as static libraries, not shared ones, so they are instrumented too. For sanitizers, set environment variables such as AFL_USE_ASAN or AFL_USE_UBSAN when building. The docs advise running only one afl-fuzz instance per sanitizer type.
Step 2: prepare seeds
Gather a few small valid inputs. Then use two helper tools. afl-cmin removes inputs that add no new coverage. afl-tmin shrinks each input without changing the path it takes. Fewer, smaller seeds mean faster fuzzing.
Step 3: run
afl-fuzz -i input -o output -- ./target @@
The @@ is replaced with the input file path. Leave it out if the program reads standard input. The docs say to always set a memory limit with -m and to run afl-system-config first on a machine you control. Run it inside screen or tmux, since it runs until you stop it with Ctrl-C.
Persistent mode for speed
The docs describe persistent mode as much faster, reporting a 2x to 20x speedup, but it needs a harness that calls the target functions directly. AFL++ can also build libFuzzer style harnesses, so one harness can serve both tools.
Reading the status screen
The AFL++ documentation explains these fields:
- cycles done: passes through the queue. Every session should finish at least one.
- total paths: test cases found that trigger new behavior.
- uniq crashes and uniq hangs: unique faults and timeouts, saved in the crashes and hangs folders.
- map density: how much of the coverage bitmap is hit. Very low absolute values suggest a trivial, badly instrumented or early exiting program.
- stability: how consistently the same input behaves. 100 percent means fully repeatable. Lower values can come from uninitialized memory, leftover temp files, randomness or threads.
A crash counts as unique if its path contains a transition not seen in earlier crashes, so early counts can overstate the number of distinct bugs.
Scaling out
Run one main instance with -M and give each secondary a unique -S name, all sharing the same output folder. Performance drops past roughly 32 to 64 cores on one machine, according to the docs. Secondaries can use different builds, such as a sanitizer build or one with CmpLog.
Quick check
My stability is low. Is the run useless?
Not necessarily. Values above about 90 percent are usually harmless per the docs. Low values mean the fuzzer struggles to tell input effects from noise, so look for randomness, threads or leftover state.
Does one unique crash mean one bug?
No. One bug can be reached by several paths and counted several times. Deduplicate by stack trace when you triage.
Sources
Last reviewed: 19 October 2026.