前言
使用C/C++编程,不可避免地会遭遇各种各样的内存异常。走查代码这种方式定位异常比较低效。在Linux平台下,Address Sanitizer是一个定位程序内存异常的高效分析工具。
一、Address Sanitizer简介
相比Valgrind,Address Sanitizer(ASan)要快很多,只会拖慢程序两倍左右。它包括一个编译器instrumentation模块和一个提供malloc()/free()替代项的运行时库。AddressSanitizer是gcc的一部分,本文采用gcc-7.5.0版本进行演示。
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 0x7ffdf0 sp 0x7ffda0 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 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: byte(s) leaked in 19 allocation(s).
检测报告各部分介绍:
- ERROR:指出错误类型是global-buffer-overflow
- READ:指出线程名thread T0,操作为READ,发生的位置是address.cpp:90
- SUMMARY:前面输出的概要说明
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233256.html原文链接:https://javaforall.net