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),使检测的代码与未检测的代码不兼容。

消毒器所做的主要更改

  • 内存布局:消毒器会添加阴影内存、保护区域或跟踪元数据到您的数据周围

  • 函数调用:标准库函数(mallocfree 等)会被包装或拦截

  • 运行时依赖项:检测的代码需要消毒器运行时库(libasanlibtsan 等)

  • 链接:混合检测的代码和未检测的代码可能导致崩溃、误报或未定义行为

处理外部代码

使用消毒器时,您必须考虑如何处理第三方依赖项。由于混合检测的代码和未检测的代码可能导致问题,因此以下是一些策略

始终需要完全检测

  • 内存消毒器 (MSan):更改函数 ABI 以传递阴影状态。

  • 数据流消毒器 (DFSan):通过将标签参数附加到函数来显式修改 ABI。

  • 线程消毒器 (TSan):更改内存布局并拦截同步原语。有些代码可能无法被线程消毒器检测,但不建议这样做。

通常需要完全检测

  • 地址消毒器 (ASan):添加红区和阴影内存;可以与未检测的代码一起使用,但不建议这样做。

  • 硬件地址消毒器 (HWASan):类似于 ASan,但使用硬件标记。混合是可能的,但不建议这样做。

通常可以与未检测的代码混合

  • 未定义行为消毒器 (UBSan):添加运行时检查以进行未定义行为;ABI 更改最小,更安全地混合。

  • 泄漏消毒器 (LSan):在程序退出时检测内存泄漏;独立运行时,ABI 影响最小。

为了获得可靠的结果,始终使用相同的消毒器配置重新构建整个依赖树。

启用消毒器

Conan 无法自动从设置推断消毒器标志。您必须通过配置文件或工具链传递适当的编译器和链接器标志(例如,-fsanitize=/fsanitize=address)。Conan 工具链(例如,CMakeToolchainMesonToolchain)将传播在 [conf] 部分中定义的标志。

使用设置建模和应用消毒器

如果您想建模消毒器选项,以便受其影响的包 ID,您可以 自定义新的编译器子设置。您不应该直接修改 settings.yml;而是添加 settings_user.yml

这种方法是首选,因为启用消毒器会更改包 ID,允许您使用或不使用消毒器构建和使用相同的二进制包。这对于开发和调试工作流程来说是理想的。

将消毒器作为设置的一部分进行配置

如果您通常在构建中使用特定的消毒器或组合,则可以在 settings_user.yml 中将一个子设置指定为值列表。例如,对于 Clang、GCC 和 MSVC

settings_user.yml
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 设置是一个列表,因此一次只能选择一个值。作为支持同时使用多个消毒器的解决方法,您可以定义诸如 AddressUndefinedBehaviorThreadUndefinedBehavior 等组合,如上所示。您可以定义无限数量的组合,但请记住,这些只是帮助您管理构建的标签。您仍然需要相应地将适当的标志传递给编译器和链接器。

将消毒器作为配置文件的一部分添加

另一种选择是将消毒器值作为配置文件的一部分添加。这样,您可以通过使用专用配置文件轻松地在不同的配置之间切换。

compiler_sanitizers/profiles/gcc_asan
[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),我们可以获得地址消毒器的等效配置文件

compiler_sanitizers/profiles/msvc_asan
[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 内置的工具链(如 CMakeToolchainMesonToolchain)将自动获取在 [conf] 部分中定义的标志并将其应用于构建。

使用自定义 CMake 工具链管理消毒器

除了使用 Conan 配置文件管理消毒器设置外,您还可以使用其他方法。

如果您已经拥有 自定义 CMake 工具链文件 来管理编译器和构建选项,则可以在那里传递启用消毒器的必要标志,而不是配置文件。

cmake/my_toolchain.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 配置文件中指定此工具链文件

profiles/gcc_asan_ubsan
[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 标志。例如

cmake/sanitizer_toolchain.cmake
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 文件以包含新的构建类型

settings_user.yml
build_type: [DebugASan, DebugUBSan, DebugASanUBSan]

然后,在您的 Conan 配置文件中,指定此工具链文件

profiles/gcc_asan_ubsan
[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_OPTIONSUBSAN_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) 或某些运行时库时,某些功能受到限制。