C++内存泄漏排查

1,387 阅读11分钟

C++内存泄漏排查

对于C++程序员来说,内存问题是不可避免的。除了日常code的时候多注意,在遇到线上的内存泄漏问题我们多使用已有的工具对其进行排查。下面主要介绍三个常用的内存泄漏的检测工具。

valgrind

valgrind是大名鼎鼎的Linux上的开源工具包,其中有多个组件包括 Memcheck、Cachegrind、Callgrind、Helgrind、Massif等。我们使用valgrind的Memcheck 去检测内存泄漏。( valgrind资源占用很大,对于性能的影响很大 )

官方手册

安装

  • 下载

    wget https://sourceware.org/pub/valgrind/valgrind-3.18.1.tar.bz2
    
  • 解压

    tar -jxvf valgrind-3.18.1.tar.bz2
    
  • 编译并安装

    cd valgrind-3.18.1/
    ./configure
    make
    make install
    

使用

  • 源代码(valgrind_leak.cpp)

    #include <map>
    
    void leak() {
        std::map<int, int *> m_;
        m_[1] = new int(1);
        m_.erase(1);
    }
    int main() {
        leak();
    
        return 0;
    }
    
  • 编译

    g++ -g -o valgrind_leak valgrind_leak.cpp
    
  • 运行

    valgrind --leak-check=yes --track-origins=yes ./valgrind_leak
    
  • 结果

    image-20220219133625082

    可以看出报告上给出了具体的内存泄漏位置:在valgrind_leak.cpp的第五行出现了内存泄漏。

这只是一个简单的例子,线上的内存泄漏可能远比这复杂,而且valgrind只能在程序结束的时候才能给出报告(也可结合gdb等 使其不用退出程序,但是会更复杂些valgrind.org/docs/manual…),再加上资源消耗过大,所以有些程序不适合用valgrind。

AddressSanitizer(Asan)

Address Sanitizer是google的一个内存错误检测工具。它非常快,平均拖慢程序大概2倍。从LLVM 3.1版本开始Address Sanitizer是其中一部分,从gcc 4.8开始 Address Sanitizer也是gcc的一部分。

官方wiki

安装

Address Sanitizer作为gcc的一部分,所以我们只需要一个libasan.so动态库;如果没有 使用yum安装即可。

yum install libasan

使用

  • 源代码(asan.cpp)

    #include <map>
    
    void leak() {
        std::map<int, int *> m_;
        m_[1] = new int(1);
        m_.erase(1);
    }
    int main() {
        leak();
    
        return 0;
    }
    
  • 编译

    g++ -g -o asan asan.cpp -fsanitize=address -fno-omit-frame-pointer
    
  • 运行

    ./asan
    
    
  • 结果

    image-20220219140304738

gperftools

gperftools是google的性能分析工具,包括HEAP PROFILER、HEAP CHECKER、CPU PROFILER

安装

yum install gperftools

使用

  • 源代码(gperf.cpp)

    #include <map>
    
    void leak() {
        std::map<int, int *> m_;
        m_[1] = new int(1);
        m_.erase(1);
    }
    int main() {
        leak();
    
        return 0;
    }
    
  • 编译

    g++ -o gperf -g gperf.cpp
    
    
  • 运行

    LD_PRELOAD=/usr/lib64/libtcmalloc.so HEAPCHECK=draconian ./gperf
    
    WARNING: Perftools heap leak checker is active -- Performance may suffer
    Not looking for thread stacks; objects reachable only from there will be reported as leaks
    Leak check _main_ detected leaks of 463 bytes in 11 objects
    The 11 largest leaks:
    *** WARNING: Cannot convert addresses to symbols in output below.
    *** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?)
    *** If you cannot fix this, try running pprof directly.
    Leak of 232 bytes in 1 objects allocated from:
    	@ 7ff140431468
    	@ 7ff14111b446
    	@ 7ff141104d3e
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 56 bytes in 1 objects allocated from:
    	@ 7ff14110f982
    	@ 7ff141104b92
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 32 bytes in 1 objects allocated from:
    	@ 7ff14110f36b
    	@ 7ff141104b92
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 31 bytes in 1 objects allocated from:
    	@ 7ff141110b83
    	@ 7ff14110f4bc
    	@ 7ff141104b92
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 24 bytes in 1 objects allocated from:
    	@ 7ff1404f0bd1
    	@ 7ff140431671
    	@ 7ff14111b42c
    	@ 7ff141104d3e
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 24 bytes in 1 objects allocated from:
    	@ 7ff1404f0bd1
    	@ 7ff140431671
    	@ 7ff14111b446
    	@ 7ff141104d3e
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 20 bytes in 1 objects allocated from:
    	@ 7ff140431642
    	@ 7ff14111b42c
    	@ 7ff141104d3e
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 20 bytes in 1 objects allocated from:
    	@ 7ff140431642
    	@ 7ff14111b446
    	@ 7ff141104d3e
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 16 bytes in 1 objects allocated from:
    	@ 7ff14110789a
    	@ 7ff141104813
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 4 bytes in 1 objects allocated from:
    	@ 7ff14110bf82
    	@ 7ff14110f98d
    	@ 7ff141104b92
    	@ 7ff1414f78ba
    	@ 7ff1414f79ba
    	@ 7ff1414e8fda
    Leak of 4 bytes in 1 objects allocated from:
    	@ 400ae5
    	@ 400b5d
    	@ 7ff14041b493
    	@ 400a0e
    
    
    If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
    
    pprof ./gperf "/tmp/gperf.150872._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv
    
    If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
    If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more repeata
    Exiting with error code (instead of crashing) because of whole-program memory leaks
    

    可以看到报告中给出的Leak check _main_ detected leaks of 463 bytes in 11 objects。gperftools中包含tcmalloc,在执行完检查后 会在/tmp目录下生成一个heap文件,可以使用pprof来解析。

    pprof ./gperf "/tmp/gperf.150872._main_-end.heap" --lines --text --stack
    
    Using local file ./gperf.
    Using local file /tmp/gperf.150872._main_-end.heap.
    Total: 0.0 MB
    Stacks:
    
    32       (00007ff14110f36b) ??:0:HeapLeakChecker_InternalInitStart
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    20       (00007ff140431642) ??:0:__add_to_environ
             (00007ff14111b445) ??:0:MallocExtension::Initialize
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    20       (00007ff140431642) ??:0:__add_to_environ
             (00007ff14111b42b) ??:0:MallocExtension::Initialize
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    16       (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    24       (00007ff1404f0bd1) ??:0:__GI___tsearch
             (00007ff140431670) ??:0:__add_to_environ
             (00007ff14111b445) ??:0:MallocExtension::Initialize
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    56       (00007ff14110f982) ??:0:HeapLeakChecker_InternalInitStart
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    4        (0000000000400ae5) /data/mem_leak/gperf/gperf.cpp:5:leak
             (0000000000400b5c) /data/mem_leak/gperf/gperf.cpp:9:main
             (00007ff14041b492) ??:0:__libc_start_main
             (0000000000400a0d) ??:0:_start
    
    24       (00007ff1404f0bd1) ??:0:__GI___tsearch
             (00007ff140431670) ??:0:__add_to_environ
             (00007ff14111b42b) ??:0:MallocExtension::Initialize
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    4        (00007ff14110bf82) ??:0:HeapLeakChecker::HeapLeakChecker
             (00007ff14110f98c) ??:0:HeapLeakChecker_InternalInitStart
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    232      (00007ff140431468) ??:0:__add_to_environ
             (00007ff14111b445) ??:0:MallocExtension::Initialize
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    31       (00007ff141110b83) ??:0:std::__cxx11::basic_string::_M_append
             (00007ff14110f4bb) ??:0:HeapLeakChecker_InternalInitStart
             (00007ff1414f78b9) dl-init.c:0:call_init.part.0
             (00007ff1414f79b9) ??:0:_dl_init
             (00007ff1414e8fd9) ??:0:_dl_start_user
    
    Leak of 232 bytes in 1 objects allocated from:
    	@ 7ff140431468 unknown
    	@ 00007ff14111b445 MallocExtension::Initialize ??:0
    	@ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 56 bytes in 1 objects allocated from:
    	@ 7ff14110f982 unknown
    	@ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 32 bytes in 1 objects allocated from:
    	@ 7ff14110f36b unknown
    	@ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 31 bytes in 1 objects allocated from:
    	@ 7ff141110b83 unknown
    	@ 00007ff14110f4bb HeapLeakChecker_InternalInitStart ??:0
    	@ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 24 bytes in 1 objects allocated from:
    	@ 7ff1404f0bd1 unknown
    	@ 00007ff140431670 __add_to_environ :0
    	@ 00007ff14111b42b MallocExtension::Initialize ??:0
    	@ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 24 bytes in 1 objects allocated from:
    	@ 7ff1404f0bd1 unknown
    	@ 00007ff140431670 __add_to_environ :0
    	@ 00007ff14111b445 MallocExtension::Initialize ??:0
    	@ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 20 bytes in 1 objects allocated from:
    	@ 7ff140431642 unknown
    	@ 00007ff14111b42b MallocExtension::Initialize ??:0
    	@ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 20 bytes in 1 objects allocated from:
    	@ 7ff140431642 unknown
    	@ 00007ff14111b445 MallocExtension::Initialize ??:0
    	@ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 16 bytes in 1 objects allocated from:
    	@ 7ff14110789a unknown
    	@ 00007ff141104812 tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 4 bytes in 1 objects allocated from:
    	@ 7ff14110bf82 unknown
    	@ 00007ff14110f98c HeapLeakChecker_InternalInitStart ??:0
    	@ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0
    	@ 00007ff1414f78b9 call_init.part.0 dl-init.c:0
    	@ 00007ff1414f79b9 _dl_init :0
    	@ 00007ff1414e8fd9 _dl_start_user :0
    Leak of 4 bytes in 1 objects allocated from:
    	@ 00400ae5 unknown
    	@ 0000000000400b5c main /data/mem_leak/gperf/gperf.cpp:9
    	@ 00007ff14041b492 __libc_start_main ??:0
    	@ 0000000000400a0d _start ??:0
    
         0.0  58.7%  58.7%      0.0  69.1% __add_to_environ :0
         0.0  19.0%  77.8%      0.0  26.6% HeapLeakChecker_InternalInitStart ??:0
         0.0  10.4%  88.1%      0.0  10.4% __GI___tsearch :0
         0.0   6.7%  94.8%      0.0   6.7% std::__cxx11::basic_string::_M_append ??:0
         0.0   3.5%  98.3%      0.0  99.1% call_init.part.0 dl-init.c:0
         0.0   0.9%  99.1%      0.0   0.9% HeapLeakChecker::HeapLeakChecker ??:0
         0.0   0.9% 100.0%      0.0   0.9% leak /data/mem_leak/gperf/gperf.cpp:5
         0.0   0.0% 100.0%      0.0  69.1% MallocExtension::Initialize ??:0
         0.0   0.0% 100.0%      0.0   0.9% __libc_start_main ??:0
         0.0   0.0% 100.0%      0.0  99.1% _dl_init :0
         0.0   0.0% 100.0%      0.0  99.1% _dl_start_user :0
         0.0   0.0% 100.0%      0.0   0.9% _start ??:0
         0.0   0.0% 100.0%      0.0   0.9% main /data/mem_leak/gperf/gperf.cpp:9
    

有的时候内存泄漏并不一定是demo上的情况,也可能是自己分配的内存,但是后面忘了去用,就一直放在那里,也没有去清除。可以用gperftools的HEAP PROFILE工具去查看内存分配的热点。

HEAP PROFILE 官方手册

  • 源码

    #include <map>
    #include <unistd.h>
    
    void malloc_mem(std::map<int, int *> &p, int i) {
    
        p[i] = new int(i);
    }
    
    int main() {
        int N = 1000;
        std::map<int, int *> m_;
        while (N--) {
            malloc_mem(m_, N);
            sleep(100);
        }
    
        return 0;
    }
    
  • 编译

    g++ -o gperf_malloc -g gperf_malloc.cpp
    
  • 执行

    HEAPPROFILE=/tmp/heapprof HEAP_PROFILE_TIME_INTERVAL=10  LD_PRELOAD="/usr/lib64/libtcmalloc.so" ./gperf_malloc
    

    HEAP_PROFILE_TIME_INTERVAL 间隔多长时间产生一个dump文件

    HEAP_PROFILE_ALLOCATION_INTERVAL 内存消耗超过多少产生一个dump文件

    具体参数可见官方文档gperftools.github.io/gperftools/…

  • 分析

    # 生成txt
    pprof --lines --text ./gperf_malloc /tmp/heapprof.0001.heap > out.txt
    # 生成pdf
    pprof --lines --pdf ./gperf_malloc /tmp/heapprof.0001.heap > out.pdf
    
  • 对比分析

    生成的文件支持以一个为基准 进行比较分析,因为有的程序启动的时候会加载很多数据,需要分配很多内存,后续的持续内存分配 则可以用前几个的heap文件为基准去分析。比如以第一个为基准,查看第三个与第一个的差。

    # 生成txt
    pprof --lines --text --base /tmp/heapprof.0001.heap ./gperf_malloc /tmp/heapprof.0003.heap > diff.txt
    # 生成pdf
    pprof --lines --pdf --base /tmp/heapprof.0001.heap ./gperf_malloc /tmp/heapprof.0003.heap > diff.pdf
    

    diff.txt

    Total: 0.0 MB
         0.0  92.3%  92.3%      0.0  92.3% __gnu_cxx::new_allocator::allocate /usr/include/c++/8/ext/new_allocator.h:112
         0.0   7.7% 100.0%      0.0 100.0% malloc_mem /data/mem_leak/gperf/gperf_malloc.cpp:6
         0.0   0.0% 100.0%      0.0 100.0% __libc_start_main ??:0
         0.0   0.0% 100.0%      0.0 100.0% _start ??:0
         0.0   0.0% 100.0%      0.0 100.0% main /data/mem_leak/gperf/gperf_malloc.cpp:13
         0.0   0.0% 100.0%      0.0  92.3% std::_Rb_tree::_M_create_node /usr/include/c++/8/bits/stl_tree.h:642
         0.0   0.0% 100.0%      0.0  92.3% std::_Rb_tree::_M_emplace_hint_unique /usr/include/c++/8/bits/stl_tree.h:2421
         0.0   0.0% 100.0%      0.0  92.3% std::_Rb_tree::_M_get_node /usr/include/c++/8/bits/stl_tree.h:588
         0.0   0.0% 100.0%      0.0  92.3% std::allocator_traits::allocate /usr/include/c++/8/bits/alloc_traits.h:436
         0.0   0.0% 100.0%      0.0  92.3% std::map::operator[] /usr/include/c++/8/bits/stl_map.h:499
    
    • 第一列是直接分配的内存单位 MB.
    • 第四列包含自己及其所有被调用者的内存使用情况.
    • 第二列和第五列只是第一列和第四列中数字的百分比表示.
    • 第三列是第二列的累积和(即,第三列中的第 k 个条目是第二列中前 k 个条目的总和。)