AddressSanitizer
概述
C 和 C++ 语言功能强大,但可能会遇到一类影响程序正确性和程序安全性的 bug。 从 Visual Studio 2019 版本 16.9 开始,Microsoft C/C++ 编译器 (MSVC) 和 IDE 支持AddressSanitizer清理器。 AddressSanitizer (ASan) 是一种编译器和运行时技术,它公开了许多误报率为零的难以发现的 bug:
- alloc/dealloc 不匹配和
new
/delete
类型不匹配 - 分配对堆来说太大
calloc
溢出和alloca
溢出- 重复释放和释放后使用
- 全局变量溢出
- 堆缓冲区溢出
- 对齐值对齐无效
memcpy
和strncat
参数重叠- 堆栈缓冲区溢出和下溢
return
后使用堆栈和限定作用域后使用- 在内存中毒后使用内存
使用 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,但尚未安装 ASan,当你运行代码时,会出现错误:
LNK1356:找不到库“clang_rt.asan_dynamic-i386.lib”
使用 AddressSanitizer
通过以下任何常见开发方法,开始使用 /fsanitize=address
编译器选项生成可执行文件:
- 命令行生成
- Visual Studio 项目系统
- Visual Studio CMake 集成
重新编译,然后正常运行程序。 此代码生成公开了许多精确诊断的 bug 类型。 这些错误是通过三种方式报告的:在调试程序 IDE 中、在命令行上,或者存储在一个新的转储文件类型中,以便进行精确的离线处理。
Microsoft 建议在以下三个标准工作流中使用 AddressSanitizer:
开发人员内部循环
CI/CD - 持续集成/持续开发
- 错误报告 - 新 AddressSanitizer 转储文件
模糊 - 使用 libFuzzer 包装器生成
- Azure OneFuzz
- 本地计算机
本文介绍了启用前面列出的三个工作流所需的信息。 此信息特定于 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
和 PGO
)兼容。 此选项适用于静态和动态 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
当在命令行中运行生成的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
红色框中突出显示的内容(从上到下)
- 内存安全 bug 是全局缓冲区溢出。
- 有 4 个字节(32 位)存储在任何用户定义变量的外部。
- 存储发生在文件
basic-global-overflow.cpp
中定义的函数main()
中(第 7 行)。 - 在 basic-global-overflow.cpp 中定义了名为
x
的变量(第 3 行,从第 8 列开始) - 此全局变量
x
的大小为 400 字节 - 描述存储目标地址的确切阴影字节的值为
0xf9
- 阴影字节图例指出
0xf9
是int x[100]
右侧的填充区域
注意
调用堆栈中的函数名称是通过在发生错误时运行时调用的 LLVM 符号器生成的。
在 Visual Studio 中使用 AddressSanitizer
AddressSanitizer 与 Visual Studio IDE 集成。 要为 MSBuild 项目启用 AddressSanitizer,请在“解决方案资源管理器”中右击项目,然后选择“属性”。 在“属性页”对话框中,选择“配置属性”>“C/C++”>“常规”,然后修改“启用 AddressSanitizer”属性。 选择“确定”以保存更改 。
若要从 IDE 生成,请选择关闭任何不兼容的选项。 对于使用 /Od
(或调试模式)编译的现有项目,可能需要关闭以下选项:
- 关闭编辑并继续
- 关闭
/RTC1
(运行时检查) - 关闭
/INCREMENTAL
(增量链接)
要生成并运行调试程序,请按F5。 Visual Studio 中显示引发的异常窗口:
从 Visual Studio: CMake 使用 AddressSanitizer
要为针对 Windows 创建的 CMake 项目启用 AddressSanitizer,请执行以下步骤:
在 IDE 顶部的工具栏中打开“配置”下拉列表,然后选择“管理配置”。
这会打开 CMake 项目设置编辑器,该编辑器反映项目
CMakeSettings.json
文件的内容。在编辑器中选择“编辑 JSON”链接。 此选择会将视图切换到原始 JSON。
将以下代码片段添加到
"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" } },
如果指定了编辑并继续 (
/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>>")
输入Ctrl+S以保存此 JSON 文件
从 Visual Studio 菜单中选择“项目>”“删除缓存并重新配置”,从而清除 CMake 缓存目录并重新配置。 出现提示以清除缓存目录并重新配置后,选择“是”。
将源文件(例如,
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; }
选择“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
) 编译器选项编译的二进制文件时,系统报告的许多运行时错误:
alloc-dealloc-mismatch
allocation-size-too-big
calloc-overflow
double-free
dynamic-stack-buffer-overflow
global-buffer-overflow
heap-buffer-overflow
heap-use-after-free
invalid-allocation-alignment
memcpy-param-overlap
new-delete-type-mismatch
stack-buffer-overflow
stack-buffer-underflow
stack-use-after-return
stack-use-after-scope
strncat-param-overlap
use-after-poison
有关示例的详细信息,请参阅 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 错误示例