当前位置:   article > 正文

LLVM入门教学——SanitizerCoverage插桩

LLVM入门教学——SanitizerCoverage插桩

1、介绍

  • LLVM 的 SanitizerCoverage 是一种代码覆盖工具,设计用于支持基于 fuzzing 的测试和其他安全相关工具。SanitizerCoverage 在编译时插桩代码,以在运行时收集覆盖信息,从而帮助识别未覆盖的代码路径,提高测试的有效性和全面性。
  • 前提:安装clang即可。
  • 核心功能:
    • 边缘覆盖:插入代码以记录每个基本块的入口和出口,生成边缘覆盖信息。这样可以捕捉到程序执行路径的变化。
    • 基本块覆盖:记录每个基本块的执行情况,生成基本块级别的覆盖信息。
    • 函数覆盖:记录每个函数的调用情况,以函数级别生成覆盖信息。
    • 间接调用覆盖:追踪间接调用(例如通过函数指针)的执行路径。
  • 官方文档:SanitizerCoverage — Clang 19.0.0git 文档 (llvm.org)

2、插桩并计算覆盖率

  • 准备一个test.c文件,计算该程序运行时的覆盖率。
      1. #include <stdio.h>
      2. int main(int argc, char *argv[])
      3. {
      4. int a, b;
      5. char op;
      6. int result;
      7. scanf("%d%c%d", &a, &op, &b);
      8. switch (op) {
      9. case '+':
      10. result = a + b;
      11. break;
      12. case '-':
      13. result = a - b;
      14. break;
      15. case '*':
      16. result = a * b;
      17. break;
      18. case '/':
      19. result = a / b;
      20. break;
      21. default:
      22. return 1;
      23. }
      24. printf("%d\n", result);
      25. return 0;
      26. }
  • 自定义一个插桩文件,放在同一目录下。
      1. // trace-pc-guard-cb.c
      2. #include <stdint.h>
      3. #include <stdio.h>
      4. #include <sanitizer/coverage_interface.h>
      5. // 这个回调由编译器作为模块构造函数插入到每个DSO中。
      6. // 'start' 和 'stop' 对应于整个二进制(可执行文件或DSO)中保护区域的起始和结束。
      7. // 这个回调将至少被每个DSO调用一次,并且可能会用相同的参数被多次调用。
      8. void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
      9. uint32_t *stop) {
      10. static uint64_t N; // 保护的计数器。
      11. if (start == stop || *start) return; // 只初始化一次。
      12. printf("INIT: %p %p\n", start, stop);
      13. for (uint32_t *x = start; x < stop; x++)
      14. *x = ++N; // 保护应该从1开始。
      15. }
      16. // 这个回调由编译器在控制流的每个边上插入(应用了一些优化)。
      17. // 通常,编译器会生成这样的代码:
      18. // if(*guard)
      19. // __sanitizer_cov_trace_pc_guard(guard);
      20. // 但是对于大型函数,它会生成一个简单的调用:
      21. // __sanitizer_cov_trace_pc_guard(guard);
      22. void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
      23. if (!*guard) return; // 复制保护检查。
      24. // 如果将 *guard 设置为 0,这段代码将不会再次被该边调用。
      25. // 现在你可以获取PC并做任何你想做的事:
      26. // 将其存储在某处或立即符号化并打印。
      27. // `*guard` 的值是你在
      28. // __sanitizer_cov_trace_pc_guard_init 中设置的,因此你可以使它们连续
      29. // 并使用它们来解除数组或位向量的引用。
      30. void *PC = __builtin_return_address(0);
      31. char PcDescr[1024];
      32. // 这个函数是sanitizer运行时的一部分。
      33. // 要使用它,请链接AddressSanitizer或其他sanitizer。
      34. __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
      35. printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
      36. }
  • 编译运行:
      1. clang -g -fsanitize-coverage=trace-pc-guard test.c -c
      2. clang trace-pc-guard-cb.c test.o -fsanitize=address -o test.exe
      3. test.exe
    • 【注】trace-pc-guard:用于边缘覆盖。
  • 运行结果:
  • 【结果分析】
    • 0x009832d8到0x009832f8之间的字节数:
      • 0x009832f8 - 0x009832d8 = 0x20
      • guard(插入的点)个数 = 0x20 / 4 = 8(每个guard变量占用4个字节)
    • 所以SanitizerCoverage在源代码中一共插入了8个点。
    • 分析运行结果,在没有输入数据前,执行了两个点(不同的)。输入数据后,又执行了两个点(不同的),所以一共执行了4个点。
  • 覆盖率计算
    • 假设总共有N个guard变量,而运行时触发了M个不同的guard变量,那么覆盖率可以计算为:
    • 覆盖率 = (
      声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/621786
推荐阅读