用mtrace定位内存泄漏
一. 缘起
有的公众号读者,看完我上次写给大学生的查bug方法后,希望我多分享一些查bug的实践经验和具体步骤,比如如何查内存泄漏和core dump问题。所以,就打算写这篇文章。
二. 内存泄漏简介
内存泄漏,是一个谈虎色变的问题。我个人的基础非常差,大学毕业后,才第一次听说内存泄漏。当时,我有点懵圈,心想内存泄漏了,是要重新去买新的内存设备吗?很傻很天真!
后来,我又听说了很多次内存泄漏,查资料后才知道,原来这是一个软件层面的东西。比如,使用了malloc, 但没有使用free, 或者使用了new, 但没有使用delete, 都会造成内存泄漏。
为什么说内存泄漏会谈虎色变呢?因为:
内存泄漏危害性大
内存泄漏潜伏时间长
内存泄漏不容易定位
那么,如果内存泄漏了,我们该如何去定位呢?在C/C++相关的面试中,不问这个问题的面试官,是不合格的;不会回答这个问题的面试者,也是不合格的。当然,这种说法,似乎有一点绝对。
有的面试者回答,只有小心配对使用malloc/free和new/delete, 就能避免内存泄漏。显然,这种面试者缺乏基本的工程实践认知,缺乏对敌人的敬畏。
有的面试者回答,使用智能指针,就能避免内存泄漏。显然,这只是一种预防机制。在实际项目中,各种复杂因素导致的后果是内存已经泄漏,需要定位。
还有的面试者,更是想出了出人意料的答案,那就是检查代码!这又是对敌人缺乏了解啊。大型工程的代码动辄几十万行,谁敢走读代码来查内存泄漏呢?
对我而言,查杀bug是我的相对强项(实话说,架构能力需要加强)。mtrace和valgrind是典型的内存泄漏分析工具。今天,我们聊mtrace定位内存泄漏。
三. mtrace简介
我们来看下mtrace的用途:
ubuntu@VM-0-15-ubuntu:~$ man mtrace
MTRACE(1) Linux user manual MTRACE(1)
NAME
mtrace - interpret the malloc trace log
SYNOPSIS
mtrace [option]... [binary] mtracedata
DESCRIPTION
mtrace is a Perl script used to interpret and provide human readable output of the trace log contained in
the file mtracedata, whose contents were produced by mtrace(3). If binary is provided, the output of
mtrace also contains the source file name with line number information for problem locations (assuming
that binary was compiled with debugging information).
For more information about the mtrace(3) function and mtrace script usage, see mtrace(3).
显然,mtrace命令是用来分析malloc函数的trace log.
那么,这个trace log是怎么生成的呢? 且看上面的see mtrace(3). 有的朋友看到这里,不知道怎么敲命令了,以为是:
ubuntu@VM-0-15-ubuntu:~$ man mtrace(3)
-bash: syntax error near unexpected token `('
ubuntu@VM-0-15-ubuntu:~$
其实,正确的姿势如下:
ubuntu@VM-0-15-ubuntu:~$ man 3 mtrace
MTRACE(3) Linux Programmer's Manual MTRACE(3)
NAME
mtrace, muntrace - malloc tracing
SYNOPSIS
#include
void mtrace(void);
void muntrace(void);
DESCRIPTION
The mtrace() function installs hook functions for the memory-allocation functions (malloc(3), realloc(3)
memalign(3), free(3)). These hook functions record tracing information about memory allocation and deal[m
location. The tracing information can be used to discover memory leaks and attempts to free nonallocated
memory in a program.
The muntrace() function disables the hook functions installed by mtrace(), so that tracing information is
no longer recorded for the memory-allocation functions. If no hook functions were successfully installed
by mtrace(), muntrace() does nothing.
When mtrace() is called, it checks the value of the environment variable MALLOC_TRACE, which should con[m
tain the pathname of a file in which the tracing information is to be recorded. If the pathname is suc[m
cessfully opened, it is truncated to zero length.
If MALLOC_TRACE is not set, or the pathname it specifies is invalid or not writable, then no hook func[m
tions are installed, and mtrace() has no effect. In set-user-ID and set-group-ID programs, MALLOC_TRACE
is ignored, and mtrace() has no effect.
显然,mtrace函数是用来记录malloc的trace log的。
所以,对于mtrace, 我们有如下的基本认知:
mtrace函数记录malloc的trace log
mtrace命令分析上述记录的trace log
这也就是用mtrace来定位内存泄漏的原理。那么,具体如何来查内存泄漏呢?不要着急,继续往下看。
四. 用mtrace定位内存泄漏
首先,我们来写一段有内存泄漏的程序:
int main()
{
setenv("MALLOC_TRACE", "test.log", "1");
mtrace();
int *p = (int *)malloc(2 * sizeof(int));
return 0;
}
我们来分析一下这段程序:
setenv是设置相关环境变量。
mtrace函数记录malloc的trace log.
malloc函数用于分配堆内存
我们来编译并运行一下(注意在编译时带-g参数):
ubuntu@VM-0-15-ubuntu:~$ gcc -g test.c
ubuntu@VM-0-15-ubuntu:~$
ubuntu@VM-0-15-ubuntu:~$
ubuntu@VM-0-15-ubuntu:~$ ./a.out
ubuntu@VM-0-15-ubuntu:~$ ls test.log
test.log
ubuntu@VM-0-15-ubuntu:~$ cat test.log
= Start
@ ./a.out:[0x4005eb] + 0x1649570 0x8
@ /lib/x86_64-linux-gnu/libc.so.6:(clearenv+0x5d)[0x7f1bc48f7e9d] - 0x1649010
@ /lib/x86_64-linux-gnu/libc.so.6:(tdestroy+0x4cf)[0x7f1bc49c291f] - 0x16490e0
@ /lib/x86_64-linux-gnu/libc.so.6:[0x7f1bc4a3223c] - 0x1649100
显然,编译运行后,生成了trace log, 即test.log文件。用cat命令查看,貌似也发现不了什么东西,这是因为,姿势错了。
我们不仅仅要用test.log, 还要结合二进制文件a.out呢,如下:
ubuntu@VM-0-15-ubuntu:~$ mtrace a.out test.log
- 0x00000000018ab010 Free 3 was never alloc'd 0x7fb41725fe9d
- 0x00000000018ab0e0 Free 4 was never alloc'd 0x7fb41732a91f
- 0x00000000018ab100 Free 5 was never alloc'd 0x7fb41739a23c
Memory not freed:
-----------------
Address Size Caller
0x00000000018ab570 0x8 at /home/ubuntu/test.c:8
ubuntu@VM-0-15-ubuntu:~$
Oh, nice啊!终于查出是第8行,存在内存泄漏。接下来,我们打算修复代码,并再次验证。
五. 修复后再验证
修复内存泄漏后,代码为:
int main()
{
setenv("MALLOC_TRACE", "test.log", "1");
mtrace();
int *p = (int *)malloc(2 * sizeof(int));
free(p);
return 0;
}
编译运行,并查看是否有内存泄漏:
ubuntu@VM-0-15-ubuntu:~$ gcc -g test.c
ubuntu@VM-0-15-ubuntu:~$
ubuntu@VM-0-15-ubuntu:~$
ubuntu@VM-0-15-ubuntu:~$ ./a.out
ubuntu@VM-0-15-ubuntu:~$ mtrace a.out test.log
- 0x00000000006ad010 Free 4 was never alloc'd 0x7faa9b044e9d
- 0x00000000006ad0e0 Free 5 was never alloc'd 0x7faa9b10f91f
- 0x00000000006ad100 Free 6 was never alloc'd 0x7faa9b17f23c
No memory leaks.
ubuntu@VM-0-15-ubuntu:~$
看到No memory leaks后,心情就好了,没有内存泄漏了。
六. 最后的话
无论是笔试面试,还是平时工作,对于内存泄漏问题,都要有自己的一套处理办法。