AddressSanitizer

概述

C 和 C++ 语言非常强大,但可能会遭受一类影响程序正确性和程序安全性的 bug。 从 Visual Studio 2019 2019 版本 16.9 开始,Microsoft C/C++ 编译器 (MSVC) 和 IDE 支持 AddressSanitizer清理器。 AddressSanitizer (ASan) 是一种编译器和运行时技术,它检测出许多没有误报的难以发现的漏洞:

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

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

AddressSanitizer 最初由 Google 引入,它提供运行时错误检测技术,这些技术可直接使用现有的构建系统和现有的测试资源。

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 安装选择 Modify,以进入以下界面。

屏幕截图显示,在“可选”部分中,Visual Studio Installer 的 C++ AddressSanitizer 组件已被突出显示。

注意

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

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

使用 AddressSanitizer

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

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

重新编译,然后正常运行程序。 此代码生成揭示了多种经精确诊断的漏洞类型。 这些错误是通过三种方式报告的:在调试程序 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 /GL)兼容。 此选项适用于静态和动态 CRT(例如 /MD/MDd/MT/MTd)。 无论你是创建 EXE 还是 DLL,它都有效。 若要对调用堆栈进行最佳格式设置,需要调试信息。 在下面的示例中,在命令行上传递了cl /fsanitize=address /Zi

注意

AddressSanitizer 不支持按配置文件引导的优化(PGO)。 不应在生产环境中使用 AddressSanitizer。

为你自动链接了 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

显示命令“cl basic-global-overflow.cpp /fsanitize=address /Zi”的命令提示符的屏幕截图。

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

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

显示基本全局溢出错误的调试器的屏幕截图。

红色突出显示(从上到下)

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

注意

当 AddressSanitizer 报告错误时,ASan 运行时会调用 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 Debug、x64 Release 以及列表底部的 Manage Configurations... 等选项。

    该操作将打开 CMake Project 设置编辑器,该编辑器反映project的 CMakeSettings.json 文件的内容。

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

  3. 若要启用 AddressSanitizer,请在 "configurePresets": 内将以下代码片段添加到 "windows-base" 预设中。

    "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. 如果指定了编辑并继续功能,则 AddressSanitizer 不起作用(新建 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 错误。 错误信息会覆盖在源代码上,就像在实时调试会话中遇到的情况一样。

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

生成一种新的转储文件类型,以便以后在另一台计算机上的 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(外部)的开创性论文描述了其实现。

另请参阅