Address Sanitizer使用指南


前言

使用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可以检查如下几种内存异常:

  1. 内存错误操作:-fsanitize=address
  2. 多线程竞争:-fsanitize=thread
  3. 内存泄漏:-fsanitize=leak
  4. 未定义操作:-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).

检测报告各部分介绍:

  1. ERROR:指出错误类型是global-buffer-overflow
  2. READ:指出线程名thread T0,操作为READ,发生的位置是address.cpp:90
  3. SUMMARY:前面输出的概要说明

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值