简介
AddressSanitizer
是一个性能非常好的C/C++
内存错误探测工具。
它由编译器的插桩模块和替换了malloc
函数的运行时库组成。
这个工具可以探测如下这些类型的错误:
- 对堆、栈和全局内存的访问越界(堆缓冲区溢出,栈缓冲区溢出,和全局缓冲区溢出)
- UAP(Use-after-free,悬挂指针的解引用,或者说野指针)
- Use-after-return(无效的栈上内存,运行时标记
ASAN_OPTIONS=detect_stack_use_after_return=1
) - Use-After-Scope (作用域外访问,
clang
标记-fsanitize-address-use-after-scope
) - 内存的重复释放 (double-free)
- 初始化顺序的BUG
- 内存泄漏 (memory leak)
使用
以CentOS7
环境下编译器gcc 4.8.5
为例 (版本支持: gcc version
> 4.8)
- 安装对应系统版本和
gcc
版本的libasan
(搜索libasan
相关包: pkgs.org):
wget http://mirror.centos.org/centos/7/os/x86_64/Packages/libasan-4.8.5-39.el7.x86_64.rpm
yum install -y libasan-4.8.5-39.el7.x86_64.rpm
- 编译源码的时候只需要加上以下编译参数即可:
-fsanitize=address -fno-omit-frame-pointer -g
- 若项目是以
CMake
作为构建工具, 可按如下做法修改:
add_definitions("-fsanitize=address -fno-omit-frame-pointer -lasan")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -lasan")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -lasan")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -fno-omit-frame-pointer -lasan")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address -fno-omit-frame-pointer -lasan")
测试例子
源码:
#include <iostream>
int main(int argc, char** argv)
{
int a[5];
int index=6;
int retval=a[index];
std::cout << "Ret :" << retval << std::endl;
return retval;
}
编译:
g++ -g -std=c++11 -fsanitize=address -fno-omit-frame-pointer test.cpp -o test
运行./test
后报错:
=================================================================
==7708==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc32facb68 at pc 0x401610 bp 0x7ffc32facae0 sp 0x7ffc32facad0
READ of size 4 at 0x7ffc32facb68 thread T0
#0 0x40160f in main test.cpp:8
#1 0x7fefe78a8504 in __libc_start_main (/lib64/libc.so.6+0x22504)
#2 0x401448 (./test+0x401448)
Address 0x7ffc32facb68 is located in stack of thread T0 at offset 56 in frame
#0 0x401525 in main ./test.cpp:5
This frame has 2 object(s):
[32, 52) 'a' <== Memory access at offset 56 overflows this variable
[96, 144) 'm'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ./test.cpp:8 main
Shadow bytes around the buggy address:
0x1000065ed910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed920: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed950: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000065ed960: 00 00 00 00 00 00 f1 f1 f1 f1 00 00 04[f4]f2 f2
0x1000065ed970: f2 f2 00 00 00 00 00 00 f4 f4 f3 f3 f3 f3 00 00
0x1000065ed980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed9a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1000065ed9b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Contiguous container OOB:fc
ASan internal: fe
==7708==ABORTING
可以看到报错提示栈缓冲区溢出 ERROR: AddressSanitizer: stack-buffer-overflow
, 通过调查源码可以发现程序对栈区内存上的数组a
的访问越界了。
实践经验
- 项目的构建方案应当有编译选项可以随时启用/关闭
ASAN
- 项目送测阶段可以打开
ASAN
以帮助暴露更多的低概率诡异问题 - 请勿在生产版本中启用
ASAN
, 其会降低程序运行速度大概2-5倍 (特殊情况除外) - 实际开发测试过程中通过
ASAN
扫出的常见问题有: - 多线程下临界资源未加保护导致同时出现读写访问, 解决方案一般是对该资源恰当地加锁即可
- 内存越界, 如申请了N字节的内存却向其内存地址拷贝大于N字节的数据, 这种情况在没有开启ASAN的情况下一般都很难发现