内存错误检测工具 您所在的位置:网站首页 内存检测程序怎么设置密码保护 内存错误检测工具

内存错误检测工具

2024-07-14 05:57| 来源: 网络整理| 查看: 265

一、ASAN简介

ASAN(AddressSanitizer的缩写)是一款面向C/C++语言的内存错误问题检查工具,可以检测如下内存问题:

使用已释放内存(野指针)堆内存越界(读写)栈内存越界(读写)全局变量越界(读写)函数返回局部变量内存泄漏

ASAN工具主要由两部分组成:

运行时库 运行时库(libasan.so.x)会接管malloc和``free函数。malloc执行完后,已分配内存的前后(称为“红区”)会被标记为“中毒”状态,而释放的内存则会被隔离起来(暂时不会分配出去)且也会被标记为“中毒”状态。

编译器插桩模块 加了ASAN相关的编译选项后,代码中的每一次内存访问操作都会被编译器修改为如下方式:

编译前:

*address = ...; // or: ... = *address;

编译后:

if (IsPoisoned(address)) { ReportError(address, kAccessSize, kIsWrite); } *address = ...; // or: ... = *address;

之前也介绍过一款传统的内存问题检测工具Valgrind : https://blog.csdn.net/qq_15437629/article/details/79264600

用过 Valgrind 的朋友应该都清楚,其会极大的降低程序运行速度,大约降低10倍,而 AddressSanitizer 大约只降低2倍!与valgrind相比asan消耗非常低,甚至可以直接在生产环境中启用asan排查跟踪内存问题。

二、ASAN安装

ASAN早先是LLVM中的特性,后被加入gcc4.8,成为 gcc 的一部分,但不支持符号信息,无法显示出问题的函数和行数。从 4.9 开始,gcc 支持 AddressSanitizer 的所有功能。因此gcc 4.8以上版本使用ASAN时不需要安装第三方库,通过在编译时指定编译CFLAGS即可打开开关。

如果使用 AddressSanitizer 时报错则需要先安装:

/usr/bin/ld: cannot find /usr/lib64/libasan.so.0.0.0

Ubuntu 安装命令:

sudo apt-get install libasan0

CentOS 安装命令:

sudo yum install libasan 三、ASAN使用

1、gcc编译选项:

# -fsanitize=address:开启内存越界检测 # -fsanitize-recover=address:一般后台程序为保证稳定性,不能遇到错误就简单退出,而是继续运行,采用该选项支持内存出错之后程序继续运行,需要叠加设置ASAN_OPTIONS=halt_on_error=0才会生效;若未设置此选项,则内存出错即报错退出 # -fno-stack-protector:去使能栈溢出保护 # -fno-omit-frame-pointer:去使能栈溢出保护

2、ASAN_OPTIONS设置:

ASAN_OPTIONS是Address-Sanitizier的运行选项环境变量。

# halt_on_error=0:检测内存错误后继续运行 # detect_leaks=1:使能内存泄露检测 # malloc_context_size=15:内存错误发生时,显示的调用栈层数为15 # log_path=/home/asan.log:内存检查问题日志存放文件路径 # export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=/home/asan.log # env |grep ASAN_OPTIONS

3、编译运行:

测试代码:

#include #include char* getMemory() { char *p = (char *)malloc(30); return p; } int main() { char *p = getMemory(); p = NULL; return 0; }

执行:

gcc test.c -fsanitize=address -fsanitize-recover=address -fno-stack-protector -fno-omit-frame-pointer ./a.out

可以在我们指定的目录下看到asan日志(后面跟上了进程号):

# cat asan.log.2793581 ================================================================= ==2793581==ERROR: LeakSanitizer: detected memory leaks Direct leak of 30 byte(s) in 1 object(s) allocated from: #0 0x7fc1d3beee70 in __interceptor_malloc (/usr/lib64/libasan.so.4+0xe0e70) #1 0x40116b in getMemory (/home/test/a.out+0x40116b) #2 0x401187 in main (/home/test/a.out+0x401187) #3 0x7fc1d397bc56 in __libc_start_main (/usr/lib64/libc.so.6+0x25c56) SUMMARY: AddressSanitizer: 30 byte(s) leaked in 1 allocation(s). 四、动态库的asan日志定位

假设我们的getMemory函数放在动态库中,情况会怎么样呢?

我们参照: https://blog.csdn.net/qq_15437629/article/details/81914996 将getMemory函数做成动态库,编译时同样也加上-fsanitize=address -fsanitize-recover=address -fno-stack-protector -fno-omit-frame-pointer参数,最后执行后asan结果如下:

# cat asan.log.3456002 ================================================================= ==3456002==ERROR: LeakSanitizer: detected memory leaks Direct leak of 30 byte(s) in 1 object(s) allocated from: #0 0x7f0360616e70 in __interceptor_malloc (/usr/lib64/libasan.so.4+0xe0e70) #1 0x7f0360008186 () #2 0x40125e in main /home/test/main.c:26 #3 0x7f036039ec56 in __libc_start_main (/usr/lib64/libc.so.6+0x25c56) SUMMARY: AddressSanitizer: 30 byte(s) leaked in 1 allocation(s).

可以看到由于close了so释放了资源,已经看不到地址对应的so信息,也无法拿到具体的堆栈了。那怎么找到着个泄漏点呢?

1,首先需要确认的就是这个到底时啥。 我们可以在程序close动态库前 执行如下命令获取进程的memory map:

cat /proc/`pidof a.out`/maps

从 这个maps文件中我们可以看到地址和so的对应关系,然后根据ASAN日志中的地址和maps中保存的地址就能确定出现泄漏的内存是在哪个模块中产生的:

7f035fe50000-7f0360000000 rw-p 00000000 00:00 0 7f0360007000-7f0360008000 r--p 00000000 fd:00 1314650 /home/test/libcount.so 7f0360008000-7f0360009000 r-xp 00001000 fd:00 1314650 /home/test/libcount.so 7f0360009000-7f036000a000 r--p 00002000 fd:00 1314650 /home/test/libcount.so 7f036000a000-7f036000b000 r--p 00002000 fd:00 1314650 /home/test/libcount.so 7f036000b000-7f036000c000 rw-p 00003000 fd:00 1314650 /home/test/libcount.so 7f036000c000-7f0360010000 rw-p 00000000 00:00 0 7f0360014000-7f0360026000 rw-p 00000000 00:00 0 7f0360026000-7f0360029000 r--p 00000000 fd:00 2761887 /usr/lib64/libgcc_s-7.3.0-20190804.so.1

可以看到 0x7f0360008186 () 地址属于: 7f0360008000-7f0360009000 r-xp 00001000 fd:00 1314650 /home/test/libcount.so

2,更进一步地,我们还需要找到so里的具体函数。这里可以通过结合addr2line命令来获取堆栈:

先将ASAN中的地址 - 动态链接库的基地址,计算下偏移地址: 0x7f0360008186 - 0x7f0360007000 = 0x1186

然后执行既可:

# addr2line -C -f -e /home/test/libcount.so 0x1186 getMemory /home/test/count.c:17

ps: 1,addr2line参数介绍:

-a --addresses:在函数名、文件和行号信息之前,显示地址,以十六进制形式。 -b --target=:指定目标文件的格式为bfdname。 -e --exe=:指定需要转换地址的可执行文件名。 -i --inlines : 如果需要转换的地址是一个内联函数,则输出的信息包括其最近范围内的一个非内联函数的信息。 -j --section=:给出的地址代表指定section的偏移,而非绝对地址。 -p --pretty-print:使得该函数的输出信息更加人性化:每一个地址的信息占一行。 -s --basenames:仅仅显示每个文件名的基址(即不显示文件的具体路径,只显示文件名)。 -f --functions:在显示文件名、行号输出信息的同时显示函数名信息。 -C --demangle[=style]:将低级别的符号名解码为用户级别的名字。 -h --help:输出帮助信息。 -v --version:输出版本号

2,使用如下脚本可以将asan日志中的(so+偏移地址)转化为(文件+行数)。需安装debuginfo

cat $1|while read line;do if [ $(echo $line|grep -o '(.*+.*)'|wc -l) = 1 ];then a=$(echo $line|cut -d '(' -f1) b=$(echo $line|cut -d '(' -f2|cut -d ')' -f1|awk -F '+' '{print $1}') c=$(echo $line|cut -d '(' -f2|cut -d ')' -f1|awk -F '+' '{print $2}') if [ -z "$b" ] || [ -z "$c" ]; then echo $line continue fi d=$(addr2line -e $b $c) if [ $(echo $d|grep '?'|wc -l) = 0 ];then echo -e "\t$a($d)" else echo -e "\t$line" fi else echo $line fi done


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有