Work
Travel
Various
C++ 运行时检测工具
下面列出推荐使用的运行时检测工具,它们实用、可移植、易于使用,并且适用于多种发行版。
其他检测工具(例如 integer、memory、HWASan)未被列入,因为它们可能不够实用,
或者其实现在不同发行版 / 编译器等环境下可能不受支持,或给出不同的结果。
address: ASan — AddressSanitizer(-fsanitize=address)
用途: 在运行时检测内存安全缺陷。
- 越界读 / 写(堆 / 栈 / 全局内存)
- 释放后使用
- 重复释放 / 非法释放
- 某些返回后使用 / 作用域结束后使用的检测模式(取决于工具链)
典型用途:“快速找出导致崩溃 / 内存损坏的问题。”
典型检测结果
- 堆缓冲区溢出 / 下溢
- 栈缓冲区溢出 / 下溢
- 释放后使用
- 返回后使用 / 作用域结束后使用(取决于编译器选项)
- 重复释放、非法释放,以及某些分配器误用
工作原理(概念层面)
- 对加载 / 存储操作进行插桩,使用影子内存及分配区域周围标记为不可访问的红区,快速捕获越界访问和释放后使用。
注意事项
- 开销:通常中等(运行时间通常为原来的 1.5–3 倍,内存占用更高)。
- 不兼容性:实际使用中,ASan 和 TSan 无法组合使用于同一个二进制文件;应将它们视为独立的构建模式。
- 误报:很少见,但使用自定义分配器 / 底层技巧时,如果没有正确添加注解,可能会出现。
thread: TSan — ThreadSanitizer(-fsanitize=thread)
检测数据竞争及其他并发问题:
- 未同步的并发访问(至少有一个写操作)
- 报告发生竞争时的调用栈及同步上下文
典型用途:“验证线程安全性。” 注意:通常使用独立于 ASan 的构建运行。
用途: 检测数据竞争及某些其他并发缺陷。
典型检测结果
- 数据竞争(未同步的并发访问,至少有一个写操作)
- 互斥量 / 原子操作的误用模式(取决于运行时)
- 有时会出现与死锁相关的诊断信息,但其核心价值在于检测数据竞争。
工作原理
- 对内存访问和同步操作进行插桩,构建先行发生(happens-before)模型,并报告相互冲突的访问。
注意事项
- 开销:显著(运行时间通常为原来的 5–15 倍,内存开销很大)。
- 不兼容性:通常不与 ASan / LSan / UBSan 组合用于同一个构建;应使用专门的“tsan”配置。
- 要有效发挥作用,需要对大部分代码(理想情况下还包括关键的第三方库)进行 TSan 插桩构建。
undefined UBSan — UndefinedBehaviorSanitizer(-fsanitize=undefined 加上子检查项)
检测 C/C++ 中的未定义行为:
- 有符号整数溢出(启用相应检查时)
- 非法移位(移位量为负或过大)
- 未对齐访问
- 非法 vptr(某些模式下)、超出范围的枚举值等
典型用途:“捕获可能不会导致崩溃但确实有错的未定义行为。”
用途: 在运行时检测未定义行为(违反语言 / ABI 规则的行为)。
典型检测结果(示例)
- 有符号整数溢出(启用相应检查时)
- 非法移位(移位量为负或 ≥ 位宽)
- 未对齐的指针解引用
- 某些情况下的空指针问题(取决于选项)
- 非法枚举值、vptr 检查等(取决于编译标志)
工作原理
- 针对特定的未定义行为类别插入检查,并在运行时触发陷阱或报告问题。
注意事项
- “UBSan” 实际上是一套检查项;具体启用哪些检查取决于所用的编译标志。
- 在有意依赖“良性未定义行为”模式的代码库中,某些检查可能会产生较多噪声。
leak LSan — LeakSanitizer(-fsanitize=leak 或 ASan 的泄漏检测)
检测内存泄漏(程序结束时已不可达且未释放的已分配内存)。
典型用途:“防止长时间运行的服务 / 测试悄无声息地泄漏内存。” 在 Linux 上通常作为 ASan 的一部分使用。
用途: 检测内存泄漏(已分配但在程序结束时不可达的对象)。
典型检测结果
- 泄漏的堆内存
- 某些内存分配 API 产生的泄漏(能够拦截这些 API 时)
工作原理
- 在进程退出时(或按请求),从内存根扫描可达内存,并报告从未释放且不可达的已分配内存。
注意事项
- 当程序正常退出,且内存由可拦截的分配器分配时,泄漏检测最为可靠。
- 在 Linux 上通常作为 ASan 工作流的一部分使用;许多团队直接运行启用泄漏检测的 ASan 构建,而非单独构建“仅检测泄漏”的版本。