上一章中,我们提出了排查内存泄漏是否存在的几种思路,但只是停留在理论上,本章就这几种思路提供具体的解决方案。在本章节,将以具体的例子的方式来演示如何定位内存泄漏的具体位置。
追踪内存泄漏的工具有很多,网上一搜能找到一大片,但针对不同的系统,均有不同的玩法,且有些东西,如果不是实际实战一把,真到了用的时候,仍然会眼高手低,不知如何下手。笔者就自己在实际工作中常用的几种手段,具体来说明一下一些常用的调试手段。
主要用到的技术有:
valgrind
mtrace
内存追踪eBPF
调试工具:bcc
- 手动
hook
C++
运算符重载
本章主要介绍内存泄漏调试神器:valgrind
。
valgrind
是Linux
系统下一个检测内存泄露的神器。相信很多C/C++
的初学者对该工具都不陌生,很多内存检测的入门教程都会提到该工具。事实上,在Linux
环境下,这个工具的确非常好用。
valgrind
之所以能够检测出内存泄漏,主要得益于其内部包含的一款叫做Memcheck
的工具。该工具建立了两个表,Valid-Value
表和Valid-Address
表。Valid-Value
负责记录进程地址空间中每个字节在 寄存器中的值是否有效。Valid_Address
表负责记录该地址是否能够被读写。
当要读写内存中的某个字节时,首先检测出这个字节对应的Value
是否有效,如果无效,则memcheck
报告读写错误。
同时,valgrind
提供了一个类似sandbox
的虚拟CPU
环境,这样当内存中某个字节被加载到真实CPU
中去时,该字节同样也会被加载到valgrind
提供的虚拟CPU
环境中,一旦这个值被读写,那么memcheck
就会检查Valid-Value
表,如果该值没有被初始化,就会报未初始化内存错误。
使用Memcheck
,可以检查很多内存方面的问题,诸如:
- 对未初始化内存的使用
- 内存释放后再次被使用
- 访问非法内存地址
- 内存泄漏,即指向一块内存的指针永久性地丢失
- 内存被
double free
memcpy
相关函数中的dst
和src
指针重叠
下面,我们就以一个简单的例子来探讨下,如何使用valgrind
来检测内存泄露的问题。
首先,valgrind
工具需要安装,安装命令如下:
yum install -y valgrind
valgrind
的命令行参数解释:
选项 | 说明 |
---|---|
-tool=<name> | 运行 valgrind 中名为toolname 的工具。默认m`emcheck |
h –help | 显示帮助信息 |
-version | 显示valgrind 内核的版本 |
q –quiet | 静默运行,只打印错误信息 |
v –verbose | 更详细的信息, 增加错误数统计 |
-trace-children=no|yes | 跟踪子线程? [no ] |
-track-fds=no|yes | 跟踪打开的文件描述符?[no ] |
-time-stamp=no|yes | 增加时间戳到LOG 信息? [no ] |
-log-fd=<number> | 输出LOG 到描述符文件 [2=stderr ] |
-log-file=<file> | 将输出的信息写入到filename.PID 的文件里,PID 是运行程序的进行ID |
-log-file-exactly=<file> | 输出LOG 信息到 file |
-log-file-qualifier=<VAR> | 取得环境变量的值来做为输出信息的文件名。 [none ] |
-log-socket=ipaddr:port | 输出LOG 到socket ,ipaddr:port |
-xml=yes | 将信息以xml 格式输出,只有memcheck 可用 |
-num-callers=<number> | 调用栈层级[12 ] |
error-limit=no|yes | 如果太多错误,则停止显示新错误? [yes ] |
-error-exitcode=<number> | 如果发现错误则返回错误代码 [0=disable ] |
-db-attach=no|yes | 当出现错误,valgrind 会自动启动调试器gdb 。[no ] |
-db-command=<command> | 启动调试器的命令行选项[gdb -nw %f %p ] |
-leak-check=no|summary|full | 要求对leak给出详细信息? [summary ] |
-leak-resolution=low|med|high | 泄漏检查中合并多少bt [low ] |
-show-reachable=no|yes | 在泄漏检查中显示可达块[no ] |
接下来,我们以一个具体的例子来说明下如何使用valgrind来检查内存泄漏。示例代码如下:
#include<malloc.h>
int main(void){
void *p1 = malloc(10);
void *p2 = malloc(20);
free(p1);
void *p3 = malloc(30);
free(p3);
return 0;
}
运行命令如下:
gcc leak.c -g
valgrind --tool=memcheck --leak-check=full ./a.out
运行后,得到如下结果:
==229181== Memcheck, a memory error detector
==229181== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==229181== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==229181== Command: ./a.out
==229181==
==229181==
==229181== HEAP SUMMARY:
==229181== in use at exit: 20 bytes in 1 blocks
==229181== total heap usage: 3 allocs, 2 frees, 60 bytes allocated
==229181==
==229181== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==229181== at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==229181== by 0x40059C: main (leak.c:5)
==229181==
==229181== LEAK SUMMARY:
==229181== definitely lost: 20 bytes in 1 blocks
==229181== indirectly lost: 0 bytes in 0 blocks
==229181== possibly lost: 0 bytes in 0 blocks
==229181== still reachable: 0 bytes in 0 blocks
==229181== suppressed: 0 bytes in 0 blocks
==229181==
==229181== For lists of detected and suppressed errors, rerun with: -s
==229181== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
我们关注一下摘要里的信息,第9
行显示,有3
次申请,2
次释放,共60
字节内存。第11~13
行则报告了内存泄漏的信息,说有一次20
字节的内存被丢失,具体位置为leak.c
的第5
行。
通过代码,可以定位到leak.c
的第5
行为void *p2 = malloc(20)
,的确p2
的内存我们没有释放。
从以上消息中,可以看出valgrind
对于内存泄漏的定位还是非常准确的,因此不失为一个内存泄漏调试的好手段。
但valgrind
也有局限性,首先它需要提前安装,因此对于生产环境几乎不可能线上实时检测;其次,由于其原理是虚拟出一个CPU
环境,因此对资源的占用也比较厉害。最后,valgrind
仅Linux
环境可用,如果你的应用程序不是运行在Linux
环境下,则无法使用,这也算是它的一个最大硬伤。
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师