C, C++ 编译器消毒器¶
警告
请勿在生产构建中使用消毒器,尤其是在具有提升权限的二进制文件(例如,SUID)中。消毒器运行时依赖于环境变量,并且可能启用权限升级。仅在开发和测试中使用。
消毒器是强大的运行时工具,可检测诸如
缓冲区溢出(栈/堆),使用已释放内存,双重释放
多线程代码中的数据竞争
内存泄漏
使用未初始化内存
各种未定义行为
GCC、Clang 和 MSVC 等编译器通过编译器和链接器标志支持消毒器。
本页解释了将编译器消毒器集成到您的 Conan 工作流程中的推荐方法。
编译器消毒器支持比较¶
重要
在使用内存消毒器 (MSan) 并且通常在使用线程消毒器 (TSan) 时,始终重新构建所有依赖项,以避免误报/漏报。对于地址消毒器、UBSan 和泄漏消毒器,将检测代码与预构建库混合通常是安全的,但可能会错过这些库中的错误。
每个编译器对各种消毒器的支持程度不同,到目前为止,Clang 的支持最为全面。为了帮助您选择满足需求的正确消毒器和编译器,以下是其中最常见消毒器的摘要
消毒器 |
GCC |
Clang |
MSVC |
备注 |
|---|---|---|---|---|
地址消毒器 (ASan) |
是 |
是 |
是 |
MSVC:支持 x86、x64 和 ARM64 |
线程消毒器 (TSan) |
是 |
是 |
否 |
检测数据竞争 |
内存消毒器 (MSan) |
否 |
是 |
否 |
仅 Clang,需要 -O1 |
未定义行为消毒器 (UBSan) |
是 |
是 |
否 |
各种未定义行为检查 |
泄漏消毒器 (LSan) |
是 |
是 |
否 |
通常与 ASan 集成 |
硬件地址消毒器 (HWASan) |
否 |
是 |
否 |
仅 ARM64,开销低于 ASan |
内核地址消毒器 (KASan) |
是 |
是 |
是 |
MSVC:需要 Windows 11 |
数据流消毒器 (DFSan) |
否 |
是 |
否 |
动态数据流分析 |
控制流完整性 (CFI) |
否 |
是 |
是 |
MSVC:/guard:cf |
除了 MSVC 对消毒器的支持有限之外,它还鼓励社区在 开发者社区 上投票支持新功能。最近,Visual Studio 2026 添加了对 ARM64 架构上地址消毒器的支持。使用 Conan 和 MSVC 时,此支持应该很简单。
此外,您可以考虑每个消毒器的典型用例
地址消毒器 (ASan):内存错误的绝佳默认选择;通常与 UBSan 结合使用以获得更广泛的覆盖范围。
线程消毒器 (TSan):查找多线程代码中的数据竞争。
内存消毒器 (MSan):检测未初始化内存读取(仅 Clang)。需要对所有依赖项进行检测。
泄漏消毒器 (LSan):通常包含在 Clang/GCC 的 ASan 中,可以显式启用。通常用于查找内存泄漏。
未定义行为消毒器 (UBSan):捕获许多未定义行为;通常与 ASan 结合使用。
常见消毒器组合¶
消毒器通常可以组合使用以提供更全面的覆盖范围,但并非所有组合都受每个编译器的支持。以下是一些常见组合及其兼容性,主要用于 GCC 和 Clang
组合 |
GCC |
Clang |
MSVC |
兼容性 |
|---|---|---|---|---|
ASan + UBSan |
是 |
是 |
否 |
最常见的组合 |
TSan + UBSan |
是 |
是 |
否 |
适用于多线程代码 |
ASan + LSan |
是 |
是 |
否 |
LSan 通常默认启用 ASan |
MSan + UBSan |
否 |
是 |
否 |
需要仔细管理依赖项 |
组合说明:
地址消毒器 (ASan)、线程消毒器 (TSan) 和内存消毒器 (MSan) 彼此互斥。
内存消毒器通常需要特殊的标志,例如
-O1、-fno-omit-frame-pointer和完全检测的依赖项;与未检测的代码混合会导致崩溃/误报。
特定于编译器的标志¶
每个编译器需要特定的标志才能启用所需的消毒器。以下是 GCC、Clang 和 MSVC 的最常见消毒器及其对应标志的摘要
消毒器 |
GCC 标志 |
Clang 标志 |
MSVC 标志 |
|---|---|---|---|
地址消毒器 |
-fsanitize=address |
-fsanitize=address |
/fsanitize=address |
线程消毒器 |
-fsanitize=thread |
-fsanitize=thread |
N/A |
内存消毒器 |
N/A |
-fsanitize=memory |
N/A |
未定义行为 |
-fsanitize=undefined |
-fsanitize=undefined |
N/A |
泄漏消毒器 |
-fsanitize=leak |
-fsanitize=leak |
N/A |
看起来有很多选项,但对于 Clang 而言,这些只是其中的一部分。要获取完整列表,请参阅每个编译器的官方文档
二进制兼容性¶
消毒器如何影响您的二进制文件¶
消毒器在编译时对您的代码进行检测,添加运行时检查和元数据。这会更改您的二进制文件的应用程序二进制接口 (ABI),使检测的代码与未检测的代码不兼容。
消毒器所做的主要更改
内存布局:消毒器会添加阴影内存、保护区域或跟踪元数据到您的数据周围
函数调用:标准库函数(
malloc、free等)会被包装或拦截运行时依赖项:检测的代码需要消毒器运行时库(
libasan、libtsan等)链接:混合检测的代码和未检测的代码可能导致崩溃、误报或未定义行为
处理外部代码¶
使用消毒器时,您必须考虑如何处理第三方依赖项。由于混合检测的代码和未检测的代码可能导致问题,因此以下是一些策略
始终需要完全检测
内存消毒器 (MSan):更改函数 ABI 以传递阴影状态。
数据流消毒器 (DFSan):通过将标签参数附加到函数来显式修改 ABI。
线程消毒器 (TSan):更改内存布局并拦截同步原语。有些代码可能无法被线程消毒器检测,但不建议这样做。
通常需要完全检测
地址消毒器 (ASan):添加红区和阴影内存;可以与未检测的代码一起使用,但不建议这样做。
硬件地址消毒器 (HWASan):类似于 ASan,但使用硬件标记。混合是可能的,但不建议这样做。
通常可以与未检测的代码混合
未定义行为消毒器 (UBSan):添加运行时检查以进行未定义行为;ABI 更改最小,更安全地混合。
泄漏消毒器 (LSan):在程序退出时检测内存泄漏;独立运行时,ABI 影响最小。
为了获得可靠的结果,始终使用相同的消毒器配置重新构建整个依赖树。
启用消毒器¶
Conan 无法自动从设置推断消毒器标志。您必须通过配置文件或工具链传递适当的编译器和链接器标志(例如,-fsanitize= 或 /fsanitize=address)。Conan 工具链(例如,CMakeToolchain、MesonToolchain)将传播在 [conf] 部分中定义的标志。
使用设置建模和应用消毒器¶
如果您想建模消毒器选项,以便受其影响的包 ID,您可以 自定义新的编译器子设置。您不应该直接修改 settings.yml;而是添加 settings_user.yml。
这种方法是首选,因为启用消毒器会更改包 ID,允许您使用或不使用消毒器构建和使用相同的二进制包。这对于开发和调试工作流程来说是理想的。
将消毒器作为设置的一部分进行配置¶
如果您通常在构建中使用特定的消毒器或组合,则可以在 settings_user.yml 中将一个子设置指定为值列表。例如,对于 Clang、GCC 和 MSVC
compiler:
clang:
sanitizer: [null, Address, Leak, Thread, Memory, UndefinedBehavior, HardwareAssistanceAddress, KernelAddress, AddressUndefinedBehavior, ThreadUndefinedBehavior]
gcc:
sanitizer: [null, Address, Leak, Thread, UndefinedBehavior, KernelAddress, AddressUndefinedBehavior, ThreadUndefinedBehavior]
msvc:
sanitizer: [null, Address, KernelAddress]
此示例定义了一些常见的消毒器。您可以添加编译器支持的任何消毒器。 null 值表示没有消毒器的构建。上述模型用于 Clang 的 -fsanitize=address、-fsanitize=thread、-fsanitize=memory、-fsanitize=leak、-fsanitize=undefined、-fsanitize=hwaddress、-fsanitize=kernel-address,以及 -fsanitize=address,undefined 和 -fsanitize=thread,undefined 等组合。
由于 sanitizer 设置是一个列表,因此一次只能选择一个值。作为支持同时使用多个消毒器的解决方法,您可以定义诸如 AddressUndefinedBehavior 和 ThreadUndefinedBehavior 等组合,如上所示。您可以定义无限数量的组合,但请记住,这些只是帮助您管理构建的标签。您仍然需要相应地将适当的标志传递给编译器和链接器。
将消毒器作为配置文件的一部分添加¶
另一种选择是将消毒器值作为配置文件的一部分添加。这样,您可以通过使用专用配置文件轻松地在不同的配置之间切换。
[settings]
arch=x86_64
os=Linux
build_type=Debug
compiler=gcc
compiler.cppstd=gnu20
compiler.libcxx=libstdc++11
compiler.version=15
compiler.sanitizer=Address
[conf]
tools.build:cflags+=["-fsanitize=address", "-fno-omit-frame-pointer"]
tools.build:cxxflags+=["-fsanitize=address", "-fno-omit-frame-pointer"]
tools.build:exelinkflags+=["-fsanitize=address"]
tools.build:sharedlinkflags+=["-fsanitize=address"]
[runenv]
ASAN_OPTIONS="halt_on_error=1:detect_leaks=1"
对于 Visual Studio (MSVC),我们可以获得地址消毒器的等效配置文件
[settings]
arch=x86_64
os=Windows
build_type=Debug
compiler=msvc
compiler.version=194
compiler.runtime=dynamic
compiler.runtime_type=Release
compiler.sanitizer=Address
[conf]
tools.build:cxxflags+=["/fsanitize=address", "/Zi"]
tools.build:exelinkflags+=["/fsanitize=address"]
Conan 客户端无法从设置推断必要的标志并在构建过程中自动应用它们。必须将期望的消毒器标志(例如,-fsanitize= 或 /fsanitize=address)作为编译器和链接器标志传递。Conan 内置的工具链(如 CMakeToolchain 和 MesonToolchain)将自动获取在 [conf] 部分中定义的标志并将其应用于构建。
使用自定义 CMake 工具链管理消毒器¶
除了使用 Conan 配置文件管理消毒器设置外,您还可以使用其他方法。
如果您已经拥有 自定义 CMake 工具链文件 来管理编译器和构建选项,则可以在那里传递启用消毒器的必要标志,而不是配置文件。
# Apply to all targets; consider per-target options for finer control
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address,undefined")
然后,在您的 Conan 配置文件中指定此工具链文件
[settings]
arch=x86_64
os=Linux
build_type=Debug
compiler=gcc
compiler.cppstd=gnu20
compiler.libcxx=libstdc++11
compiler.version=15
compiler.sanitizer=AddressUndefinedBehavior
[conf]
tools.cmake.cmaketoolchain:user_toolchain=["<path_to>/cmake/my_toolchain.cmake"]
这样,您可以保留现有的 CMake 工具链文件,并仍然利用 Conan 配置文件来管理其他设置。
请注意,只有当所有依赖项都使用 CMake 构建并且 CMakeToolchain 集成时,此方法才有效。如果您有使用其他构建系统(例如,Meson、Autotools)的依赖项,则这些依赖项将不会收到自定义 CMake 工具链文件中定义的消毒器标志。
将消毒器作为自定义 CMake 构建类型进行管理¶
另一种选择,无需使用自定义 compiler.sanitizer 设置,就是将 sanitizers 定义为自定义 CMake 构建类型。在这种方法中,您通过在 CMake 配置步骤期间选择构建类型来选择 sanitizer 配置。为此,您可以创建一个自定义 CMake 工具链文件,将构建类型映射到 sanitizer 标志。例如
if(CMAKE_BUILD_TYPE STREQUAL "DebugASan")
set(SANITIZER_FLAGS "-g -fsanitize=address -fno-omit-frame-pointer")
elseif(CMAKE_BUILD_TYPE STREQUAL "DebugUBSan")
set(SANITIZER_FLAGS "-g -fsanitize=undefined -fno-omit-frame-pointer")
elseif(CMAKE_BUILD_TYPE STREQUAL "DebugASanUBSan")
set(SANITIZER_FLAGS "-g -fsanitize=address,undefined -fno-omit-frame-pointer")
else()
set(SANITIZER_FLAGS "")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${SANITIZER_FLAGS}")
此外,您需要更新您的 settings_user.yml 文件以包含新的构建类型
build_type: [DebugASan, DebugUBSan, DebugASanUBSan]
然后,在您的 Conan 配置文件中,指定此工具链文件
[settings]
arch=x86_64
os=Linux
build_type=DebugASan
compiler=gcc
compiler.cppstd=gnu20
compiler.libcxx=libstdc++11
compiler.version=15
[conf]
tools.cmake.cmaketoolchain:user_toolchain=["<path_to>/sanitizer_toolchain.cmake"]
请注意,这种方法使用自定义构建类型,因此 Conan 不会自动应用与 Debug 构建类型关联的标准标志。因此,在您的自定义工具链文件中,您还需要定义与所需的基构建类型相对应的标志,例如,与 gcc-like 编译器中的 Debug build_type 匹配的 -g 标志。
使用这种方法,您可以轻松地在不同的 sanitizer 配置和标准构建类型之间切换,同时保留基于构建类型的包 ID 区分。
实际用法示例¶
有关在 Conan 中使用 sanitizers 的实际示例,请参阅示例中的 使用 Sanitizers 构建示例 部分。
其他建议¶
调试信息和优化
对于 ASan/TSan 并且在使用 GCC 或 Clang 时,编译器标志
-O1或-O2通常有效;对于 MSan,建议使用-O1并避免激进的内联优化。-fno-omit-frame-pointer有助于堆栈跟踪。
运行时符号化
可以配置某些 sanitizers 以提供更好的堆栈跟踪和错误报告。这些功能可以通过环境变量启用,例如 GCC 和 Clang 编译器的
ASAN_OPTIONS和UBSAN_OPTIONS。CI 的有用设置
ASAN_OPTIONS=halt_on_error=1:detect_leaks=1:log_path=asan.此配置使 AddressSanitizer 在第一个错误时停止执行,启用泄漏检测,并将输出记录到名为
asan的文件。UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1:log_path=ubsan.此设置指示 UndefinedBehaviorSanitizer 以人类可读的格式打印堆栈跟踪,在第一个错误时停止,并将输出记录到名为
ubsan的文件。
抑制
如果某些已知问题与您的测试无关,您可以创建抑制文件来从 sanitizer 报告中过滤它们。
对于 ASan:
ASAN_OPTIONS=suppressions=asan.supp。创建一个名为
asan.supp的文件,内容如下leak:FreeMyObject
此示例抑制了由
FreeMyObject产生的泄漏报告。对于 UBSan:
UBSAN_OPTIONS=suppressions=ubsan.supp。创建一个名为
ubsan.supp的文件,内容如下signed-integer-overflow IncreaseCounter
此示例抑制了来自
IncreaseCounter的有符号整数溢出报告。
第三方依赖项
混合的已检测/未检测的代码可能导致误报或崩溃,尤其是在使用 MSan 时。
最好使用相同的 sanitizer 构建依赖项,或者将 sanitizers 限制在叶应用程序中。
MSVC 和 Windows 说明
MSVC/Clang-cl 中的 ASan 使用
/fsanitize=address和通过/Zi的 PDB。不支持 32 位目标。KAsan 需要 Windows 11。
在使用全程序优化 (
/GL) 或某些运行时库时,某些功能受到限制。