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.