AddressSanitizer for Windows:x64和调试生成支持

这篇文章最后一次更新是在2021年3月24日,更新了VisualStudio16.9版本的最新信息。

null

特别感谢Jim Radigan和Matthew McGovern为本文提供内容。

注: 此功能现在已普遍可用。首先,看看官方网站 带MSVC的Windows地址消毒器 文档。

去年十月,我们宣布 AddressSanitizer对Windows的支持 作为x86零售MSVC工具集的一部分。这包括在visualstudio中使用AddressSanitizer错误报告的IDE体验,在锚定到捕获错误的源代码的对话框中可视化。AddressSanitizer是由Google设计的,通过识别违规行为来提高程序的内存安全性。它使用源代码行和符号报告精确的错误。最重要的是,它没有报告假阳性。

ASan Exception in Visual Studio

从Visual Studio 2019版本16.7开始,我们 支持x64和x86目标。这两个目标还完全支持调试和完全优化的发布版本。这些编译器生成与AddressSanitizer互操作所需的新代码生成和元数据 运行时。 它们通过所有级别的优化来支持这一新功能。

为了支持IDE和测试工作流,我们加入了一个新的 vcasan.lib。 这个小的静态库创建IDE将解析的元数据,以支持在其子窗格中报告错误。此元数据存储在发现问题时AddressSanitizer终止程序时生成的Crashdump(.dmp)文件中。这些.dmp文件通常用于调试意外程序崩溃的原因,并且可以轻松地与其他人共享,以便在崩溃之前在VisualStudio中重播程序的行为。此功能允许可执行文件利用Windows操作系统特有的快照进程类型。

为了完全支持Windows平台的这些设计目标,我们进行了一些更改,以增强开源运行时,并为社区做出更多贡献。我们已经修复了工具中的错误,这要感谢那些早期采用该工具并构建大型内部系统(如Office)的客户,他们都使用 –fsanitize=address 最后,我们在Windows上的C++开发堆栈上扩展了功能。16.8还有更多。

Windows AddressSanitizer入门

有关如何开始的更多信息,包括为VisualStudio安装必要的组件、启用构建系统集成以及从命令行或IDE运行ASan,请参阅我们以前的博客文章, 带MSVC的Windows地址消毒器 .

16.7中提供的功能

以下是您的代码在16.7版本中可以利用的AddressSanitizer功能(64位和32位):

  • 作用域后的堆栈使用
  • 堆栈缓冲区溢出
  • 堆栈缓冲区下溢
  • 堆缓冲区溢出(无下溢)
  • 释放后堆使用
  • calloc溢出
  • 动态堆栈缓冲区溢出(alloca)
  • 全局溢出(C++源代码)
  • 新删除类型不匹配
  • memcpy参数重叠
  • 分配大小太大
  • 对齐的分配对齐无效
  • 下毒后使用
  • 对象内溢出
  • 初始化命令失败
  • 双重自由
  • 分配-解除分配不匹配

16.8版的功能

16.8版本提供了以下功能:

全局“C”变量 我们已经扩展了16.8链接器来处理C代码中的全局变量场景。回想一下,在C中,可以多次声明一个global,并且每个声明可以具有不同的类型和大小。唯一可行的分配C globals的地方是链接器,它(按照惯例)只在.obj文件中有多个声明时选择最大的大小。我们将为上游LLVM repo提供一个补丁,这样它也可以针对这个新功能。

__declspec(无地址) 对于某些场景,开发人员可能希望不检测整个函数或特定变量。对于这些不寻常的情况,我们扩展了C/C++前端来注释变量和函数。我们将提供16.8更新的确切语法的细节。

自动链接适当的库 在16.7中,开发人员在创建.EXE或.DLL时必须显式地将正确的AddressSanitizer.lib文件添加到链接行。我们在16.8中推出了一个链接器改进,以消除这样做的需要。这将允许开发人员只关注目标CRT是动态的还是静态链接到正在构建的二进制文件。在此之前,请参见CMD部分中的建筑。

功能超越16.8

退货后使用 这需要为每个函数使用两个堆栈帧来生成代码,每个函数具有由AddressSanitizer运行时跟踪的局部变量。为一个函数运行两个堆栈很复杂,就像Clang一样,用户必须选择使用此功能。它速度很慢,但在发现影响可靠性的细微堆栈损坏错误或倾向于安全性的可能的ROP攻击时非常有效。这是通过简单的重新编译实现的。

从命令行生成

我们包括三个步骤:

  1. 向CL命令行添加标志
  2. 将库添加到链接命令行
  3. 将目录添加到路径以支持运行时的错误

我们一直在努力工作,以确保AddressSanitizer工具集和Windows运行时可以与所有现有的编译器和链接器标志一起使用,这样,这项技术就可以在大量的拓扑中应用到复杂的遗留构建系统中。这包括外部开发人员、大型isv和微软内部的大型团队(如Office、Windows和SQL)。

在16.7中,用户必须显式添加将AddressSanitizer运行时链接到其现有应用程序所需的特定.LIB文件。生成.EXE或.DLL所需的AddressSanitizer.LIB文件取决于您选择的CRT:

  • 动态CRT(/MD)
  • 静态CRT(/MT)

注意 : 如果没有指定/MD或/MT标志,则假定为静态CRT(/MT)。这是Windows多年来的默认行为。

CL命令行

  1. 设置u CL_= -fsanitize=address /Zi ,或
  2. 手动添加 -fsanitize=address /Zi 到所有现有的CL命令行

这些标志告诉编译器生成代码并布局堆栈框架,这些框架将与AddressSanitizer运行时互操作。这个 /Zi 标志将确保为优化代码发出调试信息。此信息确保在报告代码中的错误时,stack walker可以打印带有函数名和源代码行号的堆栈帧。

根据您的visualstudio版本,您可能需要执行一个附加步骤,以便在命令行生成中使用MSVC链接器启用地址消毒器体验。

16.9预览版3及更高版本

从16.9 Preview 3开始,fsanitize=address编译器标志自动表示下面指定的库,您不需要手动在链接行上指定其他库。如果您使用的是旧版本,那么还必须执行下面适合您的场景的附加步骤。

16.9预览版2及更早版本

AddressSanitizer运行时将“钩住”VC++运行时中的许多入口点。例如,AddressSanitizer运行时需要将malloc和free临时定向到AddressSanitizer运行时,以跟踪堆分配并通过原始CRT主体返回。这意味着CRT入口点决定需要显式链接到正在生成的二进制文件的AddressSanitizer.LIB文件。假设您的安装位置缓存在具有以下内容的环境变量MyVS中: set MyVS= C:Program Files (x86)Microsoft Visual Studio2019PreviewVCToolsMSVC14.27.29109

构建与动态CRT链接的x86.EXE:

set _LINK_= /debug -incremental:no /wholearchive:%MyVS%libx86clang_rt.asan_dynamic-i386.lib /wholearchive:%MyVS%libx86clang_rt.asan_dynamic_runtime_thunk-i386.lib

构建与静态CRT链接的x86.EXE:

set _LINK_= /debug -incremental:no /wholearchive:%MyVS%libx86clang_rt.asan-i386.lib /wholearchive:%MyVS%libx86clang_rt.asan_cxx-i386.lib

生成与动态CRT链接的X64.EXE:

set _LINK_= /debug -incremental:no /wholearchive: %MyVS%libx64clang_rt.asan_dynamic-x86_64.lib /wholearchive: %MyVS%libx64clang_rt.asan_dynamic_runtime_thunk-x86_64.lib

生成与静态CRT链接的x64.EXE:

set _LINK_= /debug -incremental:no /wholearchive:%MyVS%libx64clang_rt.asan-x86_64.lib /wholearchive:%MyVS%libx64clang_rt.asan_cxx-x86_64.lib

正在构建与动态CRT链接的DLL:

将EXE和DLL链接到: set _LINK_= /debug -incremental:no /wholearchive:%MyVS%lib{arch}clang_rt.asan_dynamic-{arch}.lib /wholearchive:%MyVS%lib{arch}clang_rt.asan_dynamic_runtime_thunk-{arch}.lib

正在构建与静态CRT链接的DLL:

将EXE链接到: set _LINK_= /debug -incremental:no /wholearchive:%MyVS%lib{arch}clang_rt.asan-{arch}.lib /wholearchive:%MyVS%lib{arch}clang_rt.asan_cxx-{arch}.lib

将DLL链接到: set _LINK_= /debug -incremental:no /wholearchive:%MyVS%lib{arch}clang_rt.asan_dll_thunk-{arch}.lib

Symboler–运行应用程序

运行用编译的应用程序时 –fsanitize=address ,确保AddressSanitizer运行时可以找到它的“符号器” llvm-symboler.exe . 这将遍历失败程序中的堆栈,并打印活动函数名和行号,作为详细诊断错误消息的一部分。

符号器位于编译器和链接器的默认目录中。所以,跑步时:

  • 在x86上,设置 path=%path%; %MyVS%inHostx64x86
  • 在X64上,设置 path=%path%; %MyVS%inHostx64x64

Vcasan.lib–实验性

我们添加了一个新的静态库,在从IDE和项目系统构建AddressSanitizer时自动使用它。对于命令行生成,根据您的visualstudio版本,可能需要额外的步骤。

16.9预览版3及更高版本

从16.9 Preview 3开始,vcasan.lib会自动链接到命令行生成中。你不需要做任何额外的工作来使用它,

16.9预览版2及更早版本

对于旧版本的visualstudio,如果是从命令行生成,则必须手动将此库链接到要生成的二进制文件。该库可用于捕获磁盘故障,以便在VisualStudio中脱机查看。这有助于使用AddressSanitizer的自动化测试系统和基于云的工作流。

对于16.7 ,将vcasan.lib链接到可执行文件后,可以设置环境变量:

  • set ASAN_SAVE_DUMPS=”MyFileName.dmpx”

当AddressSanitizer捕捉到错误时,这将保存快照文件。保存在转储文件中的元数据由新的VisualStudioIDE解析。您可以在每个测试的基础上设置这个变量,并存储这些二进制工件,然后在IDE中通过适当的源索引查看这些工件。

已知问题

对齐

对于手动对齐的局部变量,报告的误报存在已知问题:

  • _declspec(align(n)
  • #pragma align(n)
  • #pragma pack(n)

要提供一些见解,请考虑 __declspec(align(32)) . 对象的大小用于计算数组中的偏移量以及使用指针时的偏移量,因此 sizeof(x) 必须始终是对齐值的倍数。在这种情况下,1 x 32。

但如果你有 __declspec(align(32)) struct aType {int a[12]; }; ,则大小为2 x 32=64,因为 sizeof(a) 是12 x 4=48。如果我们把它改成4,8或16,它就是48。

编译器需要生成的代码(对于所有对齐方案)可能会变得复杂,这是我们正在完成的扩展压力测试领域。同时,此区域中的错误将导致误报。

调试-异常(在16.9 Preview 3和更早版本上)

在版本16.9 Preview 3或更低版本上,请参阅以下说明。对于Preview 4以后的版本,不需要执行这些操作。

一旦你建立了一个可执行文件 -fsanitize=address ,您可能希望在调试器下运行应用程序。刚刚链接到应用程序的地址消毒器运行时将开始在虚拟空间中分页。虚拟空间不是静态地保留大量可能未使用的内存,而是通过异常进行扩展。

可以从命令行使用调试器,如下所示:

devenv.exe /debug my.exe

调试会话将遇到这些异常。当存在访问冲突时,需要禁用Win32异常以进行中断。

在Visual Studio中,使用 调试>窗口>异常设置 打开 异常设置 窗口并取消选中 0xc0000005 复选框如下:

Image exception settings

调试–源行号(在16.9 Preview 3及更早版本上)

在版本16.9 Preview 3或更低版本上,请参阅以下说明。对于Preview 4以后的版本,不需要执行这些操作。

对于编译器为检测应用程序而注入的合成代码,保持行号正确是一个已知的问题。 单步执行时,光标可能会偶尔跳转,然后返回到源代码。 这是一个正在修复的错误。

其他限制

请注意体验中的以下附加限制:

  • 目前不完全支持本机/托管互操作性。与地址更新器运行时链接的C/C++二进制文件可能会遇到不同域中堆分配的问题。
  • 使用优化和内联编译时,可以关闭行号和列信息。低于 /O2 在上面,我们积极地转换程序并失去与线性源代码的紧密关联。如果需要,您可以随时编译 /Od 获取正确的源映射。
  • 优化- /Od 与。 /O2 与。 /LTCG . 各种级别的优化都试图在内联时尽可能多地保留寄存器中的操作数,而不会造成过多的代码大小膨胀。编译器只在下列情况下插入内存引用 –fsanitize=address 添加到命令行。如果您正在编译相同的代码 /Od ,那么 /O2 ,然后 /O2 /LTCG ,加载和存储可能已优化或从不同的功能中移出。如果优化器成功,那么使用 /Od 编译时可能看不到 /O2 . 同样,报告的内容 /O2 编译时可能看不到 /O2 /LTCG .
  • 加载生成的DLL –fsanitize=address 并运行未编译的EXE –fsanitize=address 不支持。即使从main调用LoadLibary也会导致误报。
  • 仅仅调用LoadLibrary而不链接AddressSanitizer运行时的导入库和运行时(参见上面的Building from CMD)将导致误报错误。

致谢

我们要感谢克斯特亚·塞雷巴尼、里德·克莱克纳、维塔利·布卡、马丁·斯托斯ö, 以及LLVM开发人员社区的其他成员的意见,并继续致力于LLVM和所有其他消毒剂。

如果你想了解更多关于AddressSanitizer的信息,Google已经发布了 概述 关于算法和实现。他们的文档还详细介绍了各种AddressSanitizer运行时选项,这些选项可以通过 ASANU选项 环境变量。这些函数用于AddressSanitizer的CLANG和MSVC实现,因为它们共享一组公共的运行时库。还有,看看 关于消毒剂的原始文件 .

我们需要你的反馈!

您的反馈是我们在visualstudio和MSVC工具集中提供良好体验的关键。我们很想让你试试最新的 Visual Studio 2019预览版 并让我们知道它是如何为您工作,无论是在下面的评论或通过电子邮件。如果您在体验中遇到问题或有改进建议,请报告问题或通过开发者社区联系。你也可以在Twitter上找到我们 @视觉 .

附录-高级用户

本文的其余部分包含了更多关于ASan在Windows上的高级使用场景的详细信息,以帮助开发人员了解可用的支持级别。

Windows堆拦截功能

ASan运行时截获一组特定的分配和内存管理函数。 HeapAlloc , GlobalAlloc , LocalAlloc ,和 RtlAllocateHeap 加上相应的 Free , Size ,和 ReAlloc 支持函数。用户可以通过添加 windows_hook_rtl_allocators=true 在执行其程序之前,将其添加到ASANu OPTIONS环境变量。

作为可用性注意事项,我们建议设置 ASAN_OPTIONS=windows_hook_rtl_allocators=true 在启动visualstudio之前在用户范围内,确保环境选项通过其他程序配置更改保持不变。

此时不支持所有堆功能标志:

  • HEAP_ZERO_MEMORY 所有Windows堆分配、释放和重新分配都支持
  • HEAP_NO_SERIALIZE 任何Windows堆函数都不支持
  • HEAP_GENERATE_EXCEPTIONS 任何Windows堆函数都不支持
  • HEAP_REALLOC_IN_PLACE_ONLY 不支持任何Windows堆重新分配

在不支持分配标志的情况下,AddressSanitizer运行时将把分配转移到原始的Windows堆分配器以保留程序执行。如果在Windows堆函数中使用这些不受支持的堆标志的代码中发生内存损坏,则可能会导致误报。

重载新建/删除

AddressSanitizer运行时重载 asan_cxx C++运行时。如果用户还重载了这些库函数,则可能会错过错误报告,因为运行时不再拦截和跟踪分配。

未能使整套设备过载 new/delete/new[]/delete[] 在加载AddressSanitizer运行时也可能导致不一致。这种情况可能导致误报错误。如果代码无法避免重载全局new/delete操作符,则可能需要延迟使用AddressSanitizer,直到运行时解决方案就绪。

自定义分配器

用户可以通过手动投毒/取消投毒阴影内存来启发其分配器。 有关此过程的详细信息,请 查看文档 . 一定要去 #include sanitizers/asan_interface.h 访问用户API以读取和写入卷影字节。

调试库

  • Windows调试运行时在编译时启用了其他断言和预处理器定义。即使在没有C++二进制文件的情况下,这些情况也会导致额外的调试断点和断言被抛出。 –fsanitize=address . 用户可以继续通过这些断点,但请报告在测试ASan时遇到的任何运行时断言。
  • 使用静态调试运行时编译可能需要使用 /force:multiple 在连接阶段。虽然malloc和free被设计成由用户重载, malloc_dbg free_dbg 不会过载。链接器将抛出“多符号定义”错误。是的/force:multiple’开关将强制链接器忽略此警告并使用AddressSanitizer函数定义。会有一些警告输出,详细说明选择了哪个版本的函数。此输出可用于验证 [malloc|free|msize|...]_dbg 已正确选择一组CRT存储器功能。

X64支持

首次机会AV例外

x64windows上的AddressSanitizer运行时使用按需映射方案,该方案使用异常处理程序映射阴影内存。这是目前在开源版本和我们的visualstudio提供的版本。调试时,这些异常将显示为首次访问冲突异常,代码为0xC0000005。将会有相当数量的异常,但是这些第一次出现的异常可以被安全地忽略或禁用。Windbg用户可以使用 sxd av 忽略第一次访问冲突异常。VisualStudio用户可以选择在第一次出现异常时忽略异常类型。运行时旨在捕获任何实际的访问冲突,并在它们发生之前报告它们。

注: x86不使用此内存模型。x86版本只是在早期程序初始化期间将200MB的进程内存映射为0x30000000,以用作阴影内存区域。

全局报告和x64调试断言

x86->x64交叉编译器当前可能会导致全局变量出现问题。此问题将导致程序初始化时AddressSanitizer运行时出现断言。如果遇到此错误,请设置 PreferredToolArchitecture=x64 强制VisualStudio使用x64本机工具集。

问题抑制

使用关闭消毒仪器 _declspec(no_sanitize_address) 目前在16.7中不可用。运行时有几种方法来处理已知的或“故意”的内存冲突。抑制文件可以在单个函数中跳过对bug的报告。详情见 https://clang.llvm.org/docs/AddressSanitizer.html#issue-压制 .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享