mtrace
是Linux
系统内核自带的一个内存追踪的函数,它会在每个内存申请函数malloc/realloc/calloc
的位置记录下信息,并在每个内存释放的位置记录下free
的内存信息,其中包含有内存申请的地址,内存申请的大小,释放内存的地址,释放内存的大小。
这其实是一种hook
技术,在malloc
和free
等内存申请释放的调用处,插入自己的一些代码,用来记录内存的信息,包括指针的地址,内存块的大小等。关于hook
技术,后面还会提到,这里就不深入探究了。
mtrace
的使用也很简单,首先我们要包含头文件 mcheck.h
, 然后使用mtrace
函数开启内存使用记录,你也可以使用muntrace
函数取消内存使用记录。这些内存使用记录会记录到MALLOC_TRACE
环境变量指向的文件中。
假设我们现在有一段程序如下:
//main.c #include<string.h> #include<stdio.h> #include<stdlib.h> #include<malloc.h> void func1(); void func2(); int main() { int i = 0; while(i++ < 20) func1(); return 0; } void func1() { func2(); } void func2() { char *str; str=(char *) malloc(1024*1024); strcpy(str,"testing"); }
在这段程序中,我们循环调用func1
函数20
遍,在func1
函数里,调用func2
函数去malloc
申请1M
内存,注意这1M
内存并没有释放,所以这段程序是会内存泄漏的。我们接下来使用mtrace
来检查,看是否能检查出来。
首先,我们修改程序如下:
//main_mtrace.c #include<string.h> #include<stdio.h> #include<stdlib.h> #include<malloc.h> #include<mcheck.h> void func1(); void func2(); int main() { mtrace(); int i = 0; while(i++ < 20) func1(); muntrace(); return 0; } void func1() { func2(); } void func2() { char *str; str=(char *) malloc(1024*1024); strcpy(str,"testing"); }
注意第6
行,第12
行和第15
行代码。我们只需要增加这三行代码就可以实现内存追踪(第15
行事实上可以不要,因为程序运行到这里就已经结束了)。
编译:
gcc -o main_mtrace main_mtrace.c -g
编译时注意两点,首先要保证O0
编译,不要开编译优化,其次需要加-g
选项,这是为了后面调试时做地址转换。
在运行前,需要设置环境变量 MALLOC_TRACE
, 如:
export MALLOC_TRACE=mtrace.log
然后运行:
./main_mtrace
运行完后,正常情况下会生成一个mtrace.log
文件,该文件是由MALLOC_TRACE
环境变量指定的:
[root@ck08 memleak]# l total 20 -rw-r--r-- 1 root root 351 May 1 17:31 main_mtrace.c -rwxr-xr-x 1 root root 9776 May 1 17:31 main_mtrace -rw-r--r-- 1 root root 1074 May 1 17:31 mtrace.log
我们可以查看一下其中的内容:
[root@ck08 memleak]# cat mtrace.log = Start @ ./main_mtrace:[0x400629] + 0x7f78520b1010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7851add010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78519dc010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78518db010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78517da010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78516d9010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78515d8010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78514d7010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78513d6010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78512d5010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78511d4010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78510d3010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850fd2010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850ed1010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850dd0010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850ccf010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850bce010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f7850acd010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78509cc010 0x100000 @ ./main_mtrace:[0x400629] + 0x7f78508cb010 0x100000 = End
以上内容,很难直观看出到底有没有内存泄漏问题,这时候我们需要借助于mtrace
工具。该工具是 glibc-utils
工具集中的一个,所以如果系统没有该命令的话,可以使用yum install -u glibc-utils
来安装。
mtrace
是一个perl
脚本,通过调用addr2line
来解析代码文件位置,因此,需要在编译选项中加入-g
才能正常工作。
其使用方式为:mtrace <execute_file> <mtrace_file>
, 如:
[root@ck08 memleak]# mtrace main_mtrace mtrace.log Memory not freed: ----------------- Address Size Caller 0x00007f78508cb010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78509cc010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850acd010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850bce010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850ccf010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850dd0010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850ed1010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7850fd2010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78510d3010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78511d4010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78512d5010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78513d6010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78514d7010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78515d8010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78516d9010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78517da010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78518db010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78519dc010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f7851add010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24 0x00007f78520b1010 0x100000 at /root/chenyc/src/ctest/memleak/main_mtrace.c:24
首先看第3
行,它告诉我们下面这些内存是没有被释放的,其次可以看到从第6
到第25
行,它提示在main_mtrace.c
第24
行有0x100000
大小的内存没有被释放,这个大小,转成10
进制,正好是1048576
字节,也就是1M
的内存。
因此,定位还是比较准的。
由于mtrace
使用malloc_hook
和return_addr
机制来实现内存的追踪,因此,局限性非常明显:
- 内存记录日志仅记录到
malloc
层面,无法记录更上层的调用栈,因此当工程封装比较复杂时,调用的内存申请特别多,那么日志记录的信息也会特别多,但是又无法准确定位到其调用栈的具体信息,因此,比较难以直接定位到到底是哪个使用场景出了问题。
上面的描述可能不那么直观,举个例子,假如有以下函数:
void allocation(){ ... malloc(); ... } void func1(){ allocation(); } void func2(){ allocation(); }
mtrace
仅能追踪到第3
行申请了内存,但到底是由func1
调用引起的,还是func2
调用引起的,则无法判断了。如果调用allocation
的函数还有很多,那么对于排查问题将十分困难。
- 暂无法追踪
mmap
函数映射的内存。对于有些内存池技术,如apr_pool
,对于大块的内存申请,都是直接使用mmap
映射内存,这种方式则无法追踪。 - 局限于
Linux
系统,如果你的应用是运行在Windows
或者AIX
等非Linux
系统上,则没有更多的办法。
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师