AddressSanitizer

概述

C 和 C++ 语言功能强大,但可能会遇到一类影响程序正确性和程序安全性的 bug。 从 Visual Studio 2019 版本 16.9 开始,Microsoft C/C++ 编译器 (MSVC) 和 IDE 支持AddressSanitizer清理器。 AddressSanitizer (ASan) 是一种编译器和运行时技术,它公开了许多误报率为零的难以发现的 bug:

使用 AddressSanitizer 减少在以下方面花费的时间:

  • 基本正确性
  • 跨平台可移植性
  • 安全性
  • 压力测试
  • 集成新代码

AddressSanitizer 最初由 Google 引入,它提供运行时 bug 查找技术,这些技术可直接使用现有的生成系统和现有的测试资产。

AddressSanitizer 与 Visual Studio 项目系统、CMake 生成系统和 IDE 集成。 项目可以通过设置项目属性或使用一个额外的编译器选项 /fsanitize=address 来启用 AddressSanitizer。 这个新选项与 x86 和 x64 的所有优化和配置级别兼容。 但是,它与编辑并继续增量链接/RTC不兼容。

从 Visual Studio 2019 版本 16.9 开始,Microsoft 的 AddressSanitizer 技术可实现与 Visual Studio IDE 的集成。 如果擦除器在运行时发现 bug,该功能可以选择创建故障转储文件。 如果在运行程序之前设置了ASAN_SAVE_DUMPS=MyFileName.dmp环境变量,会导致使用额外的元数据创建故障转储文件,以便对精确诊断的 bug 进行高效的事后检查调试。 这些转储文件使 AddressSanitizer 的扩展使用更加容易,以便进行:

  • 本地计算机测试
  • 本地分布式测试
  • 用于测试的基于云的工作流

安装 AddressSanitizer

默认情况下,Visual Studio 安装程序中的 C++ 工作负载安装 AddressSanitizer 库和 IDE 集成。 但是,如果你要从较旧版本的 Visual Studio 2019 升级,请在升级后使用安装程序启用 ASan 支持。 可以通过工具>获取工具和功能...从 Visual Studio 主菜单中打开安装程序.从 Visual Studio 安装程序中选择现有 Visual Studio 安装上的“修改”,进入下面的屏幕。

Visual Studio 安装程序的屏幕截图。突出显示了“可选”部分下的 C++ AddressSanitizer 组件。

注意

如果你在新的更新上运行 Visual Studio,但尚未安装 ASan,当你运行代码时,会出现错误:

LNK1356:找不到库“clang_rt.asan_dynamic-i386.lib”

使用 AddressSanitizer

通过以下任何常见开发方法,开始使用 /fsanitize=address 编译器选项生成可执行文件:

  • 命令行生成
  • Visual Studio 项目系统
  • Visual Studio CMake 集成

重新编译,然后正常运行程序。 此代码生成公开了许多精确诊断的 bug 类型。 这些错误是通过三种方式报告的:在调试程序 IDE 中、在命令行上,或者存储在一个新的转储文件类型中,以便进行精确的离线处理。

Microsoft 建议在以下三个标准工作流中使用 AddressSanitizer:

本文介绍了启用前面列出的三个工作流所需的信息。 此信息特定于 AddressSanitizer 的依赖于平台的 Windows 10 实现。 本文档是对 Google、Apple 和 GCC 已发布的优秀文档的补充。

注意

当前支持仅限于 Windows 10 上的 x86 和 x64。 通过向我们发送反馈,可以告诉我们你希望在将来的版本中看到哪些内容。 你的反馈可帮助我们确定未来的其他擦除器(例如 /fsanitize=thread/fsanitize=leak/fsanitize=memory/fsanitize=undefined/fsanitize=hwaddress)的优先级。 如果你遇到问题,可以在此处报告 bug

从开发人员命令提示符中使用 AddressSanitizer

开发人员命令提示符中使用 /fsanitize=address 编译器选项为 AddressSanitizer 运行时启用编译。 /fsanitize=address 选项与所有现有的 C++ 或 C 优化级别(例如 /Od/O1/O2/O2 /GLPGO)兼容。 此选项适用于静态和动态 CRT(例如 /MD/MDd/MT/MTd)。 无论你是创建 EXE 还是 DLL,它都有效。 若要对调用堆栈进行最佳格式设置,需要调试信息。 在下面的示例中,在命令行上传递了cl /fsanitize=address /Zi

为你自动链接了 AddressSanitizer 库(.lib 文件)。 有关详细信息,请参阅 AddressSanitizer 语言、生成和调试参考

示例 - 基本全局缓冲区溢出

// basic-global-overflow.cpp
#include <stdio.h>
int x[100];
int main() {
    printf("Hello!\n");
    x[100] = 5; // Boom!
    return 0;
}

在适用于 Visual Studio 2019 的开发人员命令提示符中,使用 /fsanitize=address /Zi 编译 main.cpp

命令提示符的屏幕截图,其中显示了使用 AddressSanitizer 选项编译的命令。命令为:“cl main.cpp -faanitize-address /Zi”。

当在命令行中运行生成的main.exe时,它会创建以下格式的错误报告。

看一看叠加的红色框,其中突出显示了七个关键信息:

调试程序的屏幕截图,其中显示了基本全局溢出错误。

错误报告中有七个红色突出显示,用于识别信息的关键部分。 它们映射到此屏幕截图后面的编号列表。 编号框突出显示了以下文本:1) global-buffer-overflow 2) 写入大小 4 3) basic-global-overflow.cpp 7 4) 到 'basic-global-overflow.cpp:3:8' 中定义的全局变量“x”5) 400 大小 6) 00 00[f9]f9 f9 7) 框位于阴影字节图例区域中且包含全局红区:f9

红色框中突出显示的内容(从上到下)

  1. 内存安全 bug 是全局缓冲区溢出。
  2. 有 4 个字节(32 位)存储在任何用户定义变量的外部。
  3. 存储发生在文件 basic-global-overflow.cpp 中定义的函数 main() 中(第 7 行)。
  4. 在 basic-global-overflow.cpp 中定义了名为 x 的变量(第 3 行,从第 8 列开始)
  5. 此全局变量 x 的大小为 400 字节
  6. 描述存储目标地址的确切阴影字节的值为 0xf9
  7. 阴影字节图例指出 0xf9int x[100] 右侧的填充区域

注意

调用堆栈中的函数名称是通过在发生错误时运行时调用的 LLVM 符号器生成的。

在 Visual Studio 中使用 AddressSanitizer

AddressSanitizer 与 Visual Studio IDE 集成。 要为 MSBuild 项目启用 AddressSanitizer,请在“解决方案资源管理器”中右击项目,然后选择“属性”。 在“属性页”对话框中,选择“配置属性”>“C/C++”>“常规”,然后修改“启用 AddressSanitizer”属性。 选择“确定”以保存更改 。

“属性页”对话框的屏幕截图,其中显示了“启用 AddressSanitizer”属性。

若要从 IDE 生成,请选择关闭任何不兼容的选项。 对于使用 /Od(或调试模式)编译的现有项目,可能需要关闭以下选项:

要生成并运行调试程序,请按F5。 Visual Studio 中显示引发的异常窗口:

调试程序的屏幕截图,其中显示了全局缓冲区溢出错误。

从 Visual Studio: CMake 使用 AddressSanitizer

要为针对 Windows 创建的 CMake 项目启用 AddressSanitizer,请执行以下步骤:

  1. 在 IDE 顶部的工具栏中打开“配置”下拉列表,然后选择“管理配置”。

    CMake 配置下拉列表的屏幕截图。它显示 x64 调试、x64 发布等选项。在列表底部,管理配置...突出显示。

    这会打开 CMake 项目设置编辑器,该编辑器反映项目CMakeSettings.json文件的内容。

  2. 在编辑器中选择“编辑 JSON”链接。 此选择会将视图切换到原始 JSON。

  3. 将以下代码片段添加到"windows-base"预设,在"configurePresets":中打开地址清理器:

    "environment": {
      "CFLAGS": "/fsanitize=address",
      "CXXFLAGS": "/fsanitize=address"
    }
    

    之后,"configurePresets"如下所示:

        "configurePresets": [
          {
            "name": "windows-base",
            "hidden": true,
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/out/build/${presetName}",
            "installDir": "${sourceDir}/out/install/${presetName}",
            "cacheVariables": {
              "CMAKE_C_COMPILER": "cl.exe",
              "CMAKE_CXX_COMPILER": "cl.exe"
            },
            "condition": {
              "type": "equals",
              "lhs": "${hostSystemName}",
              "rhs": "Windows"
            },
            "environment": {
              "CFLAGS": "/fsanitize=address",
              "CXXFLAGS": "/fsanitize=address"
            }
          },
    
  4. 如果指定了编辑并继续 (/ZI),则地址清理器不起作用,该清理器默认为新的 CMake 项目启用。 在CMakeLists.txt中,注释禁止以set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT"开头的行(前缀为#)。 之后,该行如下所示:

    # set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
    
  5. 输入Ctrl+S以保存此 JSON 文件

  6. 从 Visual Studio 菜单中选择“项目>”“删除缓存并重新配置”,从而清除 CMake 缓存目录并重新配置。 出现提示以清除缓存目录并重新配置后,选择“”。

  7. 将源文件(例如,CMakeProject1.cpp)的内容替换为以下内容:

    // CMakeProject1.cpp : Defines the entry point for the application
    
    #include <stdio.h>
    
    int x[100];
    
    int main()
    {
        printf("Hello!\n");
        x[100] = 5; // Boom!
        return 0;
    }
    
  8. 选择“F5”以重新编译并在调试器下运行。

    此屏幕截图捕获了 CMake 生成中的错误。

    显示:地址清理器错误:全局缓冲区溢出的异常的屏幕截图。在后台,地址清理器输出在命令窗口中可见。

AddressSanitizer 故障转储

我们在 AddressSanitizer 中引入了新功能,用于云和分布式工作流。 此功能允许在 IDE 中离线查看 AddressSanitizer 错误。 该错误会叠加在你的源之上,就像在实时调试会话中遇到的情况一样。

在分析 bug 时,这些新的转储文件可以提高效率。 你无需重新运行、查找远程数据或查找离线的计算机。

生成一种新的转储文件类型,以便以后在另一台计算机上的 Visual Studio 中进行查看:

set ASAN_SAVE_DUMPS=MyFileName.dmp

从 Visual Studio 16.9 开始,可以在源代码之上显示精确诊断的错误,该错误存储在 *.dmp 文件中。

这一新的故障转储功能支持基于云的工作流或分布式测试。 它还可用于在任何场景中提交详细的可操作 bug。

示例错误

AddressSanitizer 可以检测多种内存滥用错误。 下面是当你运行使用 AddressSanitizer (/fsanitize=address) 编译器选项编译的二进制文件时,系统报告的许多运行时错误:

有关示例的详细信息,请参阅 AddressSanitizer 错误示例

与 Clang 12.0 的差异

MSVC 目前在两个功能方面与 Clang 12.0 存在差异:

  • stack-use-after-scope - 此设置默认处于打开状态,且无法关闭。
  • stack-use-after-return - 此功能需要一个额外的编译器选项,无法仅通过设置 ASAN_OPTIONS 来使用。

做出这些决定是为了减少交付此第一个版本所需的测试矩阵。

Visual Studio 2019 16.9 中可能导致误报的功能没有包括在内。 在考虑与几十年的现有代码的互操作时,该规则强制实施了必要的有效测试完整性。 在以后的版本中可能会考虑更多功能:

有关详细信息,请参阅使用 MSVC 生成 AddressSanitizer

现有的行业文档

目前存在大量有关 AddressSanitizer 技术的这些依赖于语言和平台的实现的文档。

有关 AddressSanitizer 的这篇开创性论文介绍了实现。

另请参阅

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