ZyLiu's Blog

内存泄露检测--mtrace的使用

字数统计: 1.1k阅读时长: 5 min
2018/09/30 Share

代码配置

首先检测单一文件的内存泄漏。

有以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <mcheck.h>
#include <unistd.h>

int main() {
setenv("MALLOC_TRACE", "mem.log", 1);
mtrace();

int cnt = 100;
while (0 != cnt--) {
int *p = (int *)malloc(2 * sizeof(int));
usleep(10);
}
printf("over\n");

return 0;
}

其中mtrace()依赖于头文件mcheck.h,其只能检测使用glibc的内存分配函数分配的内存,也就是malloc()等内存分配函数,而不支持C++中的new操作符。
usleep()依赖头文件unistd.h,其休眠1us。

需要export MALLOC_TRACE=mem.log来指定工作路径,从而指定生成的log。
或者调用setenv("MALLOC_TRACE", "mem.log", 1);。依赖于头文件stdlib.h

使用如下make文件进行编译,重点在于必须加-g,编译优化选项必须为-O0(或者默认):

1
2
3
4
5
test : test.o
gcc -o $@ $^

test.o : test.cpp
gcc -c -O0 $^ -g

查看log

代码运行后生成log,使用命令mtrace progname logname进行log查看。
其中progname为二进制文件名,logname为log的文件名。
如果没有内存泄漏会报No memory leaks.。而上述程序的输出结果为:

1
2
3
4
5
6
Memory not freed:
-----------------
Address Size Caller
0x00000000023be6a0 0x8 at /home/zyliu12/Documents/memoryleak/onefile/test.cpp:12
0x00000000023be6c0 0x8 at /home/zyliu12/Documents/memoryleak/onefile/test.cpp:12
...

从而将所有已malloc而未free的内存的malloc处打印出来。

编译生成库文件

首先将内存泄露的函数转移到另一个文件fun.cpp中:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <unistd.h>
#include "fun.h"

int memleak() {
int cnt = 100;
while (0 != cnt--) {
int *p = (int *)malloc(2 * sizeof(int));
usleep(10);
}
int *p2 = (int *)malloc(40 * sizeof(int));
return 0;
}

main.cpp中调用该函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <mcheck.h>
#include "fun.h"
#include <stdlib.h>
#include <stdio.h>

int main() {
setenv("MALLOC_TRACE", "taoge.log", 1);
mtrace();
memleak();
printf("over\n");

return 0;
}

这样mtrace()与内存泄漏的函数不在一个文件中,我们来看看能否检测出内存泄漏的位置。

一步生成二进制文件的makefile为:

1
2
3
4
5
6
7
8
9
10
TARGET := test
SRC_CXX := fun.cpp main.cpp

all : clean $(TARGET)

clean :
-@rm -rf *.o *.gch $(TARGET)

$(TARGET) : $(SRC_CXX)
gcc -o $@ $^ -g

运行test并使用命令mtrace test taoge.log看到以下内容:

1
2
3
4
5
6
Memory not freed:
-----------------
Address Size Caller
0x000000000098e6a0 0x8 at /home/zyliu12/Documents/memoryleak/staticlib/fun.cpp:9
...
0x000000000098f320 0xa0 at /home/zyliu12/Documents/memoryleak/staticlib/fun.cpp:12

此时能够正常判断内存泄露。

使用以下make文件先将fun.cpp生成为动态库so文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TARGET := test
SRC_CXX := main.cpp
SHARED := libfun.so

all : clean $(SHARED) $(TARGET)

clean :
-@rm -rf *.o *.gch $(TARGET) $(SHARED)

$(SHARED) : fun.cpp
gcc -g $^ -fPIC -shared -Wl,-soname,$(SHARED) -o $(SHARED) -lc

$(TARGET) : $(SRC_CXX)
gcc -o $@ $^ -g -L. -lfun

在执行二进制文件之前,需要先设置工作路径:

1
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}

运行二进制文件生成log,使用命令mtrace test taoge.log看到以下内容:

1
2
3
4
0x0000000000b466a0    0x400  at 0x7fd589037185
...
0x0000000000b47690 0x8 at 0x7fd58939377a
0x0000000000b476b0 0x8 at 0x7fd58939377a

可以看到只有地址而没有代码行号。

定位内存泄漏行号

主要是通过addr2line由地址定位到行号。

为了得到加载库的地址,首先在代码中添加:

1
fprintf(stderr, "pid=%d\n", getpid());

这行代码可用于获得程序的pid。在获得程序pid之后,假设pid为7954,进入/proc目录,执行指令cat 7954/maps。其输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
00400000-00401000 r-xp 00000000 08:01 404531                             /home/zyliu12/Documents/memoryleak/staticlib/test
00600000-00601000 r--p 00000000 08:01 404531 /home/zyliu12/Documents/memoryleak/staticlib/test
00601000-00602000 rw-p 00001000 08:01 404531 /home/zyliu12/Documents/memoryleak/staticlib/test
017b2000-017d3000 rw-p 00000000 00:00 0 [heap]
7fb30a05e000-7fb30a21e000 r-xp 00000000 08:01 660718 /lib/x86_64-linux-gnu/libc-2.23.so
7fb30a21e000-7fb30a41d000 ---p 001c0000 08:01 660718 /lib/x86_64-linux-gnu/libc-2.23.so
7fb30a41d000-7fb30a421000 r--p 001bf000 08:01 660718 /lib/x86_64-linux-gnu/libc-2.23.so
7fb30a421000-7fb30a423000 rw-p 001c3000 08:01 660718 /lib/x86_64-linux-gnu/libc-2.23.so
7fb30a423000-7fb30a427000 rw-p 00000000 00:00 0
7fb30a427000-7fb30a428000 r-xp 00000000 08:01 404409 /home/zyliu12/Documents/memoryleak/staticlib/libfun.so
7fb30a428000-7fb30a627000 ---p 00001000 08:01 404409 /home/zyliu12/Documents/memoryleak/staticlib/libfun.so
7fb30a627000-7fb30a628000 r--p 00000000 08:01 404409 /home/zyliu12/Documents/memoryleak/staticlib/libfun.so
7fb30a628000-7fb30a629000 rw-p 00001000 08:01 404409 /home/zyliu12/Documents/memoryleak/staticlib/libfun.so
7fb30a629000-7fb30a64f000 r-xp 00000000 08:01 660690 /lib/x86_64-linux-gnu/ld-2.23.so
7fb30a833000-7fb30a836000 rw-p 00000000 00:00 0
7fb30a84c000-7fb30a84e000 rw-p 00000000 00:00 0
7fb30a84e000-7fb30a84f000 r--p 00025000 08:01 660690 /lib/x86_64-linux-gnu/ld-2.23.so
7fb30a84f000-7fb30a850000 rw-p 00026000 08:01 660690 /lib/x86_64-linux-gnu/ld-2.23.so
7fb30a850000-7fb30a851000 rw-p 00000000 00:00 0
7ffd7c1e6000-7ffd7c207000 rw-p 00000000 00:00 0 [stack]
7ffd7c23b000-7ffd7c23d000 r--p 00000000 00:00 0 [vvar]
7ffd7c23d000-7ffd7c23f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

可以看到libfun.so的加载地址以7fb30a427000开始。
再次查看mtrace test taoge.log

1
0x00000000017b26a0      0x8  at 0x7fb30a42771e

0x7fb30a42771e - 0x7fb30a427000可得0x00000000071e,这就是出错代码在libfun.so中的相对位置。
执行指令addr2line -e ./libfun.so 0x00000000071e,可得出错代码行号:

1
/home/zyliu12/Documents/memoryleak/staticlib/fun.cpp:10

CATALOG
  1. 1. 代码配置
  2. 2. 查看log
  3. 3. 编译生成库文件
  4. 4. 定位内存泄漏行号