使用 eBPF检测 mmap泄露

目录

背景

官网

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 的检测。

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$ 

现在,我们看到,案例应用已经没有遗留内存,证明我们的修复工作成功完成。

mmap泄露检测

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 的输出可以看到,案例应用在不停地分配内存,并且这些分配的地址没有被回收。

调用munmap释放内存

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或许是个不错的选择。

你可能感兴趣的:(eBPF,simpleperf技术,eBPF,mmap)