覆盖分析Skill coverage-analysis

覆盖分析是一种软件测试技术,用于测量在模糊测试期间代码的执行覆盖率。它帮助评估测试harness的有效性,识别fuzzing阻塞点如魔法值检查,并跟踪harness改进的效果。关键词:覆盖分析,模糊测试,代码覆盖率,harness评估,fuzzing阻塞。

测试 0 次安装 0 次浏览 更新于 3/14/2026

名称: 覆盖分析 类型: 技术 描述: > 覆盖分析测量在模糊测试期间执行的代码。 当评估harness有效性或识别fuzzing阻塞点时使用。

覆盖分析

覆盖分析对于理解在模糊测试期间哪些代码部分被执行至关重要。它帮助识别fuzzing阻塞点,如魔法值检查,并跟踪harness改进随时间的效果。

概述

模糊测试期间的代码覆盖率有两个关键目的:

  1. 评估harness有效性:理解您的应用程序的哪些部分实际上由您的模糊测试harness执行
  2. 跟踪fuzzing进度:监视当更新harness、fuzzers或系统在测试(SUT)时覆盖率如何变化

覆盖率是模糊测试能力和性能的代理。虽然覆盖率在绝对意义上不适用于测量模糊测试性能,但它可靠地指示您的harness在给定设置中是否有效工作。

关键概念

概念 描述
覆盖工具化 跟踪哪些代码路径被执行的编译器标志
语料库覆盖率 运行模糊测试语料库中所有测试用例实现的覆盖率
魔法值检查 难以发现的条件检查,阻塞模糊测试进度
覆盖引导模糊测试 优先发现新代码路径的输入的模糊测试策略
覆盖率报告 执行与未执行代码的可视或文本表示

何时应用

在以下情况下应用此技术:

  • 开始新的模糊测试活动以建立基线
  • 模糊测试器似乎停滞,未找到新路径
  • 在harness修改后验证改进
  • 在不同模糊测试器之间迁移时
  • 识别需要字典条目或种子输入的区域
  • 调试为什么某些代码路径未到达

在以下情况下跳过此技术:

  • 模糊测试活动正在积极发现崩溃
  • 覆盖基础设施尚未设置
  • 处理极大的代码库,其中完整覆盖率报告不实用
  • 模糊测试器内部覆盖指标足够满足您的需求

快速参考

任务 命令/模式
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
[覆盖分析]
       |
       +---> 覆盖率增加? --> 继续模糊测试,使用更大语料库
       |
       +---> 覆盖率减少? --> 修复harness或调查SUT变化
       |
       +---> 覆盖率停滞? --> 添加字典条目或种子输入

关键原则:使用每个模糊测试活动后生成的语料库来计算覆盖率,而不是实时模糊测试器统计。这种方法提供了跨不同模糊测试工具的可重现、可比较的测量。

逐步指南

步骤1:使用覆盖工具化构建

根据工具链选择您的工具化方法:

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

步骤2:创建执行运行时(仅C/C++)

对于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
", 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
", filename);
        fclose(file);
        return;
    }

    long read_size = (long) fread(buffer, 1, filesize, file);
    if (read_size != filesize) {
        printf("Failed to read file: %s
", 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>
", argv[0]);
        return 1;
    }

    DIR *dir = opendir(argv[1]);
    if (dir == NULL) {
        printf("Failed to open directory: %s
", 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;
}

步骤3:在语料库上执行

LLVM(C/C++):

LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/

GCC(C/C++):

./fuzz_exec_gcov corpus/

Rust: 覆盖数据在运行cargo fuzz coverage时自动生成。

步骤4:处理覆盖数据

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/

使用gcovr的GCC:

# 安装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

步骤5:分析结果

审查覆盖率报告以识别:

  • 未覆盖代码块:可能需要更好种子输入或字典条目的区域
  • 魔法值检查:带有硬编码值的条件语句,阻塞进度
  • 死代码:可能无法通过您的harness访问的函数
  • 覆盖变化:与基线比较以跟踪改进或回归

常见模式

模式:识别魔法值

问题:模糊测试器无法发现由魔法值检查保护的路径。

覆盖显示:

// 覆盖显示此块从未执行
if (buf == 0x7F454C46) {  // ELF魔法数字
    // 开始解析buf
}

解决方案:添加魔法值到字典文件:

# magic.dict
"\x7F\x45\x4C\x46"

模式:处理崩溃输入

问题:当语料库包含崩溃输入时,覆盖生成失败。

之前:

./fuzz_exec corpus/  # 在坏输入上崩溃,未生成覆盖

之后:

// 在执行前分叉以隔离崩溃
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集成

使用案例:向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文件以跟踪随时间进度
从报告中过滤harness代码 使用-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/

处理大型代码库

对于有数百个源文件的项目:

  1. 按前缀过滤:仅生成相关目录的报告

    llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata /path/to/src/
    
  2. 使用目录覆盖:按目录分组以减少混乱(LLVM 18+)

    llvm-cov show -show-directory-coverage -format=html -output-dir html/
    
  3. 生成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
不过滤harness代码 Harness覆盖膨胀数字并掩盖SUT覆盖 使用-ignore-filename-regex--exclude过滤harness文件
混合LLVM和GCC工具化 不兼容格式导致解析失败 对覆盖构建坚持使用一个工具链
忽略崩溃输入 崩溃阻止覆盖生成,隐藏真实覆盖数据 首先修复崩溃,或使用进程分叉隔离它们
不随时间跟踪覆盖 一次性覆盖检查错过回归和改进 存储带时间戳的覆盖数据并跟踪趋势

工具特定指导

libFuzzer

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(它与配置文件工具化冲突)
  • 重用相同harness函数(LLVMFuzzerTestOneInput),但使用不同主函数
  • 使用-ignore-filename-regex标志从覆盖率报告中排除harness代码
  • 考虑对模板重的C++代码使用llvm-cov的-show-instantiation标志

AFL++

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++工具化(afl-clang-fast
  • 改用带覆盖标志的标准编译器
  • AFL++的queue/目录包含您的语料库
  • AFL++的内置覆盖统计对实时监视有用,但对详细分析不够

cargo-fuzz(Rust)

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

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
  • honggfuzz语料库通常在workspace目录中
  • 使用与libFuzzer相同的LLVM工作流

故障排除

问题 原因 解决方案
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标志
崩溃阻止覆盖生成 语料库包含崩溃输入 过滤崩溃或使用分叉方法隔离失败
在harness变化后覆盖率下降 Harness现在跳过某些代码路径 审查harness逻辑;可能需要支持更多输入格式
HTML报告是平面文件列表 使用旧LLVM版本 升级到LLVM 18+并使用-show-directory-coverage
incompatible instrumentation 混合LLVM和GCC覆盖 使用相同工具链重建一切

相关技能

使用此技术的工具

技能 如何应用
libfuzzer 使用SanitizerCoverage进行反馈;覆盖分析评估harness有效性
aflpp 使用边缘覆盖进行反馈;详细分析需要单独工具化
cargo-fuzz 内置cargo fuzz coverage命令用于Rust项目
honggfuzz 使用边缘覆盖;使用标准LLVM/GCC工具分析

相关技术

技能 关系
fuzz-harness-writing 覆盖揭示harness到达哪些代码路径;指导harness改进
fuzzing-dictionaries 覆盖识别需要字典条目的魔法值检查
corpus-management 覆盖分析通过识别冗余测试用例帮助管理语料库
sanitizers 覆盖帮助验证sanitizer工具化代码是否实际执行

资源

关键外部资源

LLVM基于源代码的代码覆盖 LLVM配置文件工具化的综合指南,包括分支覆盖、区域覆盖和与现有构建系统集成等高级功能。涵盖编译器标志、运行时行为和配置文件数据格式。

llvm-cov命令指南 llvm-cov命令的详细CLI参考,包括showreportexport。文档所有过滤选项、输出格式和与llvm-profdata的集成。

gcovr文档 从gcov数据生成覆盖报告的gcovr工具的完整指南。涵盖HTML主题、过滤选项、多目录项目和CI/CD集成模式。

SanitizerCoverage文档 LLVM的SanitizerCoverage工具化的低级文档。解释内联8位计数器、PC表以及模糊测试器如何使用覆盖反馈进行引导。

关于模糊测试器性能评估 研究论文,检查覆盖作为模糊测试性能指标的局限性。主张超越简单代码覆盖百分比的更细致评估方法。

视频资源

不适用 - 覆盖分析主要是工具和工作流主题,最好通过文档和动手实践学习。