一款你值得拥有的内存问题定位神器
一 背景介绍
使用C语言开发时,对于内存资源的申请和释放时非常常见的场景,但这也给广大程序带来了很多困扰,一旦出现如越界访问,重复释放,use after free,内存泄露等问题时,定位起来非常麻烦。
因为这个原因,现有的大部分内存管理框架或多或少的都会提供一些内存调测能力,协助定位问题。但是目前用起来不是太难使用,就是约束限制太强,使用体验太差,因此到现在也没有出现网红款,今天我们就给大家带来一款内存调测神器:libumem。
libumem类似于linux上的ptmalloc/tcmalloc等用户态内存管理框架,提供高效的内存管理功能,同时提供了非常丰富及高效的调测信息记录功能,配合mdb工具,可以轻松观察程序内存的分配情况和定位内存泄漏的问题。
libumem最初在Solaris 9中提供,OmniTI lab将此软件移植到其他流行的操作系统,如linux,我们基于OmniTI的工作成果上,将此配套调测工具(mdb)适配移植到openEuler上,可以轻松使用该工具来定位和调试我们的内存问题。
二 libumem和Linux现有对比
libumem和Linux现有用户态内存管理框架调测能力对比
2.1 调测能力对比
- libumem相比于其他用户态内存框架,支持更多的内存调测能力,更容易解决内存问题。
- libumem不需要用户程序重编,提供兼容ptmalloc的接口,方便用户使用。
library | memory overrun | double free | use after free | wild free | access uninited | read invalid memory | memory leak | use after return | stack overflow |
---|---|---|---|---|---|---|---|---|---|
libumem | Yes | Yes | Yes | - | Yes | - | Yes | - | - |
ptmalloc(Glibc) | - | Yes | - | Yes | - | - | Yes | - | Yes(if use memcpy, strcpy, etc) |
TCMalloc(Gperftools) | - | - | - | - | - | - | Yes | - | - |
Jemalloc | - | - | - | - | - | - | Yes(build with –enable-prof) | - | - |
2.2 调测性能对比
测试对象: libumem、ptmalloc(glibc)、 tcmalloc(gperftools)、jemalloc(facebook)。
各内存管理框架的调测功能使用方法:
library | version | enable memory Debugger(Common use) |
---|---|---|
ptmalloc(Glibc) | glibc-2.28 | export MALLOC_CHECK_=3;export MTRACE=/root/mtrace.log(配合进程代码修改) |
tcMalloc(Gperftools) | gperftools 2.7.90 | export HEAPCHECK=normal |
jemalloc | Jemalloc 5.1.0 | export MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true(jemalloc build with –enable-prof) |
libumem | libumem-1.0 | export UMEM_DEBUG=default; epxort UMEM_LOGGING=transaction |
2.2.1 测试环境及测试工具
- 测试环境:硬件环境:X86_64 CPU 2.3GHz 16G内存虚拟机; OS版本:openEuler 20.03 LTS版本
- 测试工具:libMicro 工具,测试malloc的性能。
2.2.2 测试结果
内存管理框架调测功能全开情况,在单线程和多线程的条件下,申请内存size < 10k以内,libumem申请效率最优;
三 libumem及相关调测工具的使用
目前libumem只支持x86_64, 后续会增加arm64支持; libumem的调测需要配合mdb工具使用,mdb目前支持libumem调测的命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
findleaks - search for potential memory leaks
ugrep - search user address space for a pointer
umalog - display umem transaction log and stack traces
umastat - umem allocator stats
umausers - display current medium and large users of the umem allocator
umem_cache - print a umem cache
umem_debug - toggle umem dcmd/walk debugging
umem_log - dump umem transaction log
umem_malloc_dist - report distribution of outstanding malloc()s
umem_malloc_info - report information about malloc()s by cache
umem_status - Print umem status and message buffer
umem_verify - check integrity of umem-managed memory
vmem - print a vmem_t
vmem_seg - print or filter a vmem_seg
bufctl - print or filter a bufctl
bufctl_audit - print a bufctl_audit
allocdby - given a thread, print its allocated buffers
3.1 libumem使用
(1) libumem构建
代码仓: https://gitee.com/src-openeuler/libumem, 构建完成后直接rpm安装。
1
2
[root@localhost ~]# git clone git@gitee.com:src-openeuler/libumem.git
[root@localhost ~]# rpmbuild -ba -D '_sourcedir `pwd`' libumem.spec
(2) libumem使用
示例代码:这段代码中存在两处内存泄漏,一处内存破坏(use after free)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void test_leak_malloc(void)
{
char *p = malloc(50); //not free
p = malloc(100);
free(p);
}
void test_UAF(void)
{
char *p = malloc(50);
free(p);
*p = 1; // use after freed
}
void main(void)
{
char *p = malloc(50); //not free
test_leak_malloc();
test_UAF();
pause();
return;
}
1
[root@localhost ~]# gcc -g test.c -o test
libumem通过LD_PRELOAD环境变量使用方法:
1
2
[root@localhost ~]# UMEM_DEBUG=default, UMEM_LOGGING=transaction LD_PRELOAD=/usr/local/lib/libumem.so.0 ./test&
//安装libumem-devel后,man umem_debug查看UMEM_DEBUG、UMEM_LOGGING的含义。
3.2 调测功能使用
(1) mdb工具构建
代码仓: https://github.com/openEuler-Computing/mdb
1
2
3
[root@localhost ~]# git clone https://github.com/openEuler-Computing/mdb.git
[root@localhost ~]# yum install byacc flex libbsd-devel elfutils-libelf-devel zlib-devel libumem-devel
[root@localhost ~]# cd mdb/common; make all -j 4; make install
(2) 定位内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@localhost ~]# mdb -p `pidof test` //使用mdb attach到test进程(mdb -p pid)
loading modules: [ libdl-2.28.so libpthread-2.28.so libc-2.28.so libumem.so.0.0.0 ld-2.28.so ]
::findleaks -d
CACHE LEAKED BUFCTL CALLER
00007f3db1bba048 1 00007f3db1b3e7e0 main+0x12
00007f3db1bba048 1 00007f3db1b3e8c0 test_leak_malloc+0x12
------------------------------------------------------------------------
Total 2 buffers, 160 bytes
umem_alloc_80 leak: 1 buffer, 80 bytes
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
7f3db1b3e7e0 7f3db1b6bf60 60116209000b4637 -1312650368
7f3db1bba048 7f3db1bf8300 0
libumem.so.0.0.0`umem_alloc+0x98
libumem.so.0.0.0`malloc+0x39
main+0x12 //memory leak here
libc-2.28.so`__libc_start_main+0xf3
_start+0x2e
umem_alloc_80 leak: 1 buffer, 80 bytes
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
7f3db1b3e8c0 7f3db1b6bef0 60116209000b463c -1312650368
7f3db1bba048 7f3db1bf83c0 0
libumem.so.0.0.0`umem_alloc+0x98
libumem.so.0.0.0`malloc+0x39
test_leak_malloc+0x12 //memory leak here
main+0x1b
libc-2.28.so`__libc_start_main+0xf3
_start+0x2e
(3)定位内存破环
常见的内存破坏: use after free, double free, memory overrun, out-of-bounds array write.
1
2
3
4
5
6
::umem_verify
Cache Name Addr Cache Integrity
...
umem_alloc_80 7f3db1bba048 1 corrupt buffer
umem_alloc_96 7f3db1bb9048 clean
...
7f3db1bba048::umem_verify
Summary for cache 'umem_alloc_80'
buffer 7f3db1b6be80 (free) seems corrupted, at 7f3db1b6be90 //memory corrupted appear in freed address 7f3db1b6be80
通过umalog命令可以查看到申请的调用栈:
1
2
3
4
5
6
7
T-0.000000005 addr=7f3db1b6be80 umem_alloc_80 //test_UAF malloc(50) addr=7f3db1b6be80
libumem.so.0.0.0`umem_alloc+0x98
libumem.so.0.0.0`malloc+0x39
test_UAF+0x12
main+0x20
libc-2.28.so`__libc_start_main+0xf3
_start+0x2e
综合信息可以知道test_UAF中出现use after free的情况;
通过以上评测结果和demo应用,相信你 已经的libumem以及mdb工具有了初步的了解,期望你来做更多的尝试…
四 更多内容关注OpenEuler
如果内存调测工具对你有用,太好了! 请传播它,让更多人使用,调试和增强它。
libumem地址:https://gitee.com/src-openeuler/libumem
配套调测工具的地址: https://github.com/openEuler-Computing/mdb