内存泄漏定位工具之 mtrace(二)

1 前言

        mtrace(memory trace),是 GNU Glibc 自带的内存问题检测工具,它可以用来协助定位内存泄露问题。它的实现源码在glibc源码的malloc目录下,其基本设计原理为设计一个函数 void mtrace (),函数对 libc 库中的 malloc/free 等函数的调用进行追踪,由此来检测内存是否存在泄漏的情况。

        上篇讲到如何使用 mtrace 检查可执行程序中的内存泄漏,可以定位到源码的行数,但是存在一个问题,它没办法直接显示动态库所在的源码行数,而 Linux 开发动态库的使用是十分常见的,那么该篇就讲讲 mtrace 如何定位动态库中的内存泄漏问题。


2 使用方式

首先我们需要在当前目录下创建两个文件(一个编译成动态库,另一个编译成可执行文件)

2.1 源码示例

inter.c 动态库源码

#include <stdlib.h>
#include <stdio.h>

void InterTest(void)
{
    int *p = (int *)malloc(10 * sizeof(int));

    printf("InterTest: p = %p\n", p);
}

编译动态库(-g 必须的,不然定位源码位置都是 ???)

gcc -g inter.c -fPIC -shared -o libinter.so

可执行文件 test.c(和上篇中的测试代码比较,多个动态库的接口调用和睡眠等待时间)

#include <mcheck.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

// 测试代码,没有接口头文件,所以使用 extern
extern void InterTest(void);

int main(int argc, char **argv)
{
    mtrace();  // 开始跟踪

    char *p = (char *)malloc(100);

    free(p);
    p = NULL;

    InterTest();

    p = (char *)malloc(100);

    muntrace();   // 结束跟踪,并生成日志信息

    int wait = 20;

    //等待的目的是方便后面操作
    while (wait--)
    {
        sleep(1);
        printf("wait = %d\n", wait);
    }

    return 0;
}

编译成可执行文件

gcc -g test.c -L"/home/const/workspace/memtest" -linter -o test

2.2 运行程序

首先先设置生成跟踪的日志路径

export MALLOC_TRACE=./test.log

接着执行程序,同时 ps 查看当前可执行程序的 PID

./test &  // & 的作用是后台运行,方便在等待期间进行 ps 等操作
const@server:~/workspace/memtest$ ps

  PID TTY          TIME CMD
 1631 pts/62   00:00:00 bash
10723 pts/62   00:00:00 test
10745 pts/62   00:00:00 ps

可以看到此时的 test 执行程序的 PID 为 10745,接着输入以下指令查看加载的动态库信息(确保 test 还在执行)

const@server:~/workspace/memtest$ cat /proc/10723/maps

00400000-00401000 r-xp 00000000 08:01 144706317                          /home/const/workspace/memtest/test
00600000-00601000 r--p 00000000 08:01 144706317                          /home/const/workspace/memtest/test
00601000-00602000 rw-p 00001000 08:01 144706317                          /home/const/workspace/memtest/test
021f8000-02219000 rw-p 00000000 00:00 0                                  [heap]
7f59842e3000-7f59844a3000 r-xp 00000000 08:16 4723539                    /lib/x86_64-linux-gnu/libc-2.23.so
7f59844a3000-7f59846a3000 ---p 001c0000 08:16 4723539                    /lib/x86_64-linux-gnu/libc-2.23.so
7f59846a3000-7f59846a7000 r--p 001c0000 08:16 4723539                    /lib/x86_64-linux-gnu/libc-2.23.so
7f59846a7000-7f59846a9000 rw-p 001c4000 08:16 4723539                    /lib/x86_64-linux-gnu/libc-2.23.so
7f59846a9000-7f59846ad000 rw-p 00000000 00:00 0 
7f59846ad000-7f59846ae000 r-xp 00000000 08:01 144706316                  /home/const/workspace/memtest/libinter.so
7f59846ae000-7f59848ad000 ---p 00001000 08:01 144706316                  /home/const/workspace/memtest/libinter.so
7f59848ad000-7f59848ae000 r--p 00000000 08:01 144706316                  /home/const/workspace/memtest/libinter.so
7f59848ae000-7f59848af000 rw-p 00001000 08:01 144706316                  /home/const/workspace/memtest/libinter.so
7f59848af000-7f59848d5000 r-xp 00000000 08:16 4723525                    /lib/x86_64-linux-gnu/ld-2.23.so
7f5984a9e000-7f5984aa1000 rw-p 00000000 00:00 0 
7f5984ad3000-7f5984ad4000 rw-p 00000000 00:00 0 
7f5984ad4000-7f5984ad5000 r--p 00025000 08:16 4723525                    /lib/x86_64-linux-gnu/ld-2.23.so
7f5984ad5000-7f5984ad6000 rw-p 00026000 08:16 4723525                    /lib/x86_64-linux-gnu/ld-2.23.so
7f5984ad6000-7f5984ad7000 rw-p 00000000 00:00 0 
7fffedca8000-7fffedcca000 rw-p 00000000 00:00 0                          [stack]
7fffedde7000-7fffeddea000 r--p 00000000 00:00 0                          [vvar]
7fffeddea000-7fffeddec000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

2.3 分析定位

首先输入指令得到内存泄漏信息

const@server:~/workspace/memtest$ mtrace test ./test.log

Memory not freed:
-----------------
           Address     Size     Caller
0x00000000021f8450     0x64  at /home/const/workspace/memtest/test.c:19
0x00000000021f84c0     0x28  at 0x7f59846ad702
0x00000000021f84f0    0x400  at 0x7f59843501d5

        从日志文件中可以看到,可执行文件的内存泄漏直接就显示了源码的具体位置,而动态库中的泄漏没有具体显示,只是机器码的地址信息,则 libinter.so 的加载地址为 0x7f59846ad000-0x7f59848af000,其中上述符合的为 0x7f59846ad702,减去 libinter.so 的 base 地址 0x7f59846ad000得到相对偏移地址为 0x702。

通过使用 "addr2line" 命令工具,得到动态库源文件的行数,定位内存泄漏的位置。

const@server:~/workspace/memtest$ addr2line -e libinter.so 0x702

/home/const/workspace/memtest/inter.c:6

至于最后一行的 0x00000000021f84f0    0x400  at 0x7f59843501d5,通过地址比较不难看出是 /lib/x86_64-linux-gnu/libc-2.23.so 中的内存信息,通过上述相同的操作打印的内容可能是 ??,原因是没有 -g 编译后的调试信息;这个其实可以忽略,标准库中基本不会存在这种问题,这种属于可执行程序运行结束后相关资源(后期自动回收处理)还没有及时释放(可以屏蔽动态库函数的调用和睡眠函数,这个就不存在了)。

posted @ 2022-06-10 19:04  大橙子疯  阅读(771)  评论(0编辑  收藏  举报