内存泄露专题(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服务器开发/高级架构师

目录
相关文章
|
7月前
|
JavaScript 前端开发 Java
内存管理和内存泄露(闭包、作用域链)(三)
内存管理和内存泄露(闭包、作用域链)
71 0
|
7月前
|
自然语言处理 JavaScript 前端开发
内存管理和内存泄露(闭包、作用域链)(二)
内存管理和内存泄露(闭包、作用域链)
53 0
|
7月前
|
安全 Linux 编译器
内存泄漏检测组件的分析与实现(linux c)-mtrace工具使用
内存泄漏产生原因 在堆上使用malloc/remalloc/calloc分配了内存空间,但是没有使用free释放对应的空间。
173 0
|
4月前
|
JavaScript 前端开发 Java
JavaScript内存泄露大揭秘!你的应用为何频频“爆内存”?点击解锁救星秘籍!
【8月更文挑战第23天】在Web前端开发中,JavaScript是构建动态网页的关键技术。然而,随着应用复杂度增加,内存管理变得至关重要。本文探讨了JavaScript中常见的内存泄露原因,包括意外的全局变量、不当使用的闭包、未清除的定时器、未清理的DOM元素引用及第三方库引发的内存泄露。通过了解这些问题并采取相应措施,开发者可以有效避免内存泄露,提高应用性能。
63 1
|
5月前
|
存储 算法 Java
(四)JVM成神路之深入理解虚拟机运行时数据区与内存溢出、内存泄露剖析
前面的文章中重点是对于JVM的子系统进行分析,在之前已经详细的阐述了虚拟机的类加载子系统以及执行引擎子系统,而本篇则准备对于JVM运行时的内存区域以及JVM运行时的内存溢出与内存泄露问题进行全面剖析。
100 0
|
7月前
|
存储 JavaScript 前端开发
内存管理和内存泄露(闭包、作用域链)(一)
内存管理和内存泄露(闭包、作用域链)
73 0
|
7月前
|
Ubuntu 架构师 Linux
内存泄露专题(5)动态内存追踪大杀器:bcc
内存泄露专题(5)动态内存追踪大杀器:bcc
155 0
|
4月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
394 0
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
64 1
|
2月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。