AddressSanitizer 语言、生成和调试参考

本文中的各节介绍 AddressSanitizer 语言规范、编译器选项和链接器选项。 它们还介绍控制特定于 AddressSanitizer 的 Visual Studio 调试程序集成的选项。

有关 AddressSanitizer 运行时的详细信息,请参阅运行时参考。 它包含有关截获的函数以及如何挂钩自定义分配器的信息。 有关从 AddressSanitizer 故障保存故障转储的详细信息,请参阅故障转储参考

语言规范

__SANITIZE_ADDRESS__

在设置 /fsanitize=address 时,__SANITIZE_ADDRESS__ 预处理器宏定义为 1。 此宏可供高级用户用于有条件地为 AddressSanitizer 运行时的存在指定源代码。

#include <cstdio>

int main() {
    #ifdef __SANITIZE_ADDRESS__
        printf("Address sanitizer enabled");
    #else
        printf("Address sanitizer not enabled");
    #endif
    return 1;
}

__declspec(no_sanitize_address)

__declspec(no_sanitize_address) 说明符可用于选择性地对函数、局部变量或全局变量禁用擦除器。 此 __declspec 映像编译器行为,而不影响运行时行为

__declspec(no_sanitize_address)
void test1() {
    int x[100];
    x[100] = 5; // ASan exception not caught
}

void test2() {
    __declspec(no_sanitize_address) int x[100];
    x[100] = 5; // ASan exception not caught
}

__declspec(no_sanitize_address) int g[100];
void test3() {
    g[100] = 5; // ASan exception not caught
}

编译器

/fsanitize=address 编译器选项

/fsanitize=address 编译器选项检测代码中的内存引用,以在运行时捕获内存安全错误。 检测会挂钩加载、存储、范围、alloca 和 CRT 函数。 它可以检测隐藏 bug,例如超出边界、释放后使用、限定范围后使用等。 有关在运行时检测到的错误的非详尽列表,请参阅 AddressSanitizer 错误示例

/fsanitize=address 与所有现有的 C++ 或 C 优化级别(例如 /Od/O1/O2/O2 /GL 以及按配置文件优化)兼容。 使用此选项生成的代码适用于静态和动态 CRT(例如,/MD/MDd/MT/MTd)。 此编译器选项可用于创建面向 x86 或 x64 的 .EXE 或 .DLL。 若要对调用堆栈进行最佳格式设置,需要调试信息。

有关演示多种错误检测类型的代码示例,请参阅 AddressSanitizer 错误示例

/fsanitize=fuzzer 编译器选项(试验)

/fsanitize=fuzzer 编译器选项将 LibFuzzer 添加到默认库列表。 它还设置以下擦除器覆盖率选项:

建议将 /fsanitize=address/fsanitize=fuzzer 配合使用。

指定 /fsanitize=fuzzer 时,这些库会添加到默认库列表:

运行时选项 LibFuzzer 库
/MT clang_rt.fuzzer_MT-{arch}
/MD clang_rt.fuzzer_MD-{arch}
/MTd clang_rt.fuzzer_MTd-{arch}
/MDd clang_rt.fuzzer_MDd-{arch}

还可使用省略 main 函数的 LibFuzzer 库。 使用这些库时,由你负责定义 main 并调用 LLVMFuzzerInitializeLLVMFuzzerTestOneInput。 若要使用这些库之一,请指定 /NODEFAULTLIB 并与对应于运行时和体系结构的以下库显式链接:

运行时选项 LibFuzzer no_main 库
/MT clang_rt.fuzzer_no_main_MT-{arch}
/MD clang_rt.fuzzer_no_main_MD-{arch}
/MTd clang_rt.fuzzer_no_main_MTd-{arch}
/MDd clang_rt.fuzzer_no_main_MDd-{arch}

如果指定了 /NODEFAULTLIB 且未指定其中一个库,则会遇到未解析外部符号链接错误。

/fsanitize-address-use-after-return 编译器选项(试验)

默认情况下,MSVC 编译器(与 Clang 不同)不会生成代码来分配堆中的帧以捕获返回后使用错误。 若要使用 AddressSanitizer 捕获这些错误,必须:

  1. 使用 /fsanitize-address-use-after-return 选项进行编译。
  2. 执行程序之前,运行 set ASAN_OPTIONS=detect_stack_use_after_return=1 以设置运行时检查选项。

将局部变量视为“获取的地址”时,/fsanitize-address-use-after-return 选项会使编译器生成代码以在堆中使用双堆栈帧。此代码比单独使用 /fsanitize=address 要慢得多。 有关详细信息及示例,请参阅错误:stack-use-after-return

堆中的双堆栈帧会在从创建它的函数返回后保留。 请考虑在返回后使用局部变量(分配给堆中的槽)的地址的示例。 与虚设堆帧关联的阴影字节包含值 0xF9。 当运行时报告错误时,0xF9 表示返回后使用堆栈错误。

堆栈帧在堆中进行分配,并在函数返回后保留。 运行时使用垃圾回收在特定时间间隔后异步释放这些虚设调用帧对象。 局部变量的地址会传输到堆中的永久性帧。 这是系统如何检测在定义函数返回后何时使用任何局部变量。 有关详细信息,请参阅 Google 记录的用于在返回后使用堆栈的算法

链接器

/INFERASANLIBS[:NO] 链接器选项

/fsanitize=address 编译器选项会标记对象以指定要链接到可执行文件的哪个 AddressSanitizer 库。 该库的名称以 clang_rt.asan* 开头。 /INFERASANLIBS 链接器选项(默认情况下)会自动从其默认位置链接这些库。 下面是选择并自动链接的库。

注意

在下表中,{arch}i386x86_64。 这些库使用体系结构名称的 Clang 约定。 MSVC 约定通常是 x86x64,而不是 i386x86_64。 它们引用相同的体系结构。

CRT 选项 AddressSanitizer 运行时库 (.lib) 地址运行时二进制文件 (.dll)
/MT/MTd clang_rt.asan_dynamic-{arch}clang_rt.asan_static_runtime_thunk-{arch} clang_rt.asan_dynamic-{arch}
/MD/MDd clang_rt.asan_dynamic-{arch}clang_rt.asan_dynamic_runtime_thunk-{arch} clang_rt.asan_dynamic-{arch}

链接器选项 /INFERASANLIBS:NO 会阻止链接器从默认位置链接 clang_rt.asan* 库文件。 如果使用此选项,请在生成脚本中添加库路径。 否则,链接器会报告未解析外部符号错误。

早期版本

在 Visual Studio 17.7 预览版 3 之前,静态链接的生成(/MT/MTd)不使用 DLL 依赖项。 与之相反,AddressSanitizer 运行时是静态链接到用户的 EXE 中的。 然后,DLL 项目将从用户的 EXE 加载导出以访问 ASan 功能。 此外,动态链接的项目(/MD/MTd)使用不同的库和 DLL,具体取决于是否为调试或发布配置了项目。 有关这些更改及其动机的详细信息,请参阅 MSVC 地址擦除系统 – 一个 DLL 适用于所有运行时配置

CRT 运行时选项 DLL 或 EXE AddressSanitizer 运行时库
/MT EXE clang_rt.asan-{arch}clang_rt.asan_cxx-{arch}
/MT DLL clang_rt.asan_dll_thunk-{arch}
/MD 任一个 clang_rt.asan_dynamic-{arch}clang_rt.asan_dynamic_runtime_thunk-{arch}
/MTd EXE clang_rt.asan_dbg-{arch}clang_rt.asan_dbg_cxx-{arch}
/MTd DLL clang_rt.asan_dbg_dll_thunk-{arch}
/MDd 任一个 clang_rt.asan_dbg_dynamic-{arch}clang_rt.asan_dbg_dynamic_runtime_thunk-{arch}

Visual Studio 集成

/fno-sanitize-address-vcasan-lib 编译器选项

/fsanitize=address 选项在额外库中链接,以便在引发 AddressSanitizer 异常时改进 Visual Studio 调试体验。 这些库称为 VCAsan。 这些库使 Visual Studio 能够显示源代码中的 AddressSanitizer 错误。 它们还使可执行文件能够在创建 AddressSanitizer 错误报告时生成故障转储。 有关详细信息,请参阅 Visual Studio AddressSanitizer 扩展功能库

选择的库取决于编译器选项,会自动链接。

运行时选项 VCAsan 版本
/MT libvcasan.lib
/MD vcasan.lib
/MTd libvcasand.lib
/MDd vcasand.lib

但是,如果使用 /Zl(省略默认库名称)进行编译,则必须手动指定库。 如果未手动指定,则会收到未解析外部符号链接错误。 以下是一些典型的示例:

error LNK2001: unresolved external symbol __you_must_link_with_VCAsan_lib
error LNK2001: unresolved external symbol ___you_must_link_with_VCAsan_lib

使用 /fno-sanitize-address-vcasan-lib 选项可以在编译时禁用改进调试。

ASAN_VCASAN_DEBUGGING 环境变量

/fsanitize=address 编译器选项会生成一个二进制文件,用于在运行时公开内存安全 bug。 从命令行启动该二进制文件并且运行时报告错误时,它会打印错误详细信息。 它随后会退出进程。 ASAN_VCASAN_DEBUGGING 环境变量可以设置为在运行时报告错误时立即启动 Visual Studio IDE。 通过此编译器选项,可以在导致错误的精确行和列上查看错误(叠加在源代码之上)。

若要启用此行为,请在运行应用程序之前运行命令 set ASAN_VCASAN_DEBUGGING=1。 可以通过运行 set ASAN_VCASAN_DEBUGGING=0 来禁用增强调试体验。

另请参阅

AddressSanitizer 概述
AddressSanitizer 已知问题
AddressSanitizer 运行时参考
AddressSanitizer 阴影字节
AddressSanitizer 云或分布式测试
AddressSanitizer 调试程序集成
AddressSanitizer 错误示例