AddressSanitizer 运行时库拦截常见的内存分配函数和操作,以检查内存访问。 有几个不同的运行时库可支持编译器可能生成的各种类型的可执行文件。 只要在编译时传递 /fsanitize=address 选项,编译器和链接器就会自动链接相应的运行时库。 可以在链接时使用 /NODEFAULTLIB 选项重写默认行为。 有关详细信息,请参阅有关在 AddressSanitizer 语言、生成和调试参考中进行链接的部分。
编译 cl /fsanitize=address时,编译器会生成用于管理和检查 阴影字节的说明。 程序使用此检测来检查堆栈、堆或全局范围内的内存访问。 编译器还会生成描述堆栈和全局变量的元数据。 此元数据使运行时能够生成精确的错误诊断:源代码中的函数名称、行和列。 将编译器检查和运行时库相结合,可以在运行时遇到多种类型的内存安全 bug 时进行准确诊断。
从 Visual Studio 17.7 预览版 3 开始,用于链接到 AddressSanitizer 运行时的运行时库列表如下所示。 有关(静态链接运行时)和(在运行时动态链接 redist)选项的详细信息/MT,请参阅/LD
Note
在下表中,{arch} 是 i386 或 x86_64。
这些库使用体系结构名称的 Clang 约定。 MSVC 约定通常是 x86 和 x64 ,而不是 i386 和 x86_64,但它们引用相同的体系结构。
| CRT 选项 | AddressSanitizer 运行时库 (.lib) |
地址运行时二进制文件 (.dll) |
|---|---|---|
/MT 或 /MTd |
%> | clang_rt.asan_dynamic-{arch} |
/MD 或 /MDd |
%> | clang_rt.asan_dynamic-{arch} |
下图显示 /MT、/MTd、/MD 和 /MDd 编译器选项下如何链接语言运行时库:
图片显示了三种链接运行时库的方案。 第一种是 /MT 或 /MTd。 My_exe.exe 和 my_dll.dll 都以自己的静态链接 VCRuntime、通用 CRT 和 C++ 运行时的副本显示。 方案显示在 /MD 中 my_exe.exe 和 my_dll.dll 共享 vcruntime140.dll、ucrtbase.dll 和 msvcp140.dll。 最后一种方案显示在 /MDd 中 my_exe.exe 和 my_dll.dll 共享运行时的调试版本:vcruntime140d.dll、ucrtbased.dll 和 msvcp140d.dll
下图显示了不同编译器选项下如何链接 ASan 库:
图片显示了四种链接 ASan 运行时库的方案。 方案适用于 /MT(静态链接运行时)、/MTd(静态链接调试运行时)、/MD(在运行时动态链接 redist)、/MDd(在运行时动态链接调试 redist)。 在所有情况下,my_exe.exe 链接及其关联 my_dll.dll 链接到单个 clang_rt.asan_dynamic-x86_64.dll实例。
与其他 C 运行时组件不同,即使是静态链接,ASan 运行时 DLL 也必须出现在运行时中。
旧版
在 Visual Studio 17.7 预览版 3 之前,静态链接(/MT 或 /MTd)的版本不使用 DLL 依赖项。 与之相反,AddressSanitizer 运行时是静态链接到用户的 EXE 中的。 然后,DLL 项目将从用户的 EXE 加载导出以访问 ASan 功能。
动态链接的项目(/MD 或 /MDd)根据其配置目的为调试或发布而使用不同的库和 DLL。 有关这些更改及其动机的详细信息,请参阅 MSVC AddressSanitizer – 所有运行时配置的一个 DLL。
下表描述了 Visual Studio 17.7 预览版 3 之前 AddressSanitizer 运行时库链接的先前行为:
| CRT 选项 | DLL 或 EXE | DEBUG? | ASan 库 (.lib) |
ASan 运行时二进制 (.dll) |
|---|---|---|---|---|
/MT |
EXE | No | %> | None |
/MT |
DLL | No | clang_rt.asan_dll_thunk-{arch} |
None |
/MD |
Either | No | %> | clang_rt.asan_dynamic-{arch} |
/MT |
EXE | Yes | %> | None |
/MT |
DLL | Yes | clang_rt.asan_dbg_dll_thunk-{arch} |
None |
/MD |
Either | Yes | %> | clang_rt.asan_dbg_dynamic-{arch} |
下图显示了 Visual Studio 2022 17.7 预览版 3 之前,不同编译器选项下如何链接 ASan 库:
图片显示了四种链接 ASan 运行时库的方案。 方案适用于 /MT(静态链接运行时)、/MTd(静态链接调试运行时)、/MD(在运行时动态链接 redist)、/MDd(在运行时动态链接调试 redist)。 对于 /MT,my_exe.exe 具有 ASan 运行时的静态链接副本。 my_dll.dll 链接 my_exe.exe 中的 ASan 运行时。 /MTd 的示意图相同,但是它使用静态链接调试 ASan 运行时。 对于 /MD,my_exe.exe 和 my_dll.dll 都链接到名为 clang_rt.asan_dynamic-x86_64.dll 的动态链接 ASan 运行时上。 /MDd 的示意图相同,但是 my_exe.exe 和 my_dll.dll 链接名为 clang_rt.asan_dbg_dynamic-x86_64.dll 的调试 ASan 运行时。
函数截获
AddressSanitizer 通过多种热修补技术实现函数拦截。 源代码本身对这些技术进行了最好的记录。
运行时库拦截许多常见的内存管理和内存操作函数。 有关列表,请参阅 AddressSanitizer 拦截函数列表。 分配拦截器管理与每个分配调用相关的元数据和影子字节。 每次调用 CRT 函数(例如 malloc 或 delete)时,拦截器都会在 AddressSanitizer 影子内存区域中设置特定值,以指示这些堆位置当前是否可访问以及分配的边界是什么。 这些影子字节允许编译器生成的 卷影字节 检查来确定加载或存储是否有效。
无法保证拦截成功。 如果函数序言太短而无法写入 jmp,则拦截可能会失败。 如果拦截失败,程序将引发 debugbreak 并停止。 如果附加一个调试器,则可以清楚了解拦截问题的原因。 如果遇到此问题,请报告 bug。
Note
用户可以选择尝试在拦截失败后继续执行,方法是将环境变量 ASAN_WIN_CONTINUE_ON_INTERCEPTION_FAILURE 设置为任意值。 在拦截失败后继续执行可能会导致丢失该函数的 bug 报告。
自定义分配器和 AddressSanitizer 运行时
AddressSanitizer 运行时为常见的分配器接口 malloc/free、new/delete、HeapAlloc/HeapFree(通过 RtlAllocateHeap/RtlFreeHeap)提供拦截器。 许多程序会出于某种原因使用自定义分配器,例如使用 dlmalloc 的任何程序或使用 std::allocator 接口和 VirtualAlloc() 的解决方案。 编译器无法自动将影子内存管理调用添加到自定义分配器。 用户有责任使用提供的手动中毒接口。 此 API 使这些分配器能够使用现有的 AddressSanitizer 运行时和 影子字节 约定正常工作。
手动 AddressSanitizer 中毒接口
启蒙的接口很简单,但它对用户施加了对齐限制。 用户可以通过导入 sanitizer/asan_interface.h 来导入这些原型。 以下是接口函数原型:
void __asan_poison_memory_region(void const volatile *addr, size_t size);
void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
为方便起见,AddressSanitizer 接口头文件提供了包装器宏。 这些宏检查在编译期间是否启用了 AddressSanitizer 功能。 它们允许源代码在不需要时省略中毒函数调用。 这些宏应该优先于直接调用上述函数:
#define ASAN_POISON_MEMORY_REGION(addr, size)
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
Note
如果手动有害内存,则必须在重复使用之前将其取消欺骗。 这对于堆栈地址尤其重要,例如对于在程序执行期间经常重复使用的局部变量。 如果不在删除堆栈帧之前取消威胁,则在手动中毒的堆栈地址中引入 use-after-poison 误报的风险。
AddressSanitizer 中毒的对齐要求
影子字节的任何手动中毒都必须考虑对齐要求。 如有必要,用户必须添加填充,以便影子字节在影子内存中的字节边界处结束。 AddressSanitizer 影子内存中的每个位对应用程序内存中单个字节的状态进行编码。 这种编码意味着每个分配(包括任何填充)的总大小必须与 8 字节边界对齐。 如果不满足对齐要求,可能会生成 bug 报告。 不正确的报告可能表现为缺少报告(漏报)或非错误报告(误报)。
有关对齐要求和潜在问题的说明,请参阅提供的 ASan 对齐示例。 一个是一个小程序,显示手动影子内存中毒可能会出错的情况。 第二个是使用 std::allocator 接口的手动中毒的示例实现。
运行时选项
MSVC AddressSanitizer 是 Clang AddressSanitizer 运行时的定期同步分支。 因此,MSVC 隐式继承许多 Clang 的 ASan 运行时选项。 可在 此处找到我们积极维护和测试的选项的完整列表。 如果发现某选项无法按预期运行,请报告 bug。
配置运行时选项
ASan 运行时选项采用以下两种方式之一进行设置:
-
ASAN_OPTIONS环境变量 -
__asan_default_options用户函数
如果环境变量和用户函数指定冲突选项,则 ASAN_OPTIONS 环境变量中的选项优先。
通过用冒号分隔多个选项来指定多个选项。:
以下示例将设置为 alloc_dealloc_mismatch 1 和 symbolize 0:
set ASAN_OPTIONS=alloc_dealloc_mismatch=1:symbolize=0
或者将以下函数添加到代码中:
extern "C" const char* __asan_default_options()
{
return "alloc_dealloc_mismatch=1:symbolize=0";
}
不支持的 AddressSanitizer 选项
detect_container_overflowunmap_shadow_on_exit
Note
AddressSanitizer 运行时选项 halt_on_error 无法按预期方式运行。 在 Clang 和 MSVC 运行时库中,许多错误类型被视为 不可连续性,包括大多数内存损坏错误。
有关详细信息,请参阅与 Clang 12.0 的差异部分。
特定于 MSVC 的 AddressSanitizer 运行时选项
continue_on_error布尔值,false默认设置。 设置为/> 时,它允许程序在报告内存冲突后继续执行,从而允许收集多个错误报告。 iat_overwrite字符串,默认设置为"error"。 其他可取的值为"protect"和"ignore"。 某些模块可能会覆盖其他模块的import address table以自定义某些函数的实现。 例如,驱动程序通常为特定硬件提供自定义实现。 该iat_overwrite选项管理 AddressSanitizer 运行时对特定memoryapi.h函数的防覆盖保护。 运行时当前跟踪VirtualAlloc、VirtualProtect和VirtualQuery函数以进行保护。 Visual Studio 2022 版本 17.5 预览版 1 及更高版本提供该选项。 以下iat_overwrite值控制在覆盖受保护函数时运行时的反应方式:- 如果设置为
"error"(默认值),运行时将在检测到覆盖时报告错误。 - 如果设置为
"protect",运行时将尝试避免使用被覆盖的定义并继续操作。 实际上,该函数的原始memoryapi定义在运行时内部使用,以避免无限递归。 进程中的其他模块仍使用被覆盖的定义。 - 如果设置为
"ignore",运行时不会尝试更正任何被覆盖的函数,并继续执行。
- 如果设置为
windows_fast_fail_on_error布尔值(false默认情况下),设置为true允许进程在打印错误报告后终止__fastfail(71)。Note
当值设置为
abort_on_error时true,在 Windows 上,程序以 aexit(3). 为了不更改当前行为,我们决定改为引入这一新选项。 如果同时存在abort_on_error,windows_fast_fail_on_errortrue程序将退出。__fastfailwindows_hook_legacy_allocators布尔值,设置为false以禁用对GlobalAlloc和LocalAlloc分配器的拦截。Note
撰写本文时,公共 llvm 项目运行时中没有
windows_hook_legacy_allocators选项。 该选项最终可能会进入公共项目;但需要经过代码审查和社区接受。选项
windows_hook_rtl_allocators以前是一个可选功能,因为 AddressSanitizer 是试验性的,但现在默认启用。 在 Visual Studio 2022 版本 17.4.6 之前的版本中,默认选项值为false。 在 Visual Studio 2022 版本 17.4.6 及更高版本中,选项windows_hook_rtl_allocators默认为true。
AddressSanitizer 拦截函数列表 (Windows)
AddressSanitizer 运行时热修补许多函数,以在运行时启用内存安全检查。 下面是 AddressSanitizer 运行时监视的函数的非详尽列表。
默认拦截器
-
__C_specific_handler(仅限 x64) _aligned_free_aligned_malloc_aligned_msize_aligned_realloc_calloc_base_calloc_crt-
_calloc_dbg(仅限调试运行时) -
_except_handler3(仅限 x86) -
_except_handler4(仅限 x86)(未记录) _expand-
_expand_base(未记录) -
_expand_dbg(仅限调试运行时) -
_free_base(未记录) -
_free_dbg(仅限调试运行时) -
_malloc_base(未记录) -
_malloc_crt(未记录) -
_malloc_dbg(仅限调试运行时) _msize-
_msize_base(未记录) -
_msize_dbg(仅限调试运行时) -
_realloc_base(未记录) -
_realloc_crt(未记录) -
_realloc_dbg(仅限调试运行时) _recalloc-
_recalloc_base(未记录) -
_recalloc_crt(未记录) -
_recalloc_dbg(仅限调试运行时) _strdupatoiatolcallocCreateThreadfreefrexplongjmpmallocmemchrmemcmpmemcpymemmovememsetRaiseExceptionreallocRtlAllocateHeapRtlCreateHeapRtlDestroyHeapRtlFreeHeapRtlRaiseException-
RtlReAllocateHeap(未记录) -
RtlSizeHeap(未记录) SetUnhandledExceptionFilterstrcatstrchrstrcmpstrcpystrcspnstrdupstrlenstrncatstrncmpstrncpystrnlenstrpbrkstrspnstrstrstrtokstrtolwcslenwcsnlen
可选拦截器
仅当启用了 AddressSanitizer 运行时选项时,才会安装此处列出的拦截器。 将 windows_hook_legacy_allocators 设置为 false 以禁用旧式分配器拦截。
set ASAN_OPTIONS=windows_hook_legacy_allocators=false
GlobalAllocGlobalFreeGlobalHandleGlobalLockGlobalReAllocGlobalSizeGlobalUnlockLocalAllocLocalFreeLocalHandleLocalLockLocalReAllocLocalSizeLocalUnlock
另请参阅
AddressSanitizer 概述
AddressSanitizer 已知问题
AddressSanitizer 生成和语言参考
AddressSanitizer 阴影字节
AddressSanitizer 云或分布式测试
AddressSanitizer 调试程序集成
AddressSanitizer 错误示例