LLVM Instrumentation
Relevant source files
The following files were used as context for generating this wiki page:
AFL++'s LLVM instrumentation provides a fast, robust, and feature-rich method for preparing source-available targets for fuzzing. It uses true compiler-level instrumentation via LLVM passes, which is more efficient and precise than the older assembly-level rewriting approach. This method supports multiple instrumentation strategies, advanced coverage metrics, and seamless integration with other AFL++ features like CmpLog, persistent mode, and selective instrumentation.
The primary tools for this are the compiler wrappers afl-clang-fast and afl-clang-lto, which act as drop-in replacements for clang and clang++. These wrappers automatically inject the necessary flags and instrumentation logic during the compilation process. This is the recommended instrumentation method for fuzzing C/C++ applications.
Sources: instrumentation/README.llvm.md:8-23, src/afl-cc.c:28-35
Core Component: afl-cc
The afl-cc binary is the central compiler wrapper that manages the entire instrumentation process. It analyzes its own invocation name (e.g., afl-clang-lto), environment variables, and command-line arguments to determine the appropriate compiler and instrumentation strategy. It then constructs a new command line to execute the real compiler (e.g., clang) with the required instrumentation passes and runtime objects.
Sources: src/afl-cc.c:1495-1590
Mode Selection Logic
afl-cc uses a specific order of precedence to select the compiler and instrumentation mode. This allows for flexible configuration via command-line flags, environment variables, or symlinks.
The following diagram illustrates the decision-making process:
This diagram shows how afl-cc prioritizes command-line flags over environment variables, which are in turn prioritized over the invocation name. If none are specified, it auto-detects the best available mode.
Sources: src/afl-cc.c:486-654
Instrumentation Modes
AFL++ offers several LLVM-based instrumentation modes, each with different trade-offs in terms of performance, coverage granularity, and compatibility. The mode can be selected using the AFL_LLVM_INSTRUMENT environment variable.
Sources: src/afl-cc.c:88-111, instrumentation/README.llvm.md:123-126
LTO (Link-Time Optimization) Mode
This is the most advanced and recommended instrumentation mode, enabled by using the afl-clang-lto compiler wrapper or setting AFL_LLVM_INSTRUMENT=LTO.
- Collision-Free Coverage: By instrumenting at link time, it assigns unique IDs to all edges, eliminating the hash collisions that can hide paths in other modes.
- Performance: Often 10-25% faster than other modes due to whole-program optimization.
- Auto-Dictionary: Automatically creates a dictionary from string comparisons found in the code and embeds it into the binary for
afl-fuzzto use. - Requirements: Requires LLVM 12+ and
lld. Build systems may needAR=llvm-arandRANLIB=llvm-ranlibto be set.
The LTO compilation process involves afl-clang-lto invoking clang to produce LTO bitcode, and then a custom linker step loads the SanitizerCoverageLTO.so pass to perform the instrumentation.
Sources: instrumentation/README.lto.md:15-40, 151-155
PCGUARD Mode
This is the default mode for afl-clang-fast when using LLVM 7 or newer. It is a robust and performant option.
- Mechanism: Uses LLVM's
trace-pc-guardfeature. - Optimized Pass: On LLVM 13+, it uses a custom pass (
SanitizerCoveragePCGUARD.so) for better performance. On older versions, it falls back to the native, unoptimizedtrace-pc-guard. - Collision-Free: Like LTO mode, PCGUARD provides collision-free coverage.
Sources: instrumentation/README.llvm.md:310-333, src/afl-cc.c:1320-1351
CLASSIC Mode
This is the original instrumentation mode from AFL, using afl-llvm-pass.so.
- Mechanism: Instruments branches at the LLVM IR level.
- Compatibility: It is the required base for advanced coverage metrics like N-gram and CTX.
- Collisions: Subject to hash collisions in the coverage map, which can be a limitation for very large programs.
Sources: instrumentation/README.llvm.md:21-23, src/afl-cc.c:1367-1371
Mode Comparison
| Mode | Wrapper | Key Feature | Coverage | Performance |
|---|---|---|---|---|
| LTO | afl-clang-lto | Link-time instrumentation | Collision-free | Excellent |
| PCGUARD | afl-clang-fast | trace-pc-guard based | Collision-free | Very Good |
| CLASSIC | afl-clang-fast | Original edge counting | Collisions possible | Good |
| LLVM-NATIVE | afl-clang-fast | Clang's built-in sancov | Collision-free | Good |
Sources: instrumentation/README.lto.md:30-33, instrumentation/README.llvm.md:10-14
Advanced Coverage Metrics
These metrics can be combined with CLASSIC mode to provide more granular coverage information to the fuzzer, potentially helping it solve complex path constraints. They are enabled via the AFL_LLVM_INSTRUMENT environment variable.
N-Gram Coverage
- Variable:
AFL_LLVM_INSTRUMENT=NGRAM-{2-16} - Description: Augments edge coverage by considering the
n-1previously visited locations. The coverage is calculated asmap[current_location ^ prev_location[0] >> 1 ^ ... up to n-1]. This can help the fuzzer understand more complex state transitions. - Recommendation: Good values for
nare 2, 4, or 8. Using this mode requires increasing the map size (MAP_SIZE_POW2) to avoid excessive collisions.
Sources: instrumentation/README.llvm.md:245-264
Context-Sensitive Coverage
- Variables:
AFL_LLVM_INSTRUMENT=CTXorAFL_LLVM_INSTRUMENT=CALLER - Description: Differentiates coverage based on the call stack.
- CTX: Hashes the entire call stack of function IDs and XORs it with the edge hash. This distinguishes paths in a function
Cwhen called fromAversus when called fromB. - CALLER: A lighter version that only considers the immediate caller's function ID.
- CTX: Hashes the entire call stack of function IDs and XORs it with the edge hash. This distinguishes paths in a function
- Recommendation: Also requires a larger map size to be effective.
Sources: instrumentation/README.llvm.md:190-231
Configuration and Usage
Basic Compilation
To compile a target, replace gcc/clang with the AFL++ LLVM wrappers.
# For LTO mode (recommended)
CC=/path/to/afl-clang-lto CXX=/path/to/afl-clang-lto++ ./configure
make
# For PCGUARD/CLASSIC mode
CC=/path/to/afl-clang-fast CXX=/path/to/afl-clang-fast++ ./configure
make
Sources: instrumentation/README.llvm.md:86-91
Key Environment Variables
The LLVM instrumentation can be fine-tuned with several environment variables set during compilation.
| Variable | Description | Example |
|---|---|---|
AFL_LLVM_INSTRUMENT | Sets the instrumentation mode and coverage metrics. | CLASSIC:CTX, NGRAM-4 |
AFL_LLVM_ALLOWLIST | Path to a file listing files/functions to instrument. | /path/to/allow.txt |
AFL_LLVM_DENYLIST | Path to a file listing files/functions to not instrument. | /path/to/deny.txt |
AFL_LLVM_CMPLOG | Enables CmpLog to record and replay comparison operands. | 1 |
AFL_LLVM_LAF_ALL | Enables all LAF-intel features (comparison splitting). | 1 |
AFL_LLVM_DICT2FILE | Extracts all constant string comparisons into a dictionary file. | /path/to/dict.txt |
AFL_LLVM_MAP_ADDR | (LTO mode) Sets a fixed address for the coverage map to improve speed. | 0x10000 |
AFL_LLVM_IJON | Enables the IJON pass for state-aware fuzzing. | 1 |
Sources: src/afl-cc.c:1000-1106, instrumentation/README.llvm.md:101-106, instrumentation/README.lto.md:167-172
Selective Instrumentation
For complex projects, it is often beneficial to instrument only the target library or code region, ignoring well-tested code like argument parsers. This can be achieved with an allow/deny list.
- Environment Variable:
AFL_LLVM_ALLOWLISTorAFL_LLVM_DENYLIST - File Format: The file should contain one entry per line. Entries can be source file names, function names, or use wildcards.
src: path/to/file.cfun: my_function_name(C++ names must be mangled)*main.c(wildcard matching)
Example allow.txt:
# Instrument only files in the 'parser' directory
src:parser/
# Also instrument this specific function
fun:process_data
This feature is implemented by the afl-llvm-lto-instrumentlist.so pass in LTO mode or parsed directly by afl-cc for other modes.
Sources: instrumentation/README.instrument_list.md:32-91, instrumentation/afl-gcc-common.h:108-259
Special Features
CmpLog Instrumentation
Enabled by setting AFL_LLVM_CMPLOG=1, this feature instruments comparison instructions and string/memory functions. During a special fuzzing stage, afl-fuzz uses this information to learn "magic values" from comparisons and applies them as mutations, which is highly effective for bypassing checks like if (input == 0xDEADBEEF).
Sources: test/test-llvm.sh:196-197, src/afl-cc.c:1104-1106
Persistent Mode
LLVM instrumentation provides macros to enable persistent mode, which can offer a massive speedup by fuzzing in a loop within a single process, avoiding repeated process creation overhead. afl-cc defines these macros for the preprocessor.
__AFL_LOOP(count): The main loop for the fuzzer.__AFL_INIT(): A one-time initialization function for deferred forkserver startup.
Sources: src/afl-cc.c:808-868
IJON Instrumentation
Enabled with AFL_LLVM_IJON=1, this feature activates the afl-llvm-ijon-pass.so pass. It allows for state-aware fuzzing by tracking how values of specific variables change at certain program locations, providing richer feedback to the fuzzer than simple edge coverage.
Sources: src/afl-cc.c:1400-1403, test/test-ijon-complete.sh:11, 15-17
Summary
AFL++'s LLVM instrumentation is a sophisticated system that provides multiple layers of optimization and coverage analysis for fuzzing. The LTO mode is the state-of-the-art choice for its collision-free coverage and performance, while PCGUARD mode serves as a strong and reliable default. By combining these core modes with features like CmpLog, selective instrumentation, and advanced coverage metrics, developers can create highly effective and tailored fuzzing harnesses for even the most complex software targets.
Sources: instrumentation/README.lto.md, instrumentation/README.llvm.md