内存泄露专题(4)mtrace内存追踪

简介: 内存泄露专题(4)mtrace内存追踪

mtraceLinux系统内核自带的一个内存追踪的函数,它会在每个内存申请函数malloc/realloc/calloc的位置记录下信息,并在每个内存释放的位置记录下free的内存信息,其中包含有内存申请的地址,内存申请的大小,释放内存的地址,释放内存的大小。

这其实是一种hook技术,在mallocfree等内存申请释放的调用处,插入自己的一些代码,用来记录内存的信息,包括指针的地址,内存块的大小等。关于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.c24行有0x100000大小的内存没有被释放,这个大小,转成10进制,正好是1048576字节,也就是1M的内存。

因此,定位还是比较准的。

由于mtrace使用malloc_hookreturn_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服务器开发/高级架构师

目录
相关文章
|
5月前
|
存储 网络虚拟化 索引
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
46 0
|
3月前
|
Ubuntu 架构师 Linux
内存泄露专题(5)动态内存追踪大杀器:bcc
内存泄露专题(5)动态内存追踪大杀器:bcc
43 0
|
3月前
|
架构师 C语言 C++
内存泄漏专题(2)如何判断程序有内存泄露
内存泄漏专题(2)如何判断程序有内存泄露
25 1
|
3月前
|
架构师 编译器 程序员
内存泄露专题(1)何为内存泄露
内存泄露专题(1)何为内存泄露
18 1
|
6月前
|
存储 缓存 监控
JVM第一讲:内存结构和内存分配,内存溢出和内存泄露
JVM第一讲:内存结构和内存分配,内存溢出和内存泄露
|
8月前
cocoscreator查内存泄露,绘制内存监视器
cocoscreator查内存泄露,绘制内存监视器
162 0
|
存储 缓存 算法
内存溢出、内存泄露的概述及常见情形
内存溢出、内存泄露的概述及常见情形
50732 3
内存溢出、内存泄露的概述及常见情形
|
监控 Java Android开发
记录一次内存泄漏追踪(二)
记录一次内存泄漏追踪(二)
|
存储 Java 容器
记录一次内存泄漏追踪(一)
记录一次内存泄漏追踪(一)
|
弹性计算 运维 监控
如何解决 Linux 内核调测两大难题:内存被改与内存泄露
一直以来,内核内存调测领域一直持续存在着两大行业难题: "内存被改" 和 "内存泄漏"。内存问题行踪诡异、飘忽不定,在 Linux 内核的调测问题中,是最让开发者头疼的 bug 之一,因为内存问题往往发生故障的现场已经是第 N 现场了,尤其是在生产环境上出现,截止目前并没有一个很有效的方案能够进行精准的线上 debug,导致难以排查、耗时耗力。
453 0
如何解决 Linux 内核调测两大难题:内存被改与内存泄露