LLVM入门教学——SanitizerCoverage插桩(自定义桩代码+Win)

1、介绍

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

2、插桩并计算覆盖率

  • 准备一个test.c文件,计算该程序运行时的覆盖率。
    • #include <stdio.h>
      
      int main(int argc, char *argv[])
      {
          int a, b;
          char op;
          int result;
          scanf("%d%c%d", &a, &op, &b);
          switch (op) {
              case '+':
                  result = a + b;
                  break;
              case '-':
                  result = a - b;
                  break;
              case '*':
                  result = a * b;
                  break;
              case '/':
                  result = a / b;
                  break;
              default:
                  return 1;
          }
          printf("%d\n", result);
          return 0;
      }
  • 自定义一个插桩文件,放在同一目录下。
    • // trace-pc-guard-cb.c
      #include <stdint.h>
      #include <stdio.h>
      #include <sanitizer/coverage_interface.h>
      
      // 这个回调由编译器作为模块构造函数插入到每个DSO中。
      // 'start' 和 'stop' 对应于整个二进制(可执行文件或DSO)中保护区域的起始和结束。
      // 这个回调将至少被每个DSO调用一次,并且可能会用相同的参数被多次调用。
      void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                               uint32_t *stop) {
        static uint64_t N;  // 保护的计数器。
        if (start == stop || *start) return;  // 只初始化一次。
        printf("INIT: %p %p\n", start, stop);
        for (uint32_t *x = start; x < stop; x++)
          *x = ++N;  // 保护应该从1开始。
      }
      
      // 这个回调由编译器在控制流的每个边上插入(应用了一些优化)。
      // 通常,编译器会生成这样的代码:
      //    if(*guard)
      //      __sanitizer_cov_trace_pc_guard(guard);
      // 但是对于大型函数,它会生成一个简单的调用:
      //    __sanitizer_cov_trace_pc_guard(guard);
      void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
        if (!*guard) return;  // 复制保护检查。
        // 如果将 *guard 设置为 0,这段代码将不会再次被该边调用。
        // 现在你可以获取PC并做任何你想做的事:
        //   将其存储在某处或立即符号化并打印。
        // `*guard` 的值是你在
        // __sanitizer_cov_trace_pc_guard_init 中设置的,因此你可以使它们连续
        // 并使用它们来解除数组或位向量的引用。
        void *PC = __builtin_return_address(0);
        char PcDescr[1024];
        // 这个函数是sanitizer运行时的一部分。
        // 要使用它,请链接AddressSanitizer或其他sanitizer。
        __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
        printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
      }
  • 编译运行:
    • clang -g -fsanitize-coverage=trace-pc-guard test.c -c
      clang trace-pc-guard-cb.c test.o -fsanitize=address -o test.exe
      test.exe
    • -fsanitize-coverage=trace-pc-guard:对边进行插桩。(默认:edge)
    • -fsanitize-coverage=func,trace-pc-guard:对每个函数插桩。
    • -fsanitize-coverage=bb,no-prune,trace-pc-guard:对基本块插桩。
  • 运行结果:
    • INIT: 001e32d8 1e32f8:表示保护区域的内存范围。
    • guard: 001e32d8 1 PC x1d180d:guard地址指向当前保护值的内存位置;1为该保护点的唯一标识;PC表示当前程序计数器的地址。
  • 【结果分析】
    • 0x009832d8到0x009832f8之间的字节数:
      • 0x009832f8 - 0x009832d8 = 0x20
      • guard(插入的点)个数 = 0x20 / 4 = 8(每个guard变量占用4个字节)
    • 所以SanitizerCoverage在源代码中一共插入了8个点。
    • 分析运行结果,在没有输入数据前,执行了两个点(不同的)。输入数据后,又执行了两个点(不同的),所以一共执行了4个点。
  • 覆盖率计算
    • 假设总共有N个guard变量,而运行时触发了M个不同的guard变量,那么覆盖率可以计算为:
    • 覆盖率 = (𝑀 / 𝑁) × 100% = 4 / 8 = 50%
  • 插桩位置:
    • #include <stdio.h>
      
      // 1 函数入口
      int main(int argc, char *argv[])
      {
          int a, b;
          char op;
          int result;
          // 2 条件分支
          scanf("%d%c%d", &a, &op, &b);
          switch (op) {
              // 3 case '+'
              case '+':
                  result = a + b;
                  break;
              // 4 case '-'
              case '-':
                  result = a - b;
                  break;
              // 5 case '*'
              case '*':
                  result = a * b;
                  break;
              // 6 case '/'
              case '/':
                  result = a / b;
                  break;
              default:
                  // 7 return 1 函数出口
                  return 1;
          } 
          printf("%d\n", result);
          // 8 return 0 函数出口
          return 0;
      }
  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恣睢s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值