Using eBPF to detect mmap leaks

Table of Contents

background

Official website

malloc leak detection

mmap leak detection

Call munmap to release memory

summary


Background

We know that the memory space requested by the mmap system call belongs to the file mapping area and the anonymous mapping area. This part of the area does not belong to the heap, so it cannot be detected using general memory leak detection tools. For example: commonly used memory leak detection tools such as vagrind, ASAN, malloc_debug, etc. For an introduction to ASAN, you can refer to: ASAN Getting Started Reference-CSDN Blog

official website

https://github.com/iovisor/bcc/blob/master/tools/memleak.py

It can be seen that the BCC tool based on eBPF supports the following memory application interfaces for memory leak detection.

 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

Let’s first look at the most common malloc detection.

malloc leak detection

test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

void allocate_mmap_memory()
{
    int *p=mmap(
        NULL, //The system specifies the first address
        getpagesize(),//A page (basic unit)
        PROT_READ|PROT_WRITE,
        MAP_ANONYMOUS|MAP_SHARED,//Anonymous mapping
        0,0);//You can do whatever you want on this page

    printf("mmap address p : %p\
",p);

    *p=20;
    *(p + 1)=30;
    *(p + 2)=40;

    munmap(p,4096);//Release memory
}

void a()
{
    int* p = malloc(5);
    printf("malloc address p : %p\
",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;
}

Compile and run:

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 means display the size and address of each memory allocation request
# -p specifies the PID number of the case application
wj@wj:/usr/share/bcc/tools$ sudo python3 memleak -a -p 4861
[sudo] Password for 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]

From the output of memleak, we can see that the case application is constantly allocating memory, and these allocated addresses are not reclaimed.

test.c plus release function.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

void allocate_mmap_memory()
{
    int *p=mmap(
        NULL, //The system specifies the first address
        getpagesize(),//One page (basic unit)
        PROT_READ|PROT_WRITE,
        MAP_ANONYMOUS|MAP_SHARED,//Anonymous mapping
        0,0);//You can do whatever you want on this page

    printf("mmap address p : %p\
",p);

    *p=20;
    *(p + 1)=30;
    *(p + 2)=40;

    munmap(p,4096);//Release memory
}

void a()
{
    int* p = malloc(5);
    printf("malloc address p : %p\
",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;
}

Test again.

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$

Now, we see that the case application has no remaining memory, proving that our repair work was successfully completed.

mmap leak detection

test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

void allocate_mmap_memory()
{
    int *p=mmap(
        NULL, //The system specifies the first address
        getpagesize(),//A page (basic unit)
        PROT_READ|PROT_WRITE,
        MAP_ANONYMOUS|MAP_SHARED,//Anonymous mapping
        0,0);//You can do whatever you want on this page

    printf("mmap address p : %p\
",p);

    *p=20;
    *(p + 1)=30;
    *(p + 2)=40;

    munmap(p,4096);//Release memory
}

void a()
{
    //int* p = malloc(5);
    //printf("malloc address p : %p\
",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;
}

Compile and run:

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]

From the output of memleak, we can see that the case application is constantly allocating memory, and these allocated addresses are not reclaimed.

Call munmap to release memory

test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

void allocate_mmap_memory()
{
    int *p=mmap(
        NULL, //The system specifies the first address
        getpagesize(),//A page (basic unit)
        PROT_READ|PROT_WRITE,
        MAP_ANONYMOUS|MAP_SHARED,//Anonymous mapping
        0,0);//You can do whatever you want on this page

    printf("mmap address p : %p\
",p);

    *p=20;
    *(p + 1)=30;
    *(p + 2)=40;

    munmap(p,4096);//Release memory
}

void a()
{
    //int* p = malloc(5);
    //printf("malloc address p : %p\
",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;
}

Compile and run:

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:

Now, we see that the case application has no remaining memory, proving that our repair work was successfully completed.

Summary

If you encounter mmap-related leaks at work, it may be a good choice to consider eBPF.