AddressSanitizer 页面

介绍

AddressSanitizer 是一个快速的内存错误检测工具,它由一个编译时插桩模块和一个运行库组成。该工具可以检测以下类型的错误:

  • OOB(包括堆、栈和全局变量)
  • UAF
  • Use-After-Return(runtime flag ASAN_OPTIONS=detect_stack_use_after_return=1)
  • Use-After-Scope(clang flag -fsanitize-address-use-after-scope)
  • Doube-Free
  • Memory Leaks

AddressSanitizer 引入的一般速度下降是两倍左右

如何构建

使用 CMAKE 构建 LLVM/Clang

用法

只需带有 -fsanitize=address 来编译链接你的程序,同时 AddressSanitizer 的运行时库链接到最终的可执行文件。因此,请确保在最终的链接时使用 clang(not ld)。当链接共享库而 AddressSanitizer 的运行时库未链接时,-Wl,-z,defs 可能会导致链接错误(不要与 AddressSanitizer 同用)。为了得到合理的性能开销,请添加 -O1 或者更高。想要在错误消息中添加更好的堆栈跟踪,启用 -fno-omit-frame-pointer。为了获得完整的堆栈跟踪信息,可能需要禁用内联(使用 -O1)与尾递归消除(-fno-optimize-sibling-calls)

% cat example_UseAfterFree.cc
int main(int argc, char **argv) {
  int *array = new int[100];
  delete [] array;
  return array[argc];  // BOOM
}

# Compile and link
% clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer example_UseAfterFree.cc

或者

# Compile
% clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -c example_UseAfterFree.cc
# Link
% clang++ -g -fsanitize=address example_UseAfterFree.o

如果检测到 bug,程序将向 stderr 输出错误消息,并以非0退出码退出。AddressSanitizer 会在检测到第一个错误时退出:

  • 这种方法允许 AddressSanitizer 生成运行更快、体积更小的生成代码(均约 5%)
  • 修复 bug 变得不可避免,AddressSanitizer 不会产生误报。一旦发生内存破坏,程序处于不一致状态,可能会导致令人困惑的结果,并有可能误导后续报告

如果你的进程是沙盒化的,且正在 OS X 10.10 或更低办不办上运行,需要设置 DYLD_INSERT_LIBRARIES 环境变量并将其指向与用于生成可执行文件的编译器打包的 ASAN 库(可以通过搜索 asan 的名字来查找库)。如果未设置环境变量,则该进程将尝试重新执行。另外请记住,将可执行文件移动到另一台机器时,ASAN 库也需要复制

符号化报告

为了让 AddressSanitizer 符号化输出,需要设置环境变量 ASAN_SYMBOLIZER_PATH 来指向 llvm-symbolizer 二进制程序(或确保 llvm-symbolizer 在 $PATH 中)

% ASAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer ./a.out
==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8
READ of size 4 at 0x7f7ddab8c084 thread T0
    #0 0x403c8c in main example_UseAfterFree.cc:4
    #1 0x7f7ddabcac4d in __libc_start_main ??:0
0x7f7ddab8c084 is located 4 bytes inside of 400-byte region [0x7f7ddab8c080,0x7f7ddab8c210)
freed by thread T0 here:
    #0 0x404704 in operator delete[](void*) ??:0
    #1 0x403c53 in main example_UseAfterFree.cc:4
    #2 0x7f7ddabcac4d in __libc_start_main ??:0
previously allocated by thread T0 here:
    #0 0x404544 in operator new[](unsigned long) ??:0
    #1 0x403c43 in main example_UseAfterFree.cc:2
    #2 0x7f7ddabcac4d in __libc_start_main ??:0
==9442== ABORTING

如果这不适用你的情况(例如你的进程已被沙盒化),可以使单独的脚本来离线标记结果(在线符号化可以通过设置禁用 ASAN_OPTIONS=symbolize=0

% ASAN_OPTIONS=symbolize=0 ./a.out 2> log
% projects/compiler-rt/lib/asan/scripts/asan_symbolize.py / < log | c++filt
==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8
READ of size 4 at 0x7f7ddab8c084 thread T0
    #0 0x403c8c in main example_UseAfterFree.cc:4
    #1 0x7f7ddabcac4d in __libc_start_main ??:0
...

请注意,在 OS X 上,可能需要在你的程序上运行 dsymutil,在 AddressSanitizer 的报告中保存的信息是 file:line

额外检查

初始化顺序检查

当一个翻译单元中定义的全局变量初始化使用另一个翻译单元中定义的全局变量时,AddressSanitizer 可以选择性地检测动态初始化顺序问题

请注意,OS X 上不支持此选项

内存泄露检查

有关 AddressSanitizer 中泄漏检测器的更多信息,请参阅 LeakSanitizer 部分。在 Linux 上漏洞检测默认打开,在 OS X 上可使用 ASAN_OPTIONS=detect_leaks=1 开启。但是在其他平台上并未得到支持

问题抑制

AddressSanitizer 不会产生误报,如果你发现了一个误报请再进一步看看,也许并不是误报

抑制外部库的报告

运行时干预允许 AddressSanitizer 在未重新编译的代码中查找 bug。如果你在外部库中遇到 bug,我们建议立即将其报告给库维护者,以便得到解决。也可以使用如下方法启用抑制机制来解除这种阻塞关系,以便保持继续测试。此抑制机制只用于抑制外部代码中的问题,它不适用于 AddressSanitizer 重新编译的代码。若要消除外部库的错误,请将 ASAN_OPTIONS 环境变量设置为指向一个抑制文件。可以指定文件的完整路径,或者相对于可执行文件的路径

ASAN_OPTIONS=suppressions=MyASan.supp

使用以下格式来指定要抑制的函数或库的名字。你可以在错误报告中看到这些信息。请记住,抑制的范围越小,能捕捉到的 bug 越多

interceptor_via_fun:NameOfCFunctionToSuppress
interceptor_via_fun:-[ClassName objCMethodToSuppress:]
interceptor_via_lib:NameOfTheLibraryToSuppress

条件编译

在某些情况下,根据是否启用 AddressSanitizer,可能需要执行不同的代码。 __has_feature 可以用于此目的

#if defined(__has_feature)
#  if __has_feature(address_sanitizer)
// code that builds only under AddressSanitizer
#  endif
#endif

禁用插桩

某些代码不应由 AddressSanitizer 进行检测。可以使用函数属性 __attribute__ (no_sanitize ( "address")) (已弃用同义词 no_sanitize_address 和 no_address_safety_analysis) 来禁用特定函数的检测。其他编译器可能不支持此属性, 因此建议将其与 __has_feature (address_sanitizer) 一起使用

重新编译代码中的错误(黑名单)

AddressSanitizer 在特殊情况列表中支持 srcfun 实体类型,可以用来抑制在指定的源文件或函数的错误报告。此外,AddressSanitizer 还引入了 globaltype 实体类型,可用于抑制对带有确定名字或类型的全局变量的 OOB 的错误报告,可以只指定类或结构体类型

可以使用 init 类别来抑制某些源文件或某些全局变量中发生的初始化顺序问题的错误报告

# Suppress error reports for code in a file or in a function:
src:bad_file.cpp
# Ignore all functions with names containing MyFooBar:
fun:*MyFooBar*
# Disable out-of-bound checks for global:
global:bad_array
# Disable out-of-bound checks for global instances of a given class ...
type:Namespace::BadClassName
# ... or a given struct. Use wildcard to deal with anonymous namespace.
type:Namespace2::*::BadStructName
# Disable initialization-order checks for globals:
global:bad_init_global=init
type:*BadInitClassSubstring*=init
src:bad/init/files/*=init

抑制内存泄漏

由 LeakSanitizer 生成的内存泄漏报告(如果它作为 AddressSanitizer 的一部分运行)可以被导出到一个单独的文件

LSAN_OPTIONS=suppressions=MyLSan.supp

其模式为 leak:<pattern>,如果模式与泄漏报告的符号化堆栈跟踪中的任何函数名、源文件名或库名匹配,则会抑制内存泄漏。有关详细信息,请参阅完整文档

局限

  • 使用更多的物理内存,确切的开销取决于分配的细致程度,分配的越细,开销越大
  • AddressSanitizer 使用更多的栈内存,大概是三倍的增长
  • 在 64 位平台上 AddressSanitizer 映射但不保留 16TB+ 的虚拟地址空间,这意味着像 ulimit 这样的工具可能不能像预期一样工作
  • 不支持静态链接

支持平台

AddressSanitizer 支持:

  • Linux i386/x86_64 (tested on Ubuntu 12.04)
  • OS X 10.7 - 10.11 (i386/x86_64)
  • iOS Simulator
  • Android ARM
  • FreeBSD i386/x86_64 (tested on FreeBSD 11-current)

其他平台的接口正在开发

当前状态

AddressSanitizer 在 LLVM 3.1 后支持的平台上完全可用,该测试套件已集成到 CMake 的构建中,可通过命令 make check-asan 运行

更多信息

https://github.com/google/sanitizers/wiki/AddressSanitizer

原文地址

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页