首页 服务器系统 Linux

linux asan的使用

在我们的Linux开发中对于C和C++来说,最头疼的事情莫过于内存泄漏的排查,因此如何找到一种高效、方便快捷的内存检测工具迫在眉睫,在过往的历史中我们知道一个工具是valgrind,该工具确实也是一把测试好手,但是该工具的缺点是,当加入valgrind命令调试工具后,代码运行极其缓慢,对于一些高并发的场景不是特别的友好,因此在我在学习ceph时了解到一种工具asan,再次介绍给大家,希望给大家带来帮助,当然Asan的运行速度是valgrind的10倍以上

1.asan,AddressSanitizer,在gcc 4.8以上不包含4.8,被加入到编译器中,因此大家在使用asan之前需要自己检查下自己的编译器版本,当然这是google的工程师开发的工具

[root@ceph-1 ~]# gcc --version
gcc (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1)
Copyright (C) 2018 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.

[root@ceph-1 ~]# g++ --version
g++ (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1)
Copyright (C) 2018 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.

2.如何使用?

yum install libasan -y安装运行asan所依赖的asan库

asan的运行检测参数有很多,这里仅仅介绍一些常用的参数
"halt_on_error=0":检测内存错误后是否继续运行,0表示检测到内存错误后继续运行
"abort_on_error=1":在打印asan的内存检测日志后使用abort()函数退出进程还是_exit()退出,为1表示使用abort退出
":use_sigaltstack=0":输出信号类似条件变量、信号量之类的泄漏输出
":disable_coredump=0":运行asan是如果出现coredump文件,是否输出,0表示生成core文件
":detect_invalid_pointer_pairs=1":是否检测使用无效的内存指针,1表示检测
":detect_stack_use_after_return=1":是都检测释放后继续使用的栈内存,1表示检测
":malloc_context_size=20":当内存泄漏发生时,最多打印的内存调用栈层级,类似gdb的bt输出层级
":log_exe_name=1":输出的asan运行报告是否包含可执行程序名字
":log_path=/var/log/asan/asan.log":asan日志文件的生成路径
#include <iostream>

using namespace std;

extern "C" {//使用C++需要私用extern,如果C语言则不需要该关键字修饰
const char* __asan_default_options() {
    return "halt_on_error=0"
           ":abort_on_error=1"
           ":use_sigaltstack=0"
           ":disable_coredump=0"
           ":detect_invalid_pointer_pairs=1"
           ":detect_stack_use_after_return=1"
           ":malloc_context_size=20"
           ":log_exe_name=1"
           ":log_path=/var/log/asan/asan.log";
}
}

int main()
{
        int *a = new (int);

        cout << "hello" << endl;
        return 0;
}

g++ 1.cc -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking

运行可执行程序./a.out
程序运行结束后,查看就可以找到内存输出情况,输出内容比较详细会罗列出该出现问题内存的生存周期,何时申请哪里申请哪里释放等等信息,比较详细
/var/log/asan/asan.log.a.out.103252

注意__asan_default_options这个东西你也可以不写在程序内部,这个东西是啥,它是asan运行是配置选项,你也可以在程序运行之前自己设置环境变量也行

export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:disable_coredump=0:malloc_context_size=20:log_exe_name=1:log_path=/tmp/asan.log

有人问这东西是啥?

-fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking

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

这是asan的gcc的编译选项,就是告诉gcc或者g++你的编译时我那些功能是需要开启检测的。

可能有人会问,我的应用程序基本上运行后就不会主动退出,我是一个守护进程,对这种情况我如何调试呢?

回答这个问题,我们演示一个完整的demo你就理解了,其中也是遇到很多坑

1.将以上的__asan_default_options asan运行参数一定要写到你的main函数前面,不要写到其他文件中,不然不生效,就写在你的int main函数之前

2.在编译时注意要加上-fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking这样的参数,无论你的写的是Makefile还是CMakeLists.txt文件

CMake:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking")
Makefile:
export CFLAGS += -fsanitize=address -fsanitize=leak -fsanitize-address-use-after-scope -fsanitize-recover=address
export CXXFLAGS += -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-recover=address

3.当运行一段时间后,asan的日志只有在进程结束时才生成,这也好理解,你不退出它也不知道你最终释放了没,所以你的退出要优雅,不要强制退出kill -9,要kill 15让其慢慢退出,然后就在你自己配置的路径生成了你的内存检测日志文件

4.如果你引入了第三方库,那么就要一起开启asan,其中一个开启则程序就无法运行

5.明明第三方库也开始asan了,程序可以运行,但是就是追踪不到第三方库中的内存泄漏堆栈,比如自己的写的API接口,然后自己写一个c程序测试,c程序中自己主动new的对象,可以检测到但是自己写的库中的内存泄漏情况无法检测,对于这个问题的处理,我需要在编译第三方库时打开debug选项,就是加入-g -ggdb选项,大家可以下载一些开源的代码,了解一下-DCMAKE_BUILD_TYPE="Debug"这个选项打开时,加入了那些编译参数

最终的编译选项如下:DW_AT_producer    : (indirect string, offset: 0x1dba): GNU C++11 8.4.1 20200928 (Red Hat 8.4.1-1) -march=corei7 -msse4.2 -g -ggdb -std=c++11 -std=c++11 -fsanitize=address -fsanitize-recover=address -fno-exceptions -fpermissive -fno-rtti -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fsanitize-recover=address -fno-var-tracking -fPIC


相关推荐