一、问题
Linux项目开发过程中,时常会出现内存泄漏的问题,而这类问题却很难定位和排查,本文介绍一种定位内存泄漏问题的常用工具asan工具。
二、asan工具原理
asan全称AddressSanitizer,是一种面向c/c++语言的内存错误问题检查工具,目的是帮助开发者检测和调试内存相关的问题。gcc工具在4.8版本后加入了asan工具,但完整的asan功能需要在4.9.2以上才的以提供。
asan主要包括三个部分:
1、阴影内存shadow memory:ASAN算法的基石,占用一部分的内存区域,用于记录哪些内存地址是可以安全访问的,哪些内存地址是不可以访问的。
2、检测模块instrumentation module:编译期间,对代码进行修改,主要增加了以下两个内容:
1)在每一次内存访问前,先检查对应的内存地址的shadow状态,确定可以安全访问时才访问,否则报错。
2)在应用数据的前后增加毒区(poisoned redzones),毒区设置为不可寻址,用于检测stack和global objects类型的越界访问的错误。
3、运行时库run-time library:运行期间,对代码进行修改,主要是修改了malloc和free函数的实现方法,在heap上分配内存时,在应用数据前后创建毒区(poisoned redzones),用于检测heap类型的越界访问的错误。
asan算法的思想是:如果想防住Buffer Overflow泄漏,只需要在每块内存区域右端(或两端,能防overflow和underflow)加一块区域(RedZone),使RedZone的区域的影子内存(Shadow Memory)设置为不可写,如图1所示。
图1 asan工具映射关系图
这里举一个stack的例子,fun的原始代码,在栈上分配8个byte的内存空间:
void fun() {
char a[8];
...
return;
}
那么asan重构Shadow Memory检测stack的越界访问后,代码变化如下。 首先asan会在应用数据的前后增设大小为32byte的redzones,然后再给对应的Shadow地址赋值。可以看到,每8byte的redzones对应的1byte的Shadow的值都为0xff,是负数-1,代表全部8byte都不可寻址。
void fun() {
char redzone1[32]; // 32-byte aligned
char a[8]; // 32-byte aligned
char redzone2[24];
char redzone3[32]; // 32-byte aligned
int *shadow_base = MemToShadow(redzone1);
shadow_base[0] = 0xffffffff; // poison redzone1
shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'
shadow_base[2] = 0xffffffff; // poison redzone3
...
shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison all
return;
}
三、asan工具使用
1、查找编译链中的asan库,并将动态库移到项目中;
root&user:which arm-linux-gnueabihf-gcc
/opt/arm/arm-linux-gnueabihf-6.5/bin/arm-ca9-linux-gnueabihf-gcc
root&user: find /opt/arm/arm-linux-gnueabihf-6.5/ -name "*asan*"
/opt/arm/arm-linux-gnueabihf-6.5/lib/gcc/arm-linux-gnueabihf/6.5.0/include/sanitizer/asan_interface.h
/opt/arm/arm-linux-gnueabihf-6.5/lib/gcc/arm-linux-gnueabihf/6.5.0/plugin/include/asan.h
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan.so.3.0.0
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan.la
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan.a
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan.so
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan_preinit.o
/opt/arm/arm-linux-gnueabihf-6.5/arm-linux-gnueabihf/lib/libasan.so.3
2、使用编译选项,编译可执行文件,生成可执行文件、可执行文件的map。
CFLAGS += -fsanitize=address -fsanitize-recover=address -fno-stack-protector -fno-omit-frame-pointer
LDFLAGS += -l:libasan.a //添加了动态库则不需要链接
-fsanitize=address:开启内存越界检测
-fsanitize-recover=address:一般后台程序为保持稳定性,不能遇到错误就简单退出,而是继续运行,采用该选项支持内存出错后程序继续运行,需要叠加设置环境变量ASAN_OPTIONS=halt_on_error才能生效;若不设置,则内存出错即报错退出
-fno-stack-protector:去使能栈溢出保护
-fno-omit-frame-pointer:检测到内存错误时,打印出函数调用栈
3、配置环境变量export,执行hicore
ASAN_OPTIONS=halt_on_error=0:malloc_context_size=10:quarantine_size_mb=0:log_path=/home/asan.log
四、asan信息解读
==3322==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61400000ffd0
at pc 0x0000004008e0 bp 0x7ffeddce53a0 sp 0x7ffeddce5390
READ of size 4 at 0x61400000ffd0 thread T0
#0 0x4008df in main /home/heap_buf_overflow.cpp:5
#1 0x7f3b83d0882f in __libc_start_main (/lib/x86_64-linuxgnu/libc.so.6+0x2082f)
#2 0x4007b8 in _start (/home/build/heap_buf_overflow+0x4007b8)
0x61400000ffd0 is located 0 bytes to the right of 400-byte region [0x61400000fe40,0x61400000ffd0) allocated by thread T0 here:
#0 0x7f3b841516b2 in operator new[](unsigned long) (/usr/lib/x86_64-linuxgnu/libasan.so.2+0x996b2)
#1 0x40089e in main /home/heap_buf_overflow.cpp:4
#2 0x7f3b83d0882f in __libc_start_main (/lib/x86_64-linuxgnu/libc.so.6+0x2082f)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/heap_buf_overflow.cpp:5 main
第一部分(ERROR):指出错误类型是heap-use-after-free,asan可以检测的内存问题如下。
heap-buffer-overflow //堆缓冲区溢出
stack-buffer-overflow //栈缓冲区溢出
global-buffer-overflow //全局变量溢出
heap-use-after-free //使用悬空指针(指针已经释放,却使用了)
stack-use-after-scope //使用退出作用域的内存,野指针
attempting double-free //重复释放
initialization-order-fiasco //初始化顺序问题
Detected memory leaks //内存泄漏
第二部分(READ):指出线程名thread T0,操作为READ,发生的位置是use-after-free.c:5。 该heap块之前已经在use-after-free.c:4被释放了。
第三部分 (SUMMARY) 前面输出的概要说明。