一、介绍
AddressSanitizer(又名 ASAN)可以检测 C/C++ 的内存错误。可以发现如下的内存错误:
- Use after free (dangling pointer dereference),使用 free 后的指针。默认开启
- Heap buffer overflow,堆内存越界。默认开启
- Stack buffer overflow,栈内存越界。默认开启
- Global buffer overflow,全局/静态内存块使用越界,默认开启
- Use after return,栈中的内存访问,默认不开启
- Use after scope,作用域外的内存访问,编译时加选项开启
- Initialization order bugs,内存初始化顺序问题,默认不开启
- Memory leaks,内存泄漏,
Global buffer overflow
1 | // RUN: clang -O -g -fsanitize=address %t && ./a.out |
Use after return
例子:
1 | int *ptr; |
默认情况下,不开启发现这种 Bug。想要开启检测,必须设置环境变量:ASAN_OPTIONS=detect_stack_use_after_return=1
Use after scope
例子:
1 | volatile int *p = 0; |
如果想在运行时禁止这种检测,可以设置环境变量:ASAN_OPTIONS=detect_stack_use_after_scope=0
Initialization order bugs
不同源文件中的全局对象的构造函数的初始化顺序是未定义的。如下举一个例子:
1 | // a.c |
如上代码,这里 x 的值取决于 extern_global 的值。同时 extern_global 的值取决于 a.c 还是 b.c 先编译。这种问题很难发现,编译器或者链接器的一些策略变化可能会导致代码行为的改变。
ASAN 在编译时插入检查,检测初始化的顺序问题。默认情况下,他们是关闭的,需要使用环境变量开启。
并且此检测有两种模式,一种是宽松的检测模式,另一种是严格的检测模式。
- 宽松的初始化顺序检测模式
1 | clang++ -fsanitize=address -g a.c b.c -o a.out |
这种模式,如果初始化顺利改变,也就是编译或链接顺序改变,可能会检测不到问题
1 | // 这样编译可能检测不到问题 |
- 严格的初始化顺序检测模式
1 | clang++ -fsanitize=address -g b.c a.c |
在这种模式下,如果一个源文件的全局变量的初始化依赖另一个源文件的任何动态初始化的全局变量,都会报错。同时可能会产出误报:https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco
Memory leaks
LeakSanitizer 是一个集成到 AddressSanitizer 中的内存泄漏检测器。该工具在 x86_64 Linux 和 OS X 上受支持。
LeakSanitizer 在 x86_64 Linux 的 ASan 版本中默认启用,在 x86_64 OS X 上启用ASAN_OPTIONS=detect_leaks=1
。LSan 处于休眠状态,直到该过程的最后阶段,此时有一个额外的泄漏检测阶段。在性能关键场景中,LSan 也可以在没有 ASan 检测的情况下使用。
如果不想检测内存泄漏,可以使用环境变量:ASAN_OPTIONS=detect_leaks=0
。如果只想要检测内存泄漏,编译时使用:-fsanitize=leak
而不是 -fsanitize=address
设计文档:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizerDesignDocument
对于 tcmalloc 的泄漏检测:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizerVsHeapChecker
其他的一些行为:https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer