C++ 中的运行时检查是用于在程序运行时检测错误、漏洞或意外行为的机制或工具。这些检查在运行时动态执行,而非在编译期进行,可以帮助识别在静态分析或代码审查中可能不明显的问题。
在软件开发的语境中,"sanitizer"(检查器)一词指的就是这类运行时检查。检查器通过为代码插入额外的检查或跟踪机制来工作,以检测常见的编程错误或安全漏洞。当检测到问题时,检查器通常会提供反馈或生成诊断信息,帮助开发者理解和解决问题。
然而,并非所有 C++ 开发者都知道,几乎所有 C++ 编译器都提供原生的运行时检查。Clang、GCC 和微软编译器都提供先进的检查器,以增强您的 C++ 代码的安全性。
微软编译器在调试模式下默认启用这些运行时检查。而对于 Clang 和 GCC,您需要手动启用它们。
以下是一些最流行的检查器列表:
- AddressSanitizer(ASan):AddressSanitizer 检测内存损坏错误,如缓冲区溢出、释放后使用和越界访问。它的工作原理是在程序内存旁边分配一个影子内存区域,并对照该影子内存检查内存访问,以检测违规。
- MemorySanitizer(MSan):MemorySanitizer 检测对未初始化内存的使用,这可能导致未定义行为和安全漏洞。它在运行时跟踪内存位置的初始化状态,并报告任何使用未初始化内存的尝试。
- UndefinedBehaviorSanitizer(UBSan):UndefinedBehaviorSanitizer 检测 C 和 C++ 代码中的未定义行为,如整数溢出、空指针解引用和有符号整数溢出。它对代码进行插桩,以在运行时检测和报告未定义行为的实例。
- ThreadSanitizer(TSan):ThreadSanitizer 检测多线程 C 和 C++ 代码中的数据竞争和其他与并发相关的缺陷。它的工作原理是分析程序的执行,识别不同线程对共享内存的冲突访问。
- DataFlowSanitizer(DFSan):DataFlowSanitizer 通过跟踪数据在程序中的流动并识别涉及受污染数据的潜在危险操作(如 SQL 注入或命令注入),来检测污点型漏洞。
Clang
Clang 提供了一系列选项来自定义运行时检查,具体如此处。
例如:
% clang++ -fsanitize=signed-integer-overflow,null,alignment -fno-sanitize-recover=null -fsanitize-trap=alignment a.cc
程序在有符号整数溢出后将继续执行,在第一次无效使用空指针后退出,并在第一次使用未对齐指针后陷入中断(trap)。
使用 Clang 检查器可以帮助在开发过程的早期识别缺陷和漏洞,避免它们在生产代码中表现为运行时错误或安全漏洞。它们对于发现传统测试方法可能无法捕获的难以诊断的问题尤其有价值。
微软 C++ 编译器
微软编译器也提供了许多运行时检查,具体如此处。
当您调试启用了运行时检查的程序时,默认行为是在发生运行时错误时程序停止并中断到调试器。您可以为任何运行时检查更改此默认行为。有关更多信息,请参阅使用调试器管理异常。
GCC
GCC 支持许多命令行选项,用于控制向其正常生成的代码中添加运行时插桩。例如,插桩的目的之一是收集性能分析统计数据,用于寻找程序热点、代码覆盖率分析或按配置优化。另一类程序插桩是添加运行时检查,以检测无效指针解引用或数组越界访问等编程错误,以及栈溢出攻击或 C++ vtable 劫持等蓄意恶意攻击。检查器的完整列表请见此处。
我们如何让使用 C++ 检查器成为习惯?
- 了解检查器:让您自己和您的团队了解 C++ 中可用的不同类型的检查器,如 AddressSanitizer(ASan)、UndefinedBehaviorSanitizer(UBSan)和 ThreadSanitizer(TSan)。了解每种检查器如何检测代码中特定类型的缺陷和漏洞。
- 将检查器集成到构建流程中:在 C++ 项目的构建过程中启用检查器,使其成为标准做法。修改您的构建脚本或配置文件(例如 CMakeLists.txt),加入启用所需检查器的编译器标志。
- 从小项目开始:先在代码库中一个小型、非关键的项目或模块中引入检查器。这让您可以熟悉整个过程,而不会让自己或团队不堪重负。
- 在测试和调试时启用检查器:养成在启用检查器的情况下运行测试和调试构建的习惯。这有助于在开发周期的早期捕获缺陷,使其更容易、修复成本更低。
- 定期审查检查器输出:关注检查器在构建过程中生成的输出。花时间理解检测到的问题及其根本原因。利用这些反馈来改进您的代码,防止将来出现类似问题。
- 将检查器纳入代码审查:鼓励团队成员在代码审查期间启用检查器。讨论检查器检测到的任何问题,并共同努力解决它们。
- 提供培训与支持:提供培训课程或工作坊,帮助团队成员了解如何有效地使用检查器。根据需要提供了支持和指导,特别是对刚接触检查器的人。
- 记录最佳实践:在您的组织内记录使用检查器的最佳实践。包括何时以及如何启用检查器、如何解读检查器输出,以及如何处理检查器检测到的常见问题的指南。
- 设定目标并跟踪进展:为在团队或组织内提高检查器的使用率设定目标。定期跟踪进展并庆祝里程碑,以强化使用检查器的习惯。
- 持续改进:持续评估和改进您使用检查器的流程。征求团队成员的反馈,并根据吸取的经验教训调整方法。
通过遵循这些步骤,并将使用检查器作为团队内的标准实践,您可以养成有效使用 C++ 检查器的习惯,从而提高代码的质量和安全性。
