npx skills add https://github.com/trailofbits/skills --skill coverage-analysis覆盖率分析对于理解在模糊测试期间代码的哪些部分被执行至关重要。它有助于识别模糊测试的阻碍因素(如魔法值检查),并跟踪测试工具改进随时间推移的效果。
模糊测试期间的代码覆盖率有两个关键用途:
覆盖率是模糊器能力和性能的一个代理指标。虽然覆盖率并非衡量模糊器性能的理想指标,但它能可靠地指示您的测试工具在给定设置中是否有效工作。
| 概念 | 描述 |
|---|---|
| 覆盖率插桩 | 跟踪哪些代码路径被执行的编译器标志 |
| 语料库覆盖率 | 通过运行模糊测试语料库中的所有测试用例实现的覆盖率 |
| 魔法值检查 | 难以发现的条件检查,会阻碍模糊器进展 |
| 覆盖率引导的模糊测试 | 优先选择能发现新代码路径的输入的模糊测试策略 |
| 覆盖率报告 | 已执行与未执行代码的可视化或文本表示 |
在以下情况下应用此技术:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在以下情况下跳过此技术:
| 任务 | 命令/模式 |
|---|---|
| LLVM 覆盖率插桩(C/C++) | -fprofile-instr-generate -fcoverage-mapping |
| GCC 覆盖率插桩 | -ftest-coverage -fprofile-arcs |
| cargo-fuzz 覆盖率(Rust) | cargo +nightly fuzz coverage <target> |
| 生成 LLVM 配置文件数据 | llvm-profdata merge -sparse file.profraw -o file.profdata |
| LLVM 覆盖率报告 | llvm-cov report ./binary -instr-profile=file.profdata |
| LLVM HTML 报告 | llvm-cov show ./binary -instr-profile=file.profdata -format=html -output-dir html/ |
| gcovr HTML 报告 | gcovr --html-details -o coverage.html |
以下工作流程代表了将覆盖率分析集成到模糊测试活动中的最佳实践:
[模糊测试活动]
|
v
[生成语料库]
|
v
[覆盖率分析]
|
+---> 覆盖率增加? --> 使用更大的语料库继续模糊测试
|
+---> 覆盖率减少? --> 修复测试工具或调查 SUT 变化
|
+---> 覆盖率停滞? --> 添加字典条目或种子输入
关键原则:使用每次模糊测试活动之后生成的语料库来计算覆盖率,而不是使用实时的模糊器统计数据。这种方法提供了跨不同模糊测试工具的可重现、可比较的测量结果。
根据工具链选择插桩方法:
LLVM/Clang (C/C++):
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
GCC (C/C++):
g++ -ftest-coverage -fprofile-arcs \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
Rust:
rustup toolchain install nightly --component llvm-tools-preview
cargo +nightly fuzz coverage fuzz_target_1
对于 C/C++ 项目,创建一个执行语料库的运行时:
// execute-rt.cc
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
void load_file_and_test(const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("Failed to open file: %s\n", filename);
return;
}
fseek(file, 0, SEEK_END);
long filesize = ftell(file);
rewind(file);
uint8_t *buffer = (uint8_t*) malloc(filesize);
if (buffer == NULL) {
printf("Failed to allocate memory for file: %s\n", filename);
fclose(file);
return;
}
long read_size = (long) fread(buffer, 1, filesize, file);
if (read_size != filesize) {
printf("Failed to read file: %s\n", filename);
free(buffer);
fclose(file);
return;
}
LLVMFuzzerTestOneInput(buffer, filesize);
free(buffer);
fclose(file);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <directory>\n", argv[0]);
return 1;
}
DIR *dir = opendir(argv[1]);
if (dir == NULL) {
printf("Failed to open directory: %s\n", argv[1]);
return 1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
}
}
closedir(dir);
return 0;
}
LLVM (C/C++):
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
GCC (C/C++):
./fuzz_exec_gcov corpus/
Rust: 运行 cargo fuzz coverage 时会自动生成覆盖率数据。
LLVM:
# 合并原始配置文件数据
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
# 生成文本报告
llvm-cov report ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc'
# 生成 HTML 报告
llvm-cov show ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc' \
-format=html -output-dir fuzz_html/
GCC 使用 gcovr:
# 安装 gcovr(通过 pip 获取最新版本)
python3 -m venv venv
source venv/bin/activate
pip3 install gcovr
# 生成报告
gcovr --gcov-executable "llvm-cov gcov" \
--exclude harness.cc --exclude execute-rt.cc \
--root . --html-details -o coverage.html
Rust:
# 安装所需工具
cargo install cargo-binutils rustfilt
# 创建 HTML 生成脚本
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Error: Name of fuzz target is required."
echo "Usage: $0 fuzz_target [sources...]"
exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
# 生成 HTML 报告
./generate_html fuzz_target_1 src/lib.rs
查看覆盖率报告以识别:
问题:模糊器无法发现受魔法值检查保护的路径。
覆盖率显示:
// 覆盖率显示此代码块从未执行
if (buf == 0x7F454C46) { // ELF 魔法数
// 开始解析 buf
}
解决方案:将魔法值添加到字典文件:
# magic.dict
"\x7F\x45\x4C\x46"
问题:当语料库包含崩溃输入时,覆盖率生成失败。
之前:
./fuzz_exec corpus/ # 在错误输入上崩溃,未生成覆盖率
之后:
// 在执行前 fork 以隔离崩溃
int main(int argc, char **argv) {
// ... 目录打开代码 ...
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
pid_t pid = fork();
if (pid == 0) {
// 子进程 - 崩溃不会影响父进程
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
exit(0);
} else {
// 父进程等待子进程
waitpid(pid, NULL, 0);
}
}
}
}
用例:将覆盖率构建添加到 CMake 项目中。
project(FuzzingProject)
cmake_minimum_required(VERSION 3.0)
# 主二进制文件
add_executable(program main.cc)
# 模糊测试二进制文件
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer)
target_link_libraries(fuzz -fsanitize=fuzzer)
# 覆盖率执行二进制文件
add_executable(fuzz_exec main.cc harness.cc execute-rt.cc)
target_compile_definitions(fuzz_exec PRIVATE NO_MAIN)
target_compile_options(fuzz_exec PRIVATE -O2 -fprofile-instr-generate -fcoverage-mapping)
target_link_libraries(fuzz_exec -fprofile-instr-generate)
构建:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target fuzz_exec
| 技巧 | 为何有帮助 |
|---|---|
使用 LLVM 18+ 的 -show-directory-coverage | 按目录结构组织大型报告,而不是扁平的文件列表 |
| 导出为 lcov 格式以获得更好的 HTML | llvm-cov export -format=lcov + genhtml 提供更清晰的每文件报告 |
| 比较不同活动间的覆盖率 | 存储带时间戳的 .profdata 文件以跟踪随时间推移的进度 |
| 从报告中过滤测试工具代码 | 使用 -ignore-filename-regex 仅关注 SUT 覆盖率 |
| 在 CI/CD 中自动化覆盖率 | 在计划的模糊测试运行后自动生成覆盖率报告 |
| 对 Clang 14+ 使用 gcovr 5.1+ | 较旧的 gcovr 版本与最近的 LLVM 存在兼容性问题 |
GCC 的 gcov 插桩会在多次运行中增量更新 .gcda 文件。这在添加测试用例时跟踪覆盖率很有用:
# 第一次运行
./fuzz_exec_gcov corpus_batch_1/
gcovr --html coverage_v1.html
# 第二次运行(添加到现有覆盖率)
./fuzz_exec_gcov corpus_batch_2/
gcovr --html coverage_v2.html
# 重新开始
gcovr --delete # 删除 .gcda 文件
./fuzz_exec_gcov corpus/
对于有数百个源文件的项目:
按前缀过滤:仅生成相关目录的报告
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata /path/to/src/
使用目录覆盖率:按目录分组以减少混乱(LLVM 18+)
llvm-cov show -show-directory-coverage -format=html -output-dir html/
生成 JSON 用于程序化分析:
llvm-cov export -format=lcov > coverage.json
比较两个模糊测试活动之间的覆盖率:
# 活动 1
LLVM_PROFILE_FILE=campaign1.profraw ./fuzz_exec corpus1/
llvm-profdata merge -sparse campaign1.profraw -o campaign1.profdata
# 活动 2
LLVM_PROFILE_FILE=campaign2.profraw ./fuzz_exec corpus2/
llvm-profdata merge -sparse campaign2.profraw -o campaign2.profdata
# 比较
llvm-cov show ./fuzz_exec \
-instr-profile=campaign2.profdata \
-instr-profile=campaign1.profdata \
-show-line-counts-or-regions
| 反模式 | 问题 | 正确方法 |
|---|---|---|
| 使用模糊器报告的覆盖率进行比较 | 不同的模糊器以不同方式计算覆盖率,使得跨工具比较毫无意义 | 使用专用的覆盖率工具(llvm-cov、gcovr)进行可重现的测量 |
| 使用优化生成覆盖率 | -O3 优化可能会消除代码,使覆盖率产生误导 | 对覆盖率构建使用 -O2 或 -O0 |
| 不过滤测试工具代码 | 测试工具覆盖率会夸大数字并掩盖 SUT 覆盖率 | 使用 -ignore-filename-regex 或 --exclude 过滤测试工具文件 |
| 混合 LLVM 和 GCC 插桩 | 不兼容的格式会导致解析失败 | 对覆盖率构建坚持使用一个工具链 |
| 忽略崩溃输入 | 崩溃会阻止覆盖率生成,隐藏真实的覆盖率数据 | 首先修复崩溃,或使用进程分叉来隔离它们 |
| 不随时间跟踪覆盖率 | 一次性的覆盖率检查会错过回归和改进 | 存储带时间戳的覆盖率数据并跟踪趋势 |
libFuzzer 默认使用 LLVM 的 SanitizerCoverage 来引导模糊测试,但您需要单独的插桩来生成报告。
为覆盖率构建:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
执行语料库并生成报告:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata -format=html -output-dir html/
集成技巧:
-fsanitize=fuzzer(它会与配置文件插桩冲突)LLVMFuzzerTestOneInput),但使用不同的主函数-ignore-filename-regex 标志从覆盖率报告中排除测试工具代码-show-instantiation 标志AFL++ 提供自己的覆盖率反馈机制,但对于详细报告,请使用标准的 LLVM/GCC 工具。
使用 LLVM 为覆盖率构建:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 main.cc harness.cc execute-rt.cc -o fuzz_exec
使用 GCC 为覆盖率构建:
AFL_USE_ASAN=0 afl-gcc -ftest-coverage -fprofile-arcs \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
执行并生成报告:
# LLVM 方法
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec afl_output/queue/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov report ./fuzz_exec -instr-profile=fuzz.profdata
# GCC 方法
./fuzz_exec_gcov afl_output/queue/
gcovr --html-details -o coverage.html
集成技巧:
afl-clang-fast)queue/ 目录包含您的语料库cargo-fuzz 使用 LLVM 工具提供内置的覆盖率生成。
安装先决条件:
rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils rustfilt
生成覆盖率数据:
cargo +nightly fuzz coverage fuzz_target_1
创建 HTML 报告脚本:
cat <<'EOF' > ./generate_html
#!/bin/sh
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
生成报告:
./generate_html fuzz_target_1 src/lib.rs
集成技巧:
-Xdemangler=rustfilt 标志使函数名可读src/lib.rs)过滤以专注于 crate 代码-show-line-counts-or-regions 和 -show-instantiations 以获得更好的 Rust 特定输出fuzz/corpus/<target>/ 中honggfuzz 与标准的 LLVM/GCC 覆盖率插桩配合使用。
为覆盖率构建:
# 使用标准编译器,而不是 honggfuzz 编译器
clang -fprofile-instr-generate -fcoverage-mapping \
-O2 harness.c execute-rt.c -o fuzz_exec
执行语料库:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec honggfuzz_workspace/
集成技巧:
hfuzz-clang| 问题 | 原因 | 解决方案 |
|---|---|---|
error: no profile data available | 配置文件未生成或路径错误 | 验证 LLVM_PROFILE_FILE 是否已设置且 .profraw 文件存在 |
Failed to load coverage | 二进制文件和配置文件数据不匹配 | 使用执行期间使用的相同标志重新构建二进制文件 |
| 覆盖率报告显示 0% | 用于报告生成的二进制文件错误 | 使用插桩的二进制文件,而不是模糊测试二进制文件 |
no_working_dir_found 错误(gcovr) | .gcda 文件位于意外位置 | 添加 --gcov-ignore-errors=no_working_dir_found 标志 |
| 崩溃阻止覆盖率生成 | 语料库包含崩溃输入 | 过滤崩溃或使用分叉方法隔离故障 |
| 测试工具更改后覆盖率下降 | 测试工具现在跳过某些代码路径 | 审查测试工具逻辑;可能需要支持更多输入格式 |
| HTML 报告是扁平文件列表 | 使用旧版 LLVM | 升级到 LLVM 18+ 并使用 -show-directory-coverage |
incompatible instrumentation | 混合 LLVM 和 GCC 覆盖率 | 使用相同的工具链重新构建所有内容 |
| 技能 | 如何应用 |
|---|---|
| libfuzzer | 使用 SanitizerCoverage 进行反馈;覆盖率分析评估测试工具有效性 |
| aflpp | 使用边缘覆盖率进行反馈;详细分析需要单独的插桩 |
| cargo-fuzz | 用于 Rust 项目的内置 cargo fuzz coverage 命令 |
| honggfuzz | 使用边缘覆盖率;使用标准 LLVM/GCC 工具进行分析 |
| 技能 | 关系 |
|---|---|
| fuzz-harness-writing | 覆盖率揭示测试工具到达哪些代码路径;指导测试工具改进 |
| fuzzing-dictionaries | 覆盖率识别需要字典条目的魔法值检查 |
| corpus-management | 覆盖率分析通过识别冗余测试用例来帮助管理语料库 |
| sanitizers | 覆盖率帮助验证经过消毒器插桩的代码是否实际执行 |
LLVM 基于源代码的代码覆盖率 LLVM 配置文件插桩的综合指南,包括分支覆盖率、区域覆盖率以及与现有构建系统集成等高级功能。涵盖编译器标志、运行时行为和配置文件数据格式。
llvm-cov 命令指南 llvm-cov 命令的详细 CLI 参考,包括 show、report 和 export。记录所有过滤选项、输出格式以及与 llvm-profdata 的集成。
gcovr 文档 从 gcov 数据生成覆盖率报告的 gcovr 工具的完整指南。涵盖 HTML 主题、过滤选项、多目录项目和 CI/CD 集成模式。
SanitizerCoverage 文档 LLVM 的 SanitizerCoverage 插桩的低级文档。解释内联 8 位计数器、PC 表以及模糊器如何使用覆盖率反馈进行引导。
关于模糊器性能评估的研究论文 研究论文,探讨了覆盖率作为模糊器性能指标的局限性。主张采用比简单的代码覆盖率百分比更细致的评估方法。
不适用 - 覆盖率分析主要是一个工具和工作流程主题,最好通过文档和动手实践来学习。
每周安装次数
1.2K
代码仓库
GitHub 星标数
3.9K
首次出现时间
Jan 19, 2026
安全审计
安装于
claude-code1.0K
opencode986
gemini-cli964
codex957
cursor939
github-copilot900
Coverage analysis is essential for understanding which parts of your code are exercised during fuzzing. It helps identify fuzzing blockers like magic value checks and tracks the effectiveness of harness improvements over time.
Code coverage during fuzzing serves two critical purposes:
Coverage is a proxy for fuzzer capability and performance. While coverage is not ideal for measuring fuzzer performance in absolute terms, it reliably indicates whether your harness works effectively in a given setup.
| Concept | Description |
|---|---|
| Coverage instrumentation | Compiler flags that track which code paths are executed |
| Corpus coverage | Coverage achieved by running all test cases in a fuzzing corpus |
| Magic value checks | Hard-to-discover conditional checks that block fuzzer progress |
| Coverage-guided fuzzing | Fuzzing strategy that prioritizes inputs that discover new code paths |
| Coverage report | Visual or textual representation of executed vs. unexecuted code |
Apply this technique when:
Skip this technique when:
| Task | Command/Pattern |
|---|---|
| LLVM coverage instrumentation (C/C++) | -fprofile-instr-generate -fcoverage-mapping |
| GCC coverage instrumentation | -ftest-coverage -fprofile-arcs |
| cargo-fuzz coverage (Rust) | cargo +nightly fuzz coverage <target> |
| Generate LLVM profile data | llvm-profdata merge -sparse file.profraw -o file.profdata |
| LLVM coverage report | llvm-cov report ./binary -instr-profile=file.profdata |
| LLVM HTML report |
The following workflow represents best practices for integrating coverage analysis into your fuzzing campaigns:
[Fuzzing Campaign]
|
v
[Generate Corpus]
|
v
[Coverage Analysis]
|
+---> Coverage Increased? --> Continue fuzzing with larger corpus
|
+---> Coverage Decreased? --> Fix harness or investigate SUT changes
|
+---> Coverage Plateaued? --> Add dictionary entries or seed inputs
Key principle : Use the corpus generated after each fuzzing campaign to calculate coverage, rather than real-time fuzzer statistics. This approach provides reproducible, comparable measurements across different fuzzing tools.
Choose your instrumentation method based on toolchain:
LLVM/Clang (C/C++):
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
GCC (C/C++):
g++ -ftest-coverage -fprofile-arcs \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
Rust:
rustup toolchain install nightly --component llvm-tools-preview
cargo +nightly fuzz coverage fuzz_target_1
For C/C++ projects, create a runtime that executes your corpus:
// execute-rt.cc
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
void load_file_and_test(const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("Failed to open file: %s\n", filename);
return;
}
fseek(file, 0, SEEK_END);
long filesize = ftell(file);
rewind(file);
uint8_t *buffer = (uint8_t*) malloc(filesize);
if (buffer == NULL) {
printf("Failed to allocate memory for file: %s\n", filename);
fclose(file);
return;
}
long read_size = (long) fread(buffer, 1, filesize, file);
if (read_size != filesize) {
printf("Failed to read file: %s\n", filename);
free(buffer);
fclose(file);
return;
}
LLVMFuzzerTestOneInput(buffer, filesize);
free(buffer);
fclose(file);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <directory>\n", argv[0]);
return 1;
}
DIR *dir = opendir(argv[1]);
if (dir == NULL) {
printf("Failed to open directory: %s\n", argv[1]);
return 1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
}
}
closedir(dir);
return 0;
}
LLVM (C/C++):
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
GCC (C/C++):
./fuzz_exec_gcov corpus/
Rust: Coverage data is automatically generated when running cargo fuzz coverage.
LLVM:
# Merge raw profile data
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
# Generate text report
llvm-cov report ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc'
# Generate HTML report
llvm-cov show ./fuzz_exec \
-instr-profile=fuzz.profdata \
-ignore-filename-regex='harness.cc|execute-rt.cc' \
-format=html -output-dir fuzz_html/
GCC with gcovr:
# Install gcovr (via pip for latest version)
python3 -m venv venv
source venv/bin/activate
pip3 install gcovr
# Generate report
gcovr --gcov-executable "llvm-cov gcov" \
--exclude harness.cc --exclude execute-rt.cc \
--root . --html-details -o coverage.html
Rust:
# Install required tools
cargo install cargo-binutils rustfilt
# Create HTML generation script
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Error: Name of fuzz target is required."
echo "Usage: $0 fuzz_target [sources...]"
exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
# Generate HTML report
./generate_html fuzz_target_1 src/lib.rs
Review the coverage report to identify:
Problem : Fuzzer cannot discover paths guarded by magic value checks.
Coverage reveals:
// Coverage shows this block is never executed
if (buf == 0x7F454C46) { // ELF magic number
// start parsing buf
}
Solution : Add magic values to dictionary file:
# magic.dict
"\x7F\x45\x4C\x46"
Problem : Coverage generation fails when corpus contains crashing inputs.
Before:
./fuzz_exec corpus/ # Crashes on bad input, no coverage generated
After:
// Fork before executing to isolate crashes
int main(int argc, char **argv) {
// ... directory opening code ...
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
pid_t pid = fork();
if (pid == 0) {
// Child process - crash won't affect parent
char filepath[1024];
snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
load_file_and_test(filepath);
exit(0);
} else {
// Parent waits for child
waitpid(pid, NULL, 0);
}
}
}
}
Use Case : Adding coverage builds to CMake projects.
project(FuzzingProject)
cmake_minimum_required(VERSION 3.0)
# Main binary
add_executable(program main.cc)
# Fuzzing binary
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer)
target_link_libraries(fuzz -fsanitize=fuzzer)
# Coverage execution binary
add_executable(fuzz_exec main.cc harness.cc execute-rt.cc)
target_compile_definitions(fuzz_exec PRIVATE NO_MAIN)
target_compile_options(fuzz_exec PRIVATE -O2 -fprofile-instr-generate -fcoverage-mapping)
target_link_libraries(fuzz_exec -fprofile-instr-generate)
Build:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target fuzz_exec
| Tip | Why It Helps |
|---|---|
Use LLVM 18+ with -show-directory-coverage | Organizes large reports by directory structure instead of flat file list |
| Export to lcov format for better HTML | llvm-cov export -format=lcov + genhtml provides cleaner per-file reports |
| Compare coverage across campaigns | Store .profdata files with timestamps to track progress over time |
| Filter harness code from reports | Use -ignore-filename-regex to focus on SUT coverage only |
| Automate coverage in CI/CD | Generate coverage reports automatically after scheduled fuzzing runs |
| Use gcovr 5.1+ for Clang 14+ |
GCC's gcov instrumentation incrementally updates .gcda files across multiple runs. This is useful for tracking coverage as you add test cases:
# First run
./fuzz_exec_gcov corpus_batch_1/
gcovr --html coverage_v1.html
# Second run (adds to existing coverage)
./fuzz_exec_gcov corpus_batch_2/
gcovr --html coverage_v2.html
# Start fresh
gcovr --delete # Remove .gcda files
./fuzz_exec_gcov corpus/
For projects with hundreds of source files:
Filter by prefix : Only generate reports for relevant directories
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata /path/to/src/
Use directory coverage : Group by directory to reduce clutter (LLVM 18+)
llvm-cov show -show-directory-coverage -format=html -output-dir html/
Generate JSON for programmatic analysis :
llvm-cov export -format=lcov > coverage.json
Compare coverage between two fuzzing campaigns:
# Campaign 1
LLVM_PROFILE_FILE=campaign1.profraw ./fuzz_exec corpus1/
llvm-profdata merge -sparse campaign1.profraw -o campaign1.profdata
# Campaign 2
LLVM_PROFILE_FILE=campaign2.profraw ./fuzz_exec corpus2/
llvm-profdata merge -sparse campaign2.profraw -o campaign2.profdata
# Compare
llvm-cov show ./fuzz_exec \
-instr-profile=campaign2.profdata \
-instr-profile=campaign1.profdata \
-show-line-counts-or-regions
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Using fuzzer-reported coverage for comparisons | Different fuzzers calculate coverage differently, making cross-tool comparison meaningless | Use dedicated coverage tools (llvm-cov, gcovr) for reproducible measurements |
| Generating coverage with optimizations | -O3 optimizations can eliminate code, making coverage misleading | Use -O2 or -O0 for coverage builds |
| Not filtering harness code | Harness coverage inflates numbers and obscures SUT coverage | Use -ignore-filename-regex or --exclude to filter harness files |
| Mixing LLVM and GCC instrumentation |
libFuzzer uses LLVM's SanitizerCoverage by default for guiding fuzzing, but you need separate instrumentation for generating reports.
Build for coverage:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 -DNO_MAIN \
main.cc harness.cc execute-rt.cc -o fuzz_exec
Execute corpus and generate report:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata -format=html -output-dir html/
Integration tips:
-fsanitize=fuzzer for coverage builds (it conflicts with profile instrumentation)LLVMFuzzerTestOneInput) with a different main function-ignore-filename-regex flag to exclude harness code from coverage reports-show-instantiation flag for template-heavy C++ codeAFL++ provides its own coverage feedback mechanism, but for detailed reports use standard LLVM/GCC tools.
Build for coverage with LLVM:
clang++ -fprofile-instr-generate -fcoverage-mapping \
-O2 main.cc harness.cc execute-rt.cc -o fuzz_exec
Build for coverage with GCC:
AFL_USE_ASAN=0 afl-gcc -ftest-coverage -fprofile-arcs \
main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov
Execute and generate report:
# LLVM approach
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec afl_output/queue/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov report ./fuzz_exec -instr-profile=fuzz.profdata
# GCC approach
./fuzz_exec_gcov afl_output/queue/
gcovr --html-details -o coverage.html
Integration tips:
afl-clang-fast) for coverage buildsqueue/ directory contains your corpuscargo-fuzz provides built-in coverage generation using LLVM tools.
Install prerequisites:
rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils rustfilt
Generate coverage data:
cargo +nightly fuzz coverage fuzz_target_1
Create HTML report script:
cat <<'EOF' > ./generate_html
#!/bin/sh
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
Generate report:
./generate_html fuzz_target_1 src/lib.rs
Integration tips:
-Xdemangler=rustfilt flag makes function names readablesrc/lib.rs) to focus on crate code-show-line-counts-or-regions and -show-instantiations for better Rust-specific outputfuzz/corpus/<target>/honggfuzz works with standard LLVM/GCC coverage instrumentation.
Build for coverage:
# Use standard compiler, not honggfuzz compiler
clang -fprofile-instr-generate -fcoverage-mapping \
-O2 harness.c execute-rt.c -o fuzz_exec
Execute corpus:
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec honggfuzz_workspace/
Integration tips:
hfuzz-clang for coverage builds| Issue | Cause | Solution |
|---|---|---|
error: no profile data available | Profile wasn't generated or wrong path | Verify LLVM_PROFILE_FILE was set and .profraw file exists |
Failed to load coverage | Mismatch between binary and profile data | Rebuild binary with same flags used during execution |
| Coverage reports show 0% | Wrong binary used for report generation | Use the instrumented binary, not the fuzzing binary |
no_working_dir_found error (gcovr) | .gcda files in unexpected location |
| Skill | How It Applies |
|---|---|
| libfuzzer | Uses SanitizerCoverage for feedback; coverage analysis evaluates harness effectiveness |
| aflpp | Uses edge coverage for feedback; detailed analysis requires separate instrumentation |
| cargo-fuzz | Built-in cargo fuzz coverage command for Rust projects |
| honggfuzz | Uses edge coverage; analyze with standard LLVM/GCC tools |
| Skill | Relationship |
|---|---|
| fuzz-harness-writing | Coverage reveals which code paths harness reaches; guides harness improvements |
| fuzzing-dictionaries | Coverage identifies magic value checks that need dictionary entries |
| corpus-management | Coverage analysis helps curate corpora by identifying redundant test cases |
| sanitizers | Coverage helps verify sanitizer-instrumented code is actually executed |
LLVM Source-Based Code Coverage Comprehensive guide to LLVM's profile instrumentation, including advanced features like branch coverage, region coverage, and integration with existing build systems. Covers compiler flags, runtime behavior, and profile data formats.
llvm-cov Command Guide Detailed CLI reference for llvm-cov commands including show, report, and export. Documents all filtering options, output formats, and integration with llvm-profdata.
gcovr Documentation Complete guide to gcovr tool for generating coverage reports from gcov data. Covers HTML themes, filtering options, multi-directory projects, and CI/CD integration patterns.
SanitizerCoverage Documentation Low-level documentation for LLVM's SanitizerCoverage instrumentation. Explains inline 8-bit counters, PC tables, and how fuzzers use coverage feedback for guidance.
On the Evaluation of Fuzzer Performance Research paper examining limitations of coverage as a fuzzing performance metric. Argues for more nuanced evaluation methods beyond simple code coverage percentages.
Not applicable - coverage analysis is primarily a tooling and workflow topic best learned through documentation and hands-on practice.
Weekly Installs
1.2K
Repository
GitHub Stars
3.9K
First Seen
Jan 19, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code1.0K
opencode986
gemini-cli964
codex957
cursor939
github-copilot900
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
llvm-cov show ./binary -instr-profile=file.profdata -format=html -output-dir html/ |
| gcovr HTML report | gcovr --html-details -o coverage.html |
| Older gcovr versions have compatibility issues with recent LLVM |
| Incompatible formats cause parsing failures |
| Stick to one toolchain for coverage builds |
| Ignoring crashing inputs | Crashes prevent coverage generation, hiding real coverage data | Fix crashes first, or use process forking to isolate them |
| Not tracking coverage over time | One-time coverage checks miss regressions and improvements | Store coverage data with timestamps and track trends |
Add --gcov-ignore-errors=no_working_dir_found flag |
| Crashes prevent coverage generation | Corpus contains crashing inputs | Filter crashes or use forking approach to isolate failures |
| Coverage decreases after harness change | Harness now skips certain code paths | Review harness logic; may need to support more input formats |
| HTML report is flat file list | Using older LLVM version | Upgrade to LLVM 18+ and use -show-directory-coverage |
incompatible instrumentation | Mixing LLVM and GCC coverage | Rebuild everything with same toolchain |