前言
使用C/C++编程,不可避免地会遭遇各种各样的内存异常。走查代码这种方式定位异常比较低效。在Linux平台下,Address Sanitizer是一个定位程序内存异常的高效分析工具。
一、Address Sanitizer简介
相比Valgrind,Address Sanitizer(ASan)要快很多,只会拖慢程序两倍左右。它包括一个编译器instrumentation模块和一个提供malloc()/free()替代项的运行时库。AddressSanitizer是gcc的一部分,本文采用gcc-7.5.0版本进行演示。
下载地址:
http://www.gnu.org/software/gcc/mirrors.html
ASan可以检查如下几种内存异常:
- 内存错误操作:-fsanitize=address
- 多线程竞争:-fsanitize=thread
- 内存泄漏:-fsanitize=leak
- 未定义操作:-fsanitize=undefined(如:除0、空指针解引用、枚举值超范围、使用未初始化的变量值等)
二、使用步骤
1.安装gcc7.5.0
安装命令:
cd /opt
tar -xvzf gcc-7.5.0.tar.gz -C /opt
ln -sf /opt/gcc-7.5.0/bin/gcc /usr/bin/gcc-7.5.0 // 避免与系统自带gcc冲突,建立软连接
2.编译程序
address.h:
#include <memory>
#include <tr1/memory>
#define MSG_LEN 128
class Cert
{
public:
Cert()
{
printf("\nCert begin\n");
m_number = 0xFFFFFFFFFFFFFFFF;
m_msg = new char[MSG_LEN+1];
memset(m_msg, 0, MSG_LEN + 1);
printf("Cert end\n");
}
~Cert()
{
printf("\n~Cert begin\n");
printf("~Cert end\n");
}
public:
unsigned long long m_number;
char *m_msg;
};
typedef std::tr1::shared_ptr<Cert> Cert_Ptr;
address.cpp:
/* 注意如下任意一种都不会生成内存检测报告:
1. 使用kill -9结束进程
2. 内存检测报告的文件路径无读写权限
*/
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include "address.h"
using namespace std;
#ifdef Enable_AddressSanitizer
static const char* __attribute__((unused)) ENABLE_AS = "Enable_AddressSanitizer";
void signal_handler(int signo);
/* 结束进程: kill -s 3 PID */
void signal_handler(int signo)
{
printf("%s", ENABLE_AS);
if(SIGQUIT == signo) /* SIGQUIT: 3 */
{
exit(0);
}
}
int output_stderr_to_file(char *logfilepath);
int output_stderr_to_file(char *logfilepath)
{
int result = -1;
int logfd = open(logfilepath, O_CREAT|O_RDWR|O_APPEND);
if(-1 != logfd)
{
result = dup2(logfd, STDERR_FILENO);
}
return result;
}
#endif
void test_func();
int main()
{
#ifdef Enable_AddressSanitizer
char szlogfilename[256] = {0};
const char *pSanitizer = "Analyze_none";
#ifdef Analyze_address
pSanitizer = "Analyze_address";
#endif
#ifdef Analyze_thread
pSanitizer = "Analyze_thread";
#endif
#ifdef Analyze_leak
pSanitizer = "Analyze_leak";
#endif
#ifdef Analyze_undefined
pSanitizer = "Analyze_undefined";
#endif
char sztime[64] = {0};
time_t tmNowT;
time(&tmNowT);
struct tm *tmNowS = localtime(&tmNowT);
if(0 != tmNowS)
{
sprintf(sztime, "%04d-%02d-%02d %02d_%02d_%02d",
1900+tmNowS->tm_year,1+tmNowS->tm_mon,tmNowS->tm_mday,tmNowS->tm_hour,tmNowS->tm_min,tmNowS->tm_sec);
}
else
{
sprintf(sztime, "%04d-%02d-%02d %02d_%02d_%02d", 1900,1,1,0,0,0);
}
sprintf(szlogfilename, "./%s_%s.log", pSanitizer, sztime);
output_stderr_to_file(szlogfilename);
signal(SIGQUIT, signal_handler);
#endif
while(1)
{
test_func();
sleep(1);
}
return 0;
}
void test_func()
{
Cert* pCert = new Cert();
memcpy(pCert->m_msg, "2.0.1", MSG_LEN);
char *p = new char[10*1024*1024];
}
makefile
#gcc-4.9.4不支持选项 -fsanitize-recover=all,导致检查到内存错误时程序自动退出
CC= g++
ifeq ($(memcheck), address)
CC= gcc-7.5.0 -fsanitize=address -fsanitize-recover=all -static-libasan -fno-omit-frame-pointer -DEnable_AddressSanitizer -DAnalyze_address
else ifeq ($(memcheck), thread)
CC= gcc-7.5.0 -fsanitize=thread -fsanitize-recover=all -shared -static-libtsan -fno-omit-frame-pointer -DEnable_AddressSanitizer -DAnalyze_thread
else ifeq ($(memcheck), leak)
CC= gcc-7.5.0 -fsanitize=leak -fsanitize-recover=all -static-liblsan -fno-omit-frame-pointer -DEnable_AddressSanitizer -DAnalyze_leak
else ifeq ($(memcheck), undefined)
CC= gcc-7.5.0 -fsanitize=undefined -fsanitize-recover=all -fno-omit-frame-pointer -DEnable_AddressSanitizer -DAnalyze_undefined
endif
#makefile 必须要有标签
all:
${CC} -g address.cpp -o address -I. -L. -lpthread -fPIC -lstdc++
编译脚本 build.sh
#!/bin/bash
if [[ ${1} == memcheck=address ]];
then
make $1
elif [[ ${1} == memcheck=thread ]];
then
make $1
elif [[ ${1} == memcheck=leak ]];
then
make $1
elif [[ ${1} == memcheck=undefined ]];
then
make $1
else
make
fi
编译脚本使用示例:
./build.sh memcheck=address #生成用于检测内存错误的应用程序
./build.sh memcheck=thread #生成用于检测多线程竞争的应用程序
./build.sh memcheck=leak #生成用于检测内存泄露的应用程序
./build.sh memcheck=undefined #生成用于检测未定义操作的应用程序
3.设置运行环境
上述步骤为编译环境操作,如下是 运行环境的设置。需要设置gcc7.5.0的路径信息:导入gcc7.5.0自带的lib64路径
#若设置用户的环境变量,需执行source命令
export LD_LIBRARY_PATH=/opt/gcc-7.5.0/lib64:$LD_LIBRARY_PATH
#告诉address检测到异常不退出进程
export ASAN_OPTIONS=halt_on_error=0
4.运行程序
# 注:运行环境要有gcc-7.5.0:拷贝gcc-7.5.0.tar 解压至opt目录
./address
5.分析内存检测报告
# 运行一段时间后,kill掉进程,程序会自动生成内存检测报告
kill -3 `pidof address`
内存检测报告:
[root@localhost test_AddressSanitizer]# cat yangyulong_Analyze_address_2021-08-10\ 17_31_42.log
=================================================================
==29819==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0000005105c6 at pc 0x00000045b832 bp 0x7ffd120818f0 sp 0x7ffd120810a0
READ of size 128 at 0x0000005105c6 thread T0
#0 0x45b831 in __interceptor_memcpy ../../.././libsanitizer/asan/asan_interceptors.cc:456
#1 0x4fa7f9 in test_func() /home/debug/test_AddressSanitizer/address.cpp:90
#2 0x4fa788 in main /home/debug/test_AddressSanitizer/address.cpp:80
#3 0x7f5f846a8b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
#4 0x405dcb (/home/debug/test_AddressSanitizer/address+0x405dcb)
0x0000005105c6 is located 0 bytes to the right of global variable '*.LC9' defined in 'address.cpp' (0x5105c0) of size 6
'*.LC9' is ascii string '2.0.1'
SUMMARY: AddressSanitizer: global-buffer-overflow ../../.././libsanitizer/asan/asan_interceptors.cc:456 in __interceptor_memcpy
Shadow bytes around the buggy address:
0x00008009a060: 01 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9
0x00008009a070: 00 04 f9 f9 f9 f9 f9 f9 00 01 f9 f9 f9 f9 f9 f9
0x00008009a080: 00 00 00 f9 f9 f9 f9 f9 03 f9 f9 f9 f9 f9 f9 f9
0x00008009a090: 00 00 00 00 00 00 00 00 00 05 f9 f9 f9 f9 f9 f9
0x00008009a0a0: 00 00 f9 f9 f9 f9 f9 f9 00 00 00 06 f9 f9 f9 f9
=>0x00008009a0b0: 00 00 07 f9 f9 f9 f9 f9[06]f9 f9 f9 f9 f9 f9 f9
0x00008009a0c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00008009a0d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00008009a0e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00008009a0f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00008009a100: 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
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
=================================================================
==29819==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 73400320 byte(s) in 7 object(s) allocated from:
#0 0x4c0610 in operator new[](unsigned long) ../../.././libsanitizer/asan/asan_new_delete.cc:82
#1 0x4fa803 in test_func() /home/debug/test_AddressSanitizer/address.cpp:92
#2 0x4fa788 in main /home/debug/test_AddressSanitizer/address.cpp:80
#3 0x7f5f846a8b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
Direct leak of 96 byte(s) in 6 object(s) allocated from:
#0 0x4c0470 in operator new(unsigned long) ../../.././libsanitizer/asan/asan_new_delete.cc:80
#1 0x4fa7a9 in test_func() /home/debug/test_AddressSanitizer/address.cpp:89
#2 0x4fa788 in main /home/debug/test_AddressSanitizer/address.cpp:80
#3 0x7f5f846a8b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
Indirect leak of 774 byte(s) in 6 object(s) allocated from:
#0 0x4c0610 in operator new[](unsigned long) ../../.././libsanitizer/asan/asan_new_delete.cc:82
#1 0x4fa8ba in Cert::Cert() /home/debug/test_AddressSanitizer/address.h:13
#2 0x4fa7b4 in test_func() /home/debug/test_AddressSanitizer/address.cpp:89
#3 0x4fa788 in main /home/debug/test_AddressSanitizer/address.cpp:80
#4 0x7f5f846a8b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
SUMMARY: AddressSanitizer: 73401190 byte(s) leaked in 19 allocation(s).
检测报告各部分介绍:
- ERROR:指出错误类型是global-buffer-overflow
- READ:指出线程名thread T0,操作为READ,发生的位置是address.cpp:90
- SUMMARY:前面输出的概要说明