目录
Asan 是什么?
Asan 功能
缓冲区溢出
悬空指针(引用)
非法释放
内存泄漏
如何使用
实验环境
内存泄露检查
demo1
demo2
堆缓冲区溢出
栈缓冲区溢出
使用悬空指针
使用栈上返回的变量
使用退出作用域的变量/内存
重复释放
输出优化
使用ASAN_OPTIONS参数启动程序
使用asan_symbolize.py脚本
参考
Asan 是什么?
- ASan,即Address Sanitizer,是一个适用于c/c++的动态内存错误检测器,它由一个编译器检测模块(LLVM pass)和一个替换
malloc
函数的运行时库组成,在性能及检测内存错误方面都优于Valgrind。
Asan 功能
ASan作为编译器内置功能,支持检测各种内存错误:
缓冲区溢出
-
堆内存溢出
-
栈上内存溢出
-
全局区缓存溢出
悬空指针(引用)
-
使用释放后的堆上内存
-
使用返回的栈上内存
-
使用退出作用域的变量
非法释放
-
重复释放
-
无效释放
内存泄漏
如何使用
- 使用ASan时,只需gcc/g++选项加上-fsanitize=address
- 如果想要在使用asan的时候获取更好的性能,可以加上O1或者更高的编译优化选项
- 想要在错误信息中让栈追溯信息更友好,可以加上-fno-omit-frame-pointer选项
- 需要注意的是需要带-g选项,会加入一些调试信息到符号表以供输出使用,不带也可以,如果不带,可能看不到错在哪一行
- 需要动态链接库liblsan.so,这个动态链接库会替换掉malloc等系统函数,在自己的malloc中加上统计信息,以达到检测内存泄露的作用。需要注意的点是,我们的环境中可能会有多个gcc或者找不到dso的时候。那么我们可以使用g++ --print-file-name=libasan.so来找到系统的动态链接库(只能是系统的库),这条命令会告诉你so在哪,但在我们的环境中我遇到了一个比较坑的问题。就是so是有,但是so的大小仅为4,里面写着让重新下载so
- -fsanitize=,这个有好几种选项
6.1 asan(内存检测)
6.2 ubsan(未定义行为检测), 有的时候debug的程序没问题,release的程序会奇奇怪怪的core dump掉,那么你需要做这个检测
6.3 tsan(线程安全检测),如标题;tsan有一个点需要注意,因为大家代码跑通后一般不会用tsan做检测,再者tsan出来时间不长,一些老的库会有非常多的线程安全问题。再加上检测条件非常严格。所以,大型项目第一用tsan做检查的时候,可能每一行都会有线程安全问题。
6.4 leak(泄露检测),被6.1包括了
6.5 如果我们有多个libasan.so,我们需要跟-L/path/to/lib
6.6 如果提示请加载PRELOAD,那么请export LD_PRELOAD=或者export LD_PRELOAD=/path/to/liblsan.so/libasan.so
实验环境
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.3 LTS
Release: 20.04
Codename: focal
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
内存泄露检查
demo1
#include <iostream>
using namespace std;
int main()
{
int *p = new int(5);
std::cout << *p << std::endl;
return 0;
}
编译:g++ main.cpp -g -llsan -fsanitize=leak
第9行,是个总结,他会告诉你总共泄露了4bytes,这4bytes属于1次开辟的。
4-7行,我们这里只泄露了一次,如果多次的话会有多个4-7行,会告诉你那个线程开辟的空间,哪个线程释放的空间。并且打印出详细的开辟和释放的函数栈,当然我们注意第五行,我们用的malloc已经被替换成lsan下的malloc了。
第2行,错误类型。
demo2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* get_systeminfo()
{
char *p_system = (char*)malloc(38*sizeof(char));
strcpy(p_system, "Linux version");
return p_system;
}
int main()
{
printf("Linux version is:%s", get_systeminfo());
return 0;
}
编译运行输出:
需带上ASAN_OPTIONS=detect_leaks=1 环境变量参数启程序
编译:
g++ -fsanitize=address -fno-omit-frame-pointer -o memory_leak_test main.cpp
ASAN_OPTIONS=detect_leaks=1 ./memory_leak_test
输出:
=================================================================
==73465==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 38 byte(s) in 1 object(s) allocated from:
#0 0x7fc23737d808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x55d14d17b21e in get_systeminfo() (/home/wangji/文档/桌面/学习资料/C++/memory_leak_test+0x121e)
#2 0x55d14d17b24d in main (/home/wangji/文档/桌面/学习资料/C++/memory_leak_test+0x124d)
#3 0x7fc236d560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)
SUMMARY: AddressSanitizer: 38 byte(s) leaked in 1 allocation(s).
堆缓冲区溢出
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *heap_buf = (char*)malloc(32*sizeof(char));
memcpy(heap_buf+30, "overflow", 8);//在heap_buf的第30个字节开始,拷贝8个字符
free(heap_buf);
return 0;
}
编译:
gcc -fsanitize=address -fno-omit-frame-pointer -o heap_ovf_test main.c
./heap_ovf_test
可以看到asan报错:==74085==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000030 at xxxx,下面也列出了发生heap-buffer-overflow时的调用链及heap buffer在哪里申请的。
栈缓冲区溢出
#include <stdio.h>
#include <string.h>
int main()
{
char stack_buf[4] = {0};
strcpy(stack_buf, "1234");
return 0;
}
编译:
gcc -fsanitize=address -fno-omit-frame-pointer -o stack_ovf_test main.c
./stack_ovf_test
使用悬空指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p = (char*)malloc(32*sizeof(char));
free(p);
int a = p[1];
return 0;
}
编译:
gcc -fsanitize=address -fno-omit-frame-pointer -o dang_pointer main.c
./dang_pointer
使用栈上返回的变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int *ptr;
void get_pointer()
{
int local[10];
ptr = &local[0];
return;
}
int main()
{
get_pointer();
printf("%d\n", *ptr);
return 0;
}
编译:
启用ASAN_OPTIONS=detect_stack_use_after_return=1标志,才能检测此种内存错误使用的情况
gcc -fsanitize=address -fno-omit-frame-pointer -o use_after_return main.c
ASAN_OPTIONS=detect_stack_use_after_return=1 ./use_after_return
使用退出作用域的变量/内存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int *p;
{
int num = 10;
p = #
}
printf("%d/n", *p);
return 0;
}
编译:
gcc -fsanitize=address -fno-omit-frame-pointer -o use-after-scope main.c
./use-after-scope
重复释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *p = (char*)malloc(32*sizeof(char));
free(p);
free(p);
return 0;
}
编译:
gcc -fsanitize=address -fno-omit-frame-pointer -o invalid_free_test main.c
./invalid_free_test
输出优化
使用ASAN_OPTIONS参数启动程序
ASAN_OPTIONS='stack_trace_format="[frame=%n, function=%f, location=%S]"'参数启动程序
使用asan_symbolize.py脚本
输出的调用链中信息更精确,可以对应到代码文件的具体某一行。
参考
AddressSanitizer · google/sanitizers Wiki · GitHub