为什么afl插桩可以动态获取覆盖率,而gcov需要终止程序来收集?
AFL(American Fuzzy Lop)使用的是插桩 + 共享内存的覆盖率收集机制。插桩后的代码会把覆盖信息写到一个共享内存区域。
gcov
这类工具,通常使用静态插桩 + 文件写出的方式收集覆盖率。只有在程序正常终止时,插桩逻辑才会将内存中的统计信息写到 .gcda
等文件中。
GCC、Clang、LLVM 三者的区别
名称 | 类型 | 说明 |
---|---|---|
GCC | 编译器套件 | GNU 编译器合集,包括 gcc , g++ , gfortran 等 |
Clang | 编译器前端 | LLVM 项目中的 C/C++/Objective-C 编译器 |
LLVM | 编译基础设施框架 | 提供中间语言、优化、后端、JIT 等一整套架构 |
LLVM把gcc解耦了,前后端分离,模块化
Source Code
▼
Frontend (Parser)
GCC or Clang ← GCC 与 Clang 竞争的是这里
▼
LLVM IR ← Clang 会转成中间代码
▼
Optimizer (opt)
▼
Backend (llc)
Generate Assembly
▼
Linker (lld)
💡 AFL 根据你系统中安装的编译器选择不同的插桩模式和深度。你用得越高级(比如 afl-clang-fast
),对 Clang/LLVM 的依赖就越强。
插桩方式 | 编译器依赖 | 插桩原理 | 是否推荐 |
---|---|---|---|
afl-gcc |
只要 gcc 就行 | 静态插桩,效果一般 | ❌(老旧) |
afl-clang |
clang 即可 | 静态插桩 | ❌(少用了) |
afl-clang-fast |
clang + llvm | LLVM IR 级插桩(支持 PCGUARD) | ✅ 推荐使用 |
afl-clang-fast` 使用了 PCGUARD 插桩 ,但还封装了 共享内存逻辑 + 路径 hash + 热度计数;纯 PCGUARD 插桩更干净、更灵活,更适合你这种 Peach 外部控制执行流程的情况; 两者在“插桩方式”看起来类似,但在运行时处理覆盖率的方式不同(特别是 bitmap 的更新频率和逻辑);
项目 | AFL 默认插桩(afl-gcc) | afl-clang-fast(基于 PCGUARD) | 纯 PCGUARD 插桩 |
---|---|---|---|
插桩方式 | 修改源码或汇编 | 添加 LLVM IR pass,激活 trace-pc-guard |
使用 clang 内置的 -fsanitize-coverage=trace-pc-guard |
插桩代码 | 自己加 inline asm | 让编译器插入回调,链接 AFL runtime | 只插入调用 __sanitizer_cov_trace_pc_guard() ,不链接 AFL runtime |
侵入性 | 中等:依赖特殊宏定义 | 高:需要额外 runtime 支持 | 低:编译器自带 |
性能影响 | 中 | 高(加 hash + map 计数) | 低(只标记一次命中) |
实时性能力 | ✅ 用共享内存读 bitmap | ✅ 可用共享内存 | ✅ 你可以自己定义共享内存 & 写 hook |
结果粒度 | 分支级别 + hit 频率 | 分支级别 + hit 桶 | 分支级别(单次) |
clang插桩编译 :
1 | clang -fsanitize-coverage=trace-pc-guard -g -o target_cov target.c coverage_runtime.c |
Clang trace-pc-guard 插桩只是调用
__sanitizer_cov_trace_pc_guard()
,但是你需要提供这个函数的定义来告诉程序 “hit 了这个分支之后要干什么”。记录在哪?怎么算?怎么展示? 这些逻辑都得你来写(AFL、SanCov、LibFuzzer 都是自带的 runtime 实现)。
当面对一个使用 Makefile 的项目,想用自己写的 PCGUARD 插桩 runtime(如 coverage_runtime.c
) 时:
1 | clang -O0 -g -fno-omit-frame-pointer \ -fsanitize-coverage=trace-pc-guard \ -c coverage_runtime.c -o coverage_runtime.o |
1 |
|
guard就是edge的编号,hit(命中)了就记录在共享内存
特性/工具 | LLVM/Clang 插桩 | GCC/gcov 插桩 |
---|---|---|
插桩工具 | -fsanitize-coverage=... |
-fprofile-arcs -ftest-coverage |
覆盖粒度 | 基本块、边(edge)、调用、返回等 | 行(line)、分支(branch)、函数(function) |
用途 | 模糊测试、路径去重、反馈引导优化等 | 单元测试、代码覆盖分析 |
代表工具 | AFL, LibFuzzer, etc. | gcov, lcov, gcovr |
报告能力 | 不生成测试报告(靠 fuzz 工具处理 bitmap) | 自动生成 .gcov 报告文件 |
共享内存需要注意的问题
1) mmap映射文件到内存时必须先预分配大小,ftruncate(fd, MAX_EDGES); 要不然可能报错Aborted (core dumped)
2) 插桩runtime中定义的共享内存的地址、大小与读取覆盖率信息的程序中的必须保证完全一致,要不然可能读写的不是一个东西,导致输出全为0或其他错误。
3) 读取覆盖率信息的程序也可以创建共享内存文件, 可以避免执行先后顺序的问题。如果只读,有可能在SUT还没启动时就去访问共享内存初始化什么的出错。
demo
1 | clang -fsanitize-coverage=trace-pc-guard -g -o target_cov target.c coverage_runtime.c clang control.c -fPIC -shared -o libruntime.so mcs Program.cs -out:peach.exe |