使用 pmap 和 gdb 查找本机内存泄漏
Using pmap and gdb to find native memory leak
我正在调试 java 应用程序中的本机内存泄漏。 rss 每天增长 1GB,而堆没有增长。随着时间的推移比较 pmap 的输出,我看到多个匿名块被添加到堆顶部或两个本机库之间。
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- java
0000000000601000 4 4 4 rw--- java
00000000008fc000 64156 64028 64028 rw--- [ anon ]
00000006c0000000 2467840 2466824 2466824 rw--- [ anon ] (heap)
0000000756a00000 2775040 0 0 ----- [ anon ]
0000000800000000 39808 39636 39636 rw--- [ anon ]
00000008026e0000 607360 0 0 ----- [ anon ]
00007fc8f0000000 11268 10944 10944 rw--- [ anon ]
00007fc8f0b01000 54268 0 0 ----- [ anon ]
00007fc938000000 49204 46164 46164 rw--- [ anon ]
00007fc93b00d000 16332 0 0 ----- [ anon ]
00007fc940000000 126784 126784 126784 rw--- [ anon ]
00007fc947bd0000 4288 0 0 ----- [ anon ]
00007fc948000000 65512 65512 65512 rw--- [ anon ]
.....a lot of new anon blocks with memory 1012 and also ~64MB block shown in screenshot
00007fc98c448000 16 12 0 r-x-- sssd_pac_plugin.so
.....anon blocks with memormy increased over time:
00007fca747fe000 2044 0 0 ----- librmi.so
.....anon blocks with memormy increased over time:
00007fcb58015000 44 32 0 r-x-- libkrb5support.so.0.1
.............................................other libraries
00007fcbad8f8000 4 4 4 rw--- libnio.so
.....anon blocks with memormy increased over time like :
00007fcbb0000000 65420 65404 65404 rw--- [ anon ]
00007fcbc4f7e000 4820 4820 4820 rw--- [ anon ]
00007fcbc5433000 5420 0 0 ----- [ anon ]
00007fcbc597e000 90112 88172 88172 rwx-- [ anon ]
.....anon blocks with memormy increased over time
00007fcbd96ea000 44 16 0 r-x-- libjimage.so
...............................................other libraries
00007fcbdcdd9000 4 4 4 r---- ld-2.17.so
00007fcbdcdda000 4 4 4 rw--- ld-2.17.so
00007fcbdcddb000 4 4 4 rw--- [ anon ]
00007ffdbd52c000 140 40 40 rw--- [ stack ]
00007ffdbd578000 8 8 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 16585920 9216360 9206356
我可以说 sssd_pac_plugin.so 和 librmi.so 之间的内存增加是由于其中任何一个吗?这个内存分配是连续的吗?
https://i.stack.imgur.com/G1duY.jpg
在堆的顶部(地址:00007fc940000000 和更大)创建了很多新的内存块,从 126MB 到 1MB(100 个 1MB 的小块附图供参考)。它们是表示内存泄漏还是只是为每个新线程创建的。
- 为了查看这些块中的数据,我尝试了以下代码片段,但始终无法从中获取任何字符串。这都是我无法解释的二进制文件。有没有办法将其转换为字符串?或者映射到任何 thread/library 或我可以使用的任何东西。
gdb -pid <pid>
dump memory mem.bin 0x00007fc940000000 0x00007fc940000000+126784
#read file as:
strings mem.bin
另一个观察结果是,许多新块和旧块增加到大约 60-65MB。随着时间的推移,这些块的数量会增加很多。对rss增长贡献最大。
https://i.stack.imgur.com/xueC8.png
https://i.stack.imgur.com/hwbAb.jpg
我也尝试了 libtcmalloc 和分析器,主要问题是在生产环境中我无法使用它们。在开发实例上,泄漏并不那么严重,因此无法验证探查器的输出。
一个非常基本的方法:您可以尝试查看谁在呼叫 mmap
(而不是 munmap
)。
- 附加到进程
- 在
mmap
上设置断点,使用命令打印参数和回溯(可能 5 帧)并继续
munmap
类似
- 重定向输出
- 让它运行一天
- 分离
- 将
mmap
s 与输出中的 munmap
s 匹配
随着 pmap
周期性 运行ning 的出现,您可以将较新的匿名区域与 mmap
回溯相匹配(可能需要调整帧数)。
已经有这篇不错的小文章 LINUX GDB: IDENTIFY MEMORY LEAKS 可以帮助您入门。
注:
- 您正在寻找
mmap
和 munmap
,而不是 malloc
和 free
- 你必须找出 return 与
mmap
的偏移量
- 我还没有尝试过文章中的脚本,但我认为它可以实现文章所声称的效果
查找 mmap
return 指令偏移量(从 mmap
开始):
只需启动 gdb
同一主机上的任何可执行文件
[ aquila ~ ] $ gdb -q /usr/bin/ls
Reading symbols from /usr/bin/ls...Reading symbols from /usr/bin/ls...(no debugging symbols found)...done
.
(no debugging symbols found)...done.
Missing separate debuginfos, use: dnf debuginfo-install coreutils-8.27-5.fc26.x86_64
(gdb) set pagination off
(gdb) set breakpoint pending on
(gdb) b mmap
Function "mmap" not defined.
Breakpoint 1 (mmap) pending.
(gdb) r
Starting program: /usr/bin/ls
Breakpoint 1, 0x00007ffff7df2940 in mmap64 () from /lib64/ld-linux-x86-64.so.2
(gdb) disassemble
Dump of assembler code for function mmap64:
=> 0x00007ffff7df2940 <+0>: test %rdi,%rdi
0x00007ffff7df2943 <+3>: push %r15
0x00007ffff7df2945 <+5>: mov %r9,%r15
:
:
0x00007ffff7df2973 <+51>: mov [=10=]x9,%eax
:
0x00007ffff7df2982 <+66>: pop %rbx
:
0x00007ffff7df298a <+74>: pop %r15
0x00007ffff7df298c <+76>: retq
0x00007ffff7df298d <+77>: nopl (%rax)
:
:
0x00007ffff7df29d8 <+152>: mov [=10=]xffffffffffffffff,%rax
0x00007ffff7df29df <+159>: jmp 0x7ffff7df2982 <mmap64+66>
End of assembler dump.
注意这里的return指令:
0x00007ffff7df298c <+76>: retq
所以,在我的机器上,第二个断点必须设置在 (mmap+76)。
一旦你确定了这个偏移量,你就可以通过附加到你的目标进程并反汇编那个偏移量处的内容来验证这个偏移量。例如。以我目前的 shell 作为我的目标进程:
[ aquila ~ ] $ echo $$
9769
[ aquila ~ ] $ gdb -q
(gdb) attach 9769
Attaching to process 9769
Reading symbols from /usr/bin/bash...Reading symbols from /usr/bin/bash...(no debugging symbols found)..
.done.
(no debugging symbols found)...done.
Reading symbols from /lib64/libtinfo.so.6...Reading symbols from /lib64/libtinfo.so.6...(no debugging sy
mbols found)...done.
(no debugging symbols found)...done.
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Reading symbols from /lib64/libnss_files.so.2...(no debugging symbols found)...done.
0x00007fcfc67cc18a in waitpid () from /lib64/libc.so.6
Missing separate debuginfos, use: dnf debuginfo-install bash-4.4.12-5.fc26.x86_64
(gdb) x/i mmap+76
0x7fcfc680375c <mmap64+76>: retq
我不太确定是否需要 hbreak
,普通的 break
也可以。
我正在调试 java 应用程序中的本机内存泄漏。 rss 每天增长 1GB,而堆没有增长。随着时间的推移比较 pmap 的输出,我看到多个匿名块被添加到堆顶部或两个本机库之间。
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- java
0000000000601000 4 4 4 rw--- java
00000000008fc000 64156 64028 64028 rw--- [ anon ]
00000006c0000000 2467840 2466824 2466824 rw--- [ anon ] (heap)
0000000756a00000 2775040 0 0 ----- [ anon ]
0000000800000000 39808 39636 39636 rw--- [ anon ]
00000008026e0000 607360 0 0 ----- [ anon ]
00007fc8f0000000 11268 10944 10944 rw--- [ anon ]
00007fc8f0b01000 54268 0 0 ----- [ anon ]
00007fc938000000 49204 46164 46164 rw--- [ anon ]
00007fc93b00d000 16332 0 0 ----- [ anon ]
00007fc940000000 126784 126784 126784 rw--- [ anon ]
00007fc947bd0000 4288 0 0 ----- [ anon ]
00007fc948000000 65512 65512 65512 rw--- [ anon ]
.....a lot of new anon blocks with memory 1012 and also ~64MB block shown in screenshot
00007fc98c448000 16 12 0 r-x-- sssd_pac_plugin.so
.....anon blocks with memormy increased over time:
00007fca747fe000 2044 0 0 ----- librmi.so
.....anon blocks with memormy increased over time:
00007fcb58015000 44 32 0 r-x-- libkrb5support.so.0.1
.............................................other libraries
00007fcbad8f8000 4 4 4 rw--- libnio.so
.....anon blocks with memormy increased over time like :
00007fcbb0000000 65420 65404 65404 rw--- [ anon ]
00007fcbc4f7e000 4820 4820 4820 rw--- [ anon ]
00007fcbc5433000 5420 0 0 ----- [ anon ]
00007fcbc597e000 90112 88172 88172 rwx-- [ anon ]
.....anon blocks with memormy increased over time
00007fcbd96ea000 44 16 0 r-x-- libjimage.so
...............................................other libraries
00007fcbdcdd9000 4 4 4 r---- ld-2.17.so
00007fcbdcdda000 4 4 4 rw--- ld-2.17.so
00007fcbdcddb000 4 4 4 rw--- [ anon ]
00007ffdbd52c000 140 40 40 rw--- [ stack ]
00007ffdbd578000 8 8 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 16585920 9216360 9206356
我可以说 sssd_pac_plugin.so 和 librmi.so 之间的内存增加是由于其中任何一个吗?这个内存分配是连续的吗? https://i.stack.imgur.com/G1duY.jpg
在堆的顶部(地址:00007fc940000000 和更大)创建了很多新的内存块,从 126MB 到 1MB(100 个 1MB 的小块附图供参考)。它们是表示内存泄漏还是只是为每个新线程创建的。
- 为了查看这些块中的数据,我尝试了以下代码片段,但始终无法从中获取任何字符串。这都是我无法解释的二进制文件。有没有办法将其转换为字符串?或者映射到任何 thread/library 或我可以使用的任何东西。
gdb -pid <pid>
dump memory mem.bin 0x00007fc940000000 0x00007fc940000000+126784
#read file as:
strings mem.bin
另一个观察结果是,许多新块和旧块增加到大约 60-65MB。随着时间的推移,这些块的数量会增加很多。对rss增长贡献最大。 https://i.stack.imgur.com/xueC8.png https://i.stack.imgur.com/hwbAb.jpg
我也尝试了 libtcmalloc 和分析器,主要问题是在生产环境中我无法使用它们。在开发实例上,泄漏并不那么严重,因此无法验证探查器的输出。
一个非常基本的方法:您可以尝试查看谁在呼叫 mmap
(而不是 munmap
)。
- 附加到进程
- 在
mmap
上设置断点,使用命令打印参数和回溯(可能 5 帧)并继续 munmap
类似
- 重定向输出
- 让它运行一天
- 分离
- 将
mmap
s 与输出中的munmap
s 匹配
随着 pmap
周期性 运行ning 的出现,您可以将较新的匿名区域与 mmap
回溯相匹配(可能需要调整帧数)。
已经有这篇不错的小文章 LINUX GDB: IDENTIFY MEMORY LEAKS 可以帮助您入门。
注:
- 您正在寻找
mmap
和munmap
,而不是malloc
和free
- 你必须找出 return 与
mmap
的偏移量
- 我还没有尝试过文章中的脚本,但我认为它可以实现文章所声称的效果
查找 mmap
return 指令偏移量(从 mmap
开始):
只需启动 gdb
同一主机上的任何可执行文件
[ aquila ~ ] $ gdb -q /usr/bin/ls
Reading symbols from /usr/bin/ls...Reading symbols from /usr/bin/ls...(no debugging symbols found)...done
.
(no debugging symbols found)...done.
Missing separate debuginfos, use: dnf debuginfo-install coreutils-8.27-5.fc26.x86_64
(gdb) set pagination off
(gdb) set breakpoint pending on
(gdb) b mmap
Function "mmap" not defined.
Breakpoint 1 (mmap) pending.
(gdb) r
Starting program: /usr/bin/ls
Breakpoint 1, 0x00007ffff7df2940 in mmap64 () from /lib64/ld-linux-x86-64.so.2
(gdb) disassemble
Dump of assembler code for function mmap64:
=> 0x00007ffff7df2940 <+0>: test %rdi,%rdi
0x00007ffff7df2943 <+3>: push %r15
0x00007ffff7df2945 <+5>: mov %r9,%r15
:
:
0x00007ffff7df2973 <+51>: mov [=10=]x9,%eax
:
0x00007ffff7df2982 <+66>: pop %rbx
:
0x00007ffff7df298a <+74>: pop %r15
0x00007ffff7df298c <+76>: retq
0x00007ffff7df298d <+77>: nopl (%rax)
:
:
0x00007ffff7df29d8 <+152>: mov [=10=]xffffffffffffffff,%rax
0x00007ffff7df29df <+159>: jmp 0x7ffff7df2982 <mmap64+66>
End of assembler dump.
注意这里的return指令:
0x00007ffff7df298c <+76>: retq
所以,在我的机器上,第二个断点必须设置在 (mmap+76)。
一旦你确定了这个偏移量,你就可以通过附加到你的目标进程并反汇编那个偏移量处的内容来验证这个偏移量。例如。以我目前的 shell 作为我的目标进程:
[ aquila ~ ] $ echo $$
9769
[ aquila ~ ] $ gdb -q
(gdb) attach 9769
Attaching to process 9769
Reading symbols from /usr/bin/bash...Reading symbols from /usr/bin/bash...(no debugging symbols found)..
.done.
(no debugging symbols found)...done.
Reading symbols from /lib64/libtinfo.so.6...Reading symbols from /lib64/libtinfo.so.6...(no debugging sy
mbols found)...done.
(no debugging symbols found)...done.
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Reading symbols from /lib64/libnss_files.so.2...(no debugging symbols found)...done.
0x00007fcfc67cc18a in waitpid () from /lib64/libc.so.6
Missing separate debuginfos, use: dnf debuginfo-install bash-4.4.12-5.fc26.x86_64
(gdb) x/i mmap+76
0x7fcfc680375c <mmap64+76>: retq
我不太确定是否需要 hbreak
,普通的 break
也可以。