目录
背景
官网
malloc泄露检测
mmap泄露检测
调用munmap释放内存
小结
我们知道 mmap系统调用申请的内存空间,属于文件映射区域 和 匿名映射区域。这部分区域并不属于 heap,所以用一般的内存泄露检测工具是检测不出来的。例如:一般常用的内存泄露检测工具 vagrind、ASAN、malloc_debug等。关于ASAN的介绍,可以参考:ASAN入门参考-CSDN博客
https://github.com/iovisor/bcc/blob/master/tools/memleak.py
可以看到,基于eBPF的BCC工具是支持以下内存申请接口,进行内存泄露检测的。
attach_probes("malloc")
attach_probes("calloc")
attach_probes("realloc")
attach_probes("mmap")
attach_probes("posix_memalign")
attach_probes("valloc", can_fail=True) # failed on Android, is deprecated in libc.so from bionic directory
attach_probes("memalign")
attach_probes("pvalloc", can_fail=True) # failed on Android, is deprecated in libc.so from bionic directory
attach_probes("aligned_alloc", can_fail=True) # added in C11
我们先来看看最常见的 malloc 的检测。
test.c
#include
#include
#include
#include
void allocate_mmap_memory()
{
int *p=mmap(
NULL,//系统指定首地址
getpagesize(),//一个页(基本单位)
PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,//匿名映射
0,0);//可以在这一页里随便折腾
printf("mmap address p : %p\n",p);
*p=20;
*(p+1)=30;
*(p+2)=40;
munmap(p,4096);//释放内存
}
void a()
{
int* p = malloc(5);
printf("malloc address p : %p\n",p);
//allocate_mmap_memory();
//free(p);
}
void b()
{
a();
}
void c()
{
b();
}
int main(int argc,char* argv[])
{
while(1)
{
sleep(15);
c();
}
return 0;
}
编译运行:
wj@wj:~/linux$ gcc test.c -o test.out
wj@wj:~/linux$ ./test.out
malloc address p : 0x561051d3a2a0
malloc address p : 0x561051d3a6d0
malloc address p : 0x561051d3a6f0
malloc address p : 0x561051d3a710
^C
wj@wj:~/linux$ ps -ef | grep test
kernoops 1078 1 0 19:33 ? 00:00:00 /usr/sbin/kerneloops --test
wj 4861 2537 0 21:52 pts/0 00:00:00 ./test.out
wj 4863 3794 0 21:52 pts/2 00:00:00 grep --color=auto test
sudo python3 memleak -a -p 4861
# -a 表示显示每个内存分配请求的大小以及地址
# -p 指定案例应用的 PID 号
wj@wj:/usr/share/bcc/tools$ sudo python3 memleak -a -p 4861
[sudo] wj 的密码:
Attaching to pid 4861, Ctrl+C to quit.
[21:52:46] Top 10 stacks with outstanding allocations:
[21:52:51] Top 10 stacks with outstanding allocations:
[21:52:56] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
5 bytes in 1 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
[21:53:01] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
5 bytes in 1 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
[21:53:06] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
5 bytes in 1 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
[21:53:11] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
addr = 561051d3a710 size = 5
10 bytes in 2 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
[21:53:16] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
addr = 561051d3a710 size = 5
10 bytes in 2 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
[21:53:21] Top 10 stacks with outstanding allocations:
addr = 561051d3a6f0 size = 5
addr = 561051d3a710 size = 5
10 bytes in 2 allocations from stack
0x0000561051aec28e a+0x16 [test.out]
0x0000561051aec2c2 b+0x12 [test.out]
0x0000561051aec2d7 c+0x12 [test.out]
0x0000561051aec301 main+0x27 [test.out]
0x00007fa2fc829d90 __libc_start_call_main+0x80 [libc.so.6]
从 memleak 的输出可以看到,案例应用在不停地分配内存,并且这些分配的地址没有被回收。
test.c 加上释放函数。
#include
#include
#include
#include
void allocate_mmap_memory()
{
int *p=mmap(
NULL,//系统指定首地址
getpagesize(),//一个页(基本单位)
PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,//匿名映射
0,0);//可以在这一页里随便折腾
printf("mmap address p : %p\n",p);
*p=20;
*(p+1)=30;
*(p+2)=40;
munmap(p,4096);//释放内存
}
void a()
{
int* p = malloc(5);
printf("malloc address p : %p\n",p);
//allocate_mmap_memory();
free(p);
}
void b()
{
a();
}
void c()
{
b();
}
int main(int argc,char* argv[])
{
while(1)
{
sleep(15);
c();
}
return 0;
}
再次检测。
wj@wj:~/linux$ gcc test.c -o test.out
wj@wj:~/linux$ ./test.out
malloc address p : 0x558bfb5142a0
malloc address p : 0x558bfb5142a0
malloc address p : 0x558bfb5142a0
malloc address p : 0x558bfb5142a0
^C
wj@wj:~/linux$ ps -ef | grep test
kernoops 1078 1 0 19:33 ? 00:00:00 /usr/sbin/kerneloops --test
wj 5018 2537 0 22:07 pts/0 00:00:00 ./test.out
wj 5029 3794 0 22:08 pts/2 00:00:00 grep --color=auto test
wj@wj:~/linux$
wj@wj:/usr/share/bcc/tools$ sudo python3 memleak -a -p 5018
Attaching to pid 5018, Ctrl+C to quit.
[22:08:09] Top 10 stacks with outstanding allocations:
[22:08:14] Top 10 stacks with outstanding allocations:
[22:08:19] Top 10 stacks with outstanding allocations:
[22:08:24] Top 10 stacks with outstanding allocations:
[22:08:29] Top 10 stacks with outstanding allocations:
[22:08:34] Top 10 stacks with outstanding allocations:
[22:08:39] Top 10 stacks with outstanding allocations:
[22:08:44] Top 10 stacks with outstanding allocations:
^Cwj@wj:/usr/share/bcc/tools$
现在,我们看到,案例应用已经没有遗留内存,证明我们的修复工作成功完成。
test.c
#include
#include
#include
#include
void allocate_mmap_memory()
{
int *p=mmap(
NULL,//系统指定首地址
getpagesize(),//一个页(基本单位)
PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,//匿名映射
0,0);//可以在这一页里随便折腾
printf("mmap address p : %p\n",p);
*p=20;
*(p+1)=30;
*(p+2)=40;
munmap(p,4096);//释放内存
}
void a()
{
//int* p = malloc(5);
//printf("malloc address p : %p\n",p);
allocate_mmap_memory();
//free(p);
}
void b()
{
a();
}
void c()
{
b();
}
int main(int argc,char* argv[])
{
while(1)
{
sleep(15);
c();
}
return 0;
}
编译运行:
wj@wj:~/linux$ gcc test.c -o test.out
wj@wj:~/linux$ ./test.out
mmap address p : 0x7fd503547000
mmap address p : 0x7fd50350d000
^C
wj@wj:~/linux$
wj@wj:~/linux$ ps -ef | grep test
kernoops 1078 1 0 19:33 ? 00:00:00 /usr/sbin/kerneloops --test
wj 5100 2537 0 22:15 pts/0 00:00:00 ./test.out
wj 5105 3794 0 22:15 pts/2 00:00:00 grep --color=auto test
wj@wj:~/linux$
wj@wj:/usr/share/bcc/tools$ sudo python3 memleak -a -p 5100
Attaching to pid 5100, Ctrl+C to quit.
[22:15:52] Top 10 stacks with outstanding allocations:
addr = 55fbc12f22a0 size = 1024
addr = 7fd503547000 size = 4096
1024 bytes in 1 allocations from stack
0x00007fd50327eba4 __GI__IO_file_doallocate+0x94 [libc.so.6]
4096 bytes in 1 allocations from stack
0x000055fbc0d581df allocate_mmap_memory+0x36 [test.out]
0x000055fbc0d58239 a+0x12 [test.out]
0x000055fbc0d5824e b+0x12 [test.out]
0x000055fbc0d58263 c+0x12 [test.out]
0x000055fbc0d5828d main+0x27 [test.out]
0x00007fd503229d90 __libc_start_call_main+0x80 [libc.so.6]
[22:15:57] Top 10 stacks with outstanding allocations:
addr = 55fbc12f22a0 size = 1024
addr = 7fd503547000 size = 4096
1024 bytes in 1 allocations from stack
0x00007fd50327eba4 __GI__IO_file_doallocate+0x94 [libc.so.6]
4096 bytes in 1 allocations from stack
0x000055fbc0d581df allocate_mmap_memory+0x36 [test.out]
0x000055fbc0d58239 a+0x12 [test.out]
0x000055fbc0d5824e b+0x12 [test.out]
0x000055fbc0d58263 c+0x12 [test.out]
0x000055fbc0d5828d main+0x27 [test.out]
0x00007fd503229d90 __libc_start_call_main+0x80 [libc.so.6]
[22:16:02] Top 10 stacks with outstanding allocations:
addr = 55fbc12f22a0 size = 1024
addr = 7fd503547000 size = 4096
1024 bytes in 1 allocations from stack
0x00007fd50327eba4 __GI__IO_file_doallocate+0x94 [libc.so.6]
4096 bytes in 1 allocations from stack
0x000055fbc0d581df allocate_mmap_memory+0x36 [test.out]
0x000055fbc0d58239 a+0x12 [test.out]
0x000055fbc0d5824e b+0x12 [test.out]
0x000055fbc0d58263 c+0x12 [test.out]
0x000055fbc0d5828d main+0x27 [test.out]
0x00007fd503229d90 __libc_start_call_main+0x80 [libc.so.6]
[22:16:07] Top 10 stacks with outstanding allocations:
addr = 55fbc12f22a0 size = 1024
addr = 7fd503547000 size = 4096
addr = 7fd50350d000 size = 4096
1024 bytes in 1 allocations from stack
0x00007fd50327eba4 __GI__IO_file_doallocate+0x94 [libc.so.6]
8192 bytes in 2 allocations from stack
0x000055fbc0d581df allocate_mmap_memory+0x36 [test.out]
0x000055fbc0d58239 a+0x12 [test.out]
0x000055fbc0d5824e b+0x12 [test.out]
0x000055fbc0d58263 c+0x12 [test.out]
0x000055fbc0d5828d main+0x27 [test.out]
0x00007fd503229d90 __libc_start_call_main+0x80 [libc.so.6]
从 memleak 的输出可以看到,案例应用在不停地分配内存,并且这些分配的地址没有被回收。
test.c
#include
#include
#include
#include
void allocate_mmap_memory()
{
int *p=mmap(
NULL,//系统指定首地址
getpagesize(),//一个页(基本单位)
PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED,//匿名映射
0,0);//可以在这一页里随便折腾
printf("mmap address p : %p\n",p);
*p=20;
*(p+1)=30;
*(p+2)=40;
munmap(p,4096);//释放内存
}
void a()
{
//int* p = malloc(5);
//printf("malloc address p : %p\n",p);
allocate_mmap_memory();
//free(p);
}
void b()
{
a();
}
void c()
{
b();
}
int main(int argc,char* argv[])
{
while(1)
{
sleep(15);
c();
}
return 0;
}
编译运行:
wj@wj:~/linux$ gcc test.c -o test.out
wj@wj:~/linux$ ./test.out
mmap address p : 0x7f4e82632000
mmap address p : 0x7f4e82632000
^C
wj@wj:~/linux$
wj@wj:~/linux$ ps -ef | grep test
kernoops 1078 1 0 19:33 ? 00:00:00 /usr/sbin/kerneloops --test
wj 5467 2537 0 22:24 pts/0 00:00:00 ./test.out
wj 5470 3794 0 22:24 pts/2 00:00:00 grep --color=auto test
wj@wj:~/linux$
wj@wj:/usr/share/bcc/tools$ sudo python3 memleak -a -p 5467
Attaching to pid 5467, Ctrl+C to quit.
[22:25:09] Top 10 stacks with outstanding allocations:
[22:25:14] Top 10 stacks with outstanding allocations:
[22:25:19] Top 10 stacks with outstanding allocations:
[22:25:24] Top 10 stacks with outstanding allocations:
[22:25:29] Top 10 stacks with outstanding allocations:
现在,我们看到,案例应用已经没有遗留内存,证明我们的修复工作成功完成。
如果工作中遇到了mmap相关的泄露,考虑一下 eBPF或许是个不错的选择。