How Coverage Tracking Works
Coverage tracking is the core mechanism that makes AFL++ an intelligent, feedback-driven fuzzer. During execution, the instrumented target program records which code paths were taken, and AFL++ uses this information to decide which inputs are interesting and worth mutating further.The Coverage Map (Bitmap)
AFL++ uses a shared memory region called the coverage map or bitmap to track execution paths. This is a fixed-size array (default: 64KB) where each byte represents an edge in the program’s control flow graph.The coverage map is allocated in shared memory between afl-fuzz and the target process, enabling extremely fast communication with minimal syscall overhead.
Edge Coverage
Unlike simple code coverage tools that track which lines or blocks were executed, AFL++ tracks edges (transitions between basic blocks). This provides much richer information about program behavior. The instrumentation code inserted at each edge updates the map:- Captures the control flow between blocks
- Detects loop iterations and recursive calls
- Identifies unique execution sequences
- Uses XOR for compact representation
Hit Counts
AFL++ doesn’t just track whether an edge was hit, but how many times it was executed. The 8-bit counters are grouped into buckets:- 1 hit
- 2 hits
- 3 hits
- 4-7 hits
- 8-15 hits
- 16-31 hits
- 32-127 hits
- 128+ hits
Hit count buckets help AFL++ detect behavioral differences even when the same code paths are executed. For example, a loop running 5 times vs 100 times may expose different bugs.
Collision-Free Coverage
One of the biggest problems with traditional AFL instrumentation is edge collisions - when multiple edges map to the same position in the coverage bitmap.The Collision Problem
With random edge IDs assigned during compilation:- 64KB map with 256 edges: ~1 collision
- 64KB map with 10,000 edges: ~750 collisions
- 64KB map with 50,000 edges: ~18,000 collisions
PCGUARD Mode (LLVM)
LLVM mode with PCGUARD instrumentation (LLVM 9+) provides collision-free coverage:- No collisions within a single compilation unit
- Faster execution than classic AFL instrumentation
- Better path discovery
- Works automatically with afl-clang-fast
LTO Mode
Link-Time Optimization (LTO) mode provides the best collision-free coverage by instrumenting at link time when all compilation units are available:- Guarantees collision-free coverage across the entire program
- Assigns optimal edge IDs during linking
- Provides 10-25% performance boost
- Reports exact collision savings
NeverZero Counters
The Zero-Wrap Problem
In large or iterative programs, 8-bit edge counters can overflow. When a counter wraps from 255 back to 0, AFL++ loses the information that this edge was executed. Consider a loop that runs 256 times:How NeverZero Works
NeverZero counters prevent wrapping to zero. When a counter would overflow from 255, it jumps to 1 instead:NeverZero improves path discovery at minimal cost. Testing shows it’s superior to saturated counters (which cap at 255) for finding new paths.
NeverZero Availability
Default enabled:- LLVM mode with LLVM 9+
- LTO mode with LLVM 11+
- QEMU mode
- Unicorn mode
Thread-safe instrumentation (
AFL_LLVM_THREADSAFE_INST=1) disables NeverZero by default for performance reasons.Coverage Map Analysis
Map Density
The AFL++ status screen shows map density:- First number: Current input
- Second number: Entire corpus
1.00: Every edge always hit the same number of times (low diversity)8.00: All hit count buckets used (high diversity)
Measuring Coverage
Useafl-showmap to analyze coverage:
- Total instrumented edges: 9,960
- Edges covered: 4,331 (43.48%)
- Test cases in corpus: 7,849
Advanced Coverage Modes
Context-Sensitive Coverage
Augments edge coverage with calling context:- Distinguishes the same function called from different callers
- Discovers context-dependent bugs
- Better path exploration in complex codebases
- Requires larger map (increase
MAP_SIZE_POW2to 18-20) - More map collisions if map is too small
N-Gram Coverage
Tracks sequences of N edges instead of single edges:- Captures longer execution sequences
- Proven effective in research
- Detects order-dependent behavior
- Explodes map size (use
MAP_SIZE_POW2=20or larger) - Higher collision risk without large maps
- More memory usage
N-gram coverage with N=2, 4, or 8 has shown improvements in fuzzing effectiveness, particularly for targets with complex state machines.
Caller Coverage
Lighter alternative to context-sensitive coverage, only tracks the immediate caller:Optimizing Coverage Map Size
The default map size is 64KB (MAP_SIZE_POW2=16 in config.h). For different targets, you may need to adjust:
Small targets (<5,000 edges):
Larger maps use more memory. With many parallel instances, this can add up. Balance map size against available RAM.
Thread Safety
By default, AFL++ coverage counters are not thread-safe for performance. In multi-threaded programs, concurrent updates may lose some hits.Thread-Safe Counters
Enable atomic counter updates:- Better precision in multi-threaded apps
- Slight instrumentation overhead
- Disables NeverZero for performance
Stability Metric
AFL++ shows a “stability” percentage:- 100%: Perfect (deterministic execution)
- 90-100% (purple): Good, minor variations acceptable
- <90% (red): Problematic, causes include:
- Uninitialized memory
- Multithreading with race conditions
- Random number generation
- Timestamp dependencies
- External state (temp files, shared memory)
Coverage-Guided Corpus Minimization
AFL++ automatically minimizes the corpus during fuzzing, but you can manually minimize:afl-cmin
Removes redundant inputs that don’t add coverage:afl-tmin
Minimizes individual test cases:Minimized corpus = faster fuzzing. Smaller inputs execute faster and allow more iterations per second.
Next Steps
- Explore mutation strategies that use coverage feedback
- Learn about instrumentation modes
- Check performance optimization tips
- Read about parallel fuzzing to scale coverage exploration

