使用 gperftools 检测内存泄露
创始人
2024-06-03 13:55:16

每个 C/C++ 程序员可能都经历过定位内存泄露问题的痛苦。一方面,为了减少内存泄露问题,在 C++ 程序中,我们应该尽量使用智能指针,在 C 程序中,我们也可以通过一些内存池技术来管理内存的申请与释放。另一方面,当内存泄露问题真的出现时,通过 gperftools 的 heap checker,我们也可以比较轻松地找到内存泄露的线索,缩短问题定位时间。

一、gperftools 简介

gperftools 是 google 开源的一组套件,提供了高性能的、支持多线程的 malloc 实现,以及一组优秀的性能分析工具。

二、安装 gperftools

2.1、下载源码

从 gperftools github 官网上下载最新版本的源码包:

wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.9.1/gperftools-2.9.1.tar.gz

2.2、解压源码包

tar -zxv -f gperftools-2.9.1.tar.gz

2.3、configure

cd gperftools-2.9.1
./configure

命令结束执行后出现一个报错:

configure: WARNING: No frame pointers and no libunwind. Using experimental backtrace capturing via libgcc. Expect crashy cpu profiler.

这是因为没有安装 libunwind。这里直接使用 yum 的方式安装:

yum install libunwind-devel

再次执行 ./configure,命令执行成功。

2.4、编译并安装

执行如下两个命令,进行编译并安装:

make
sudo make install

最后执行 ldconfig 更新动态库文件

2.5、确认安装成功

执行如下命令,确认 gperftools 安装成功

[root@36eab106d3bf gperftools-2.9.1]# pprof --version
pprof (part of gperftools 2.0)Copyright 1998-2007 Google Inc.This is BSD licensed software; see the source for copying conditions
and license information.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

三、内存泄露检测

gperftools 的 heap checker 组件可以用于检测 C++ 程序中的内存泄露问题。要使用 heap checker,总共分 3 步:

  • 链接 tcmalloc 库到应用程序中
  • 运行程序
  • 分析输出

3.1、链接 tcmalloc 库

heapchecker 是 tcmalloc 的一部分,所以为了在可执行程序中使用 heap checker,在应用程序的链接阶段使用 -ltcmalloc 链接 tcmalloc 库。

3.2、运行代码

使用 heap checker 的推荐方式是 完整程序运行模式,此时 heap checker 可以在程序的 main 函数开始前跟踪内存分配,然后在程序退出时再次检查。如果它发现来了任何内存泄露,heap checker 会直接终止程序(通过 exit(1)),并打印一条消息,告诉你接下来如何使用 pprof 来继续跟踪该内存泄露问题。

完整程序运行 的 heap checker 支持 4 种模式:

  • minimal:在程序初始化期间(即 main 函数运行前)不进行内存泄露检查
  • normal:正常模式,通过跟踪某块内存是否可以被 live object 来访问,来判断是否出现内存泄露
  • strict:类似于 normal 模式,但是对全局对象的内存泄露有一些额外的检查
  • draconian:在该模式下,只有所有申请的内存都被释放,才认为没有出现内存泄露

一般使用 normal 模式就可以满足日常要求了。

heap checker 的另一种使用方法是检测指定代码块是否出现了内存泄露。为了实现这一点,需要在代码片段的开始部分创建一个 HeapLeakChecker 结构体,并在结束部分调用 NoLeaks()。例如:

HeapLeakChecker heap_checker("test_foo");
{code that exercises some foo functionality;this code should not leak memory;
}
if (!heap_checker.NoLeaks()) assert(NULL == "heap memory leak");

需要注意,添加 HeapLeakChecker 只是在程序中添加了内存泄露检测代码,为了真实地检测程序是否出现了内存泄露,仍然需要运行程序,并打开 heap-checker。

env HEAPCHECK=local your_program

除了指定为 local 模式外,之前的 normal 等模式也是可以的,此时除了运行 local 检查外,还将进行 完整程序运行 检查。

当然,运行 heap checker 是有代价的。heap checker 需要记录每次内存申请时的调用栈信息,这就导致了使用 heap checker 时,程序需要消耗更多的内存,同时程序运行速度也更慢。另外,需要注意,由于 heap checker 内部使用了 heap profile 框架,所以不能同时运行 heap checker 和 heap profile。

3.3、忽略已知的内存泄露

对于已知的内存泄露,如果想让 heap checker 忽略这些内存泄露信息,可以在应用程序代码中添加中如下代码:

{HeapLeakChecker::Disabler disabler;
}

另一种方式是使用 IgnoreObject,它接收一个指针参数,对该参数所指向的对象将不再进行内存泄露检查。

3.4、使用 pprof 查看内存泄露结果

heap checker 运行结束时会打印基本的泄露信息,包括调用栈和泄露对象的地址。除此之外,还可以使用 pprof 命令行工具来可视化地查看调用栈。

四、示例

接下来通过一个示例讲述如何使用 gperftools 的 heap checker 来发现程序的内存泄露问题。

4.1、示例程序如下:

// Copyright (C) fuchencong.com#include int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}

4.2、编译程序,并链接 tcmalloc 库:

g++ -std=c++0x -g -o memory_leak memory_leak.cpp -ltcmalloc

4.3、运行程序

[root@36eab106d3bf gperftools-test]# env HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 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 4 bytes in 1 objects allocated from:@ 4008ff@ 400935@ 7fc9f81a6555@ 400829If the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16582._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf 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 m
Exiting with error code (instead of crashing) because of whole-program memory leaks

这里虽然检测出了内存泄露,但是并没有打印出调用栈的符号信息,根据提示,可能是 PPROF_PATH 环境变量没有正确设置。按照如下方式设置环境变量:

# echo $PPROF_PATH
# which pprof
/usr/local/bin/pprof
# export PPROF_PATH=/usr/local/bin/pprof

再次运行,可以看到已经打印出了内存泄露的栈信息:

[root@36eab106d3bf gperftools-test]# env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 largest leaks:
Using local file ./memory_leak.
Leak of 4 bytes in 1 objects allocated from:@ 4008ff func@ 400935 main@ 7f39d94db555 __libc_start_main@ 400829 _startIf the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16586._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf 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 m
Exiting with error code (instead of crashing) because of whole-program memory leaks

但是这个信息展现方式并没有直接指出问题产生的行数。我们可以使用其提示的指令,调用可视化工具

pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

4.4、错误解决办法

4.4.1、gv错误
gv: Unable to open the display.

说明无法打开显示器,也就是说,–gv选项,需要在带图形界面的系统上使用,我们可以用–svg选项,将生成svg,然后在浏览器打开,命令:

pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --svg > svg.svg
4.4.2、addr2line错误

/usr/bin/addr2line: DWARF error: could not find variable specification at offset 5fc4
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 60c0
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 6137

可以在编译的时候将-g换成-pg,例如

g++ -std=c++0x -pg -o memory_leak memory_leak.cpp -ltcmalloc

4.4.3、dot错误

sh: dot: command not found

安装graphviz

yum install graphviz

在这里插入图片描述

五、线上内存泄露检测

5.1、demo

文件夹中的四个文件

BUILD
WORKSPACE
memory_leak.cpp
start.sh
.bazelrc

BUILD文件

cc_binary(name = "memory_leak",linkopts = ["-ltcmalloc",],srcs = ["memory_leak.cpp"],
)

-ltcmalloc必须链接上

memory_leak.cpp文件

#include int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}

start.sh文件

export PPROF_PATH=/usr/local/bin/pprof
nohup env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak 2>&1 | tee  "./log.log">> "./log.log" 2>&1 & 

.bazelrc文件

build --copt=-O2
build --copt=-g
build --cxxopt=-std=c++17

WORKSPACE文件为空文件

编译命令

bazel build :memory_leak

如果在生成svg过程中,出现4.4.2错误,将.bazelrc文件中的-g换成-pg

六、参考

https://fuchencong.com/2021/04/22/develop-tools-1/

https://cloud.tencent.com/developer/article/1383795

相关内容

热门资讯

玩家必看教程!智星德州扑克辅助... 玩家必看教程!智星德州扑克辅助挂多功能透视工具,好像真的是有挂(有挂详细)1、该软件可以轻松地帮助玩...
记者揭秘!wpk辅助挂多功能透... 记者揭秘!wpk辅助挂多功能透视工具,原来存在有挂(有挂猫腻)1、全新机制【wpkai辅助工具激活码...
科普!德扑圈辅助挂多功能透视工... 科普!德扑圈辅助挂多功能透视工具,总是有挂(有挂教学)1、实时德扑圈透视辅助更新:用户可以随时随地访...
玩家必备攻略!WPK辅助挂多功... 玩家必备攻略!WPK辅助挂多功能透视工具,其实存在有挂(确实有挂)1、任何WPK透视是真的假的的玩家...
分享一款!智星菠萝德州辅助挂多... 分享一款!智星菠萝德州辅助挂多功能透视工具,总是是真的有挂(有挂神器)1)智星菠萝德州辅助插件:进一...