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 安装上的“修改”,进入下面的屏幕。

Screenshot of the Visual Studio Installer. The C++ AddressSanitizer component, under the Optional section, is highlighted.

注意

如果你在新的更新上运行 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

Screenshot of a command prompt showing the command to compile with AddressSanitizer options. The command is: `cl main.cpp -faanitize-address /Zi`.

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

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

Screenshot of the debugger showing a basic global overflow error.

错误报告中有七个红色突出显示,用于识别信息的关键部分。 它们映射到此屏幕截图后面的编号列表。 编号框突出显示了以下文本: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”属性。 选择“确定”以保存更改 。

Screenshot of the Property Pages dialog showing the Enable AddressSanitizer property.

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

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

Screenshot of the debugger showing a global buffer overflow error.

从 Visual Studio: CMake 使用 AddressSanitizer

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

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

    Screenshot of the CMake configuration dropdown. It displays options like x64 Debug, x64 Release, and so on. At the bottom of the list, Manage Configurations... is highlighted.

    这会打开 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 生成中的错误。

    Screenshot of an exception that says: Address Sanitizer Error: Global buffer overflow. In the background, address sanitizer output is visible in command window.

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 错误示例