notes/article/ebpf/eBPF内存泄露检测代码实现v1.md

164 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# eBPF内存泄露检测代码实现<一>
[toc]
## 目标
这节视频的目标是用ebpf代码实现内存泄露检测工具的第一个简单版本只检测 malloc 和 free并打印如下的内存泄露堆栈指令地址对应符号名文件名行号的解析放到下节视频中讲解
```
stack_id=0x2d81 with outstanding allocation: total_size=20 nr_alloc=5
[ 0] 0x55e8fbd196f2
[ 1] 0x55e8fbd19711
[ 2] 0x55e8fbd19730
[ 3] 0x55e8fbd19770
[ 4] 0x7f5bb944ec87
[ 5] 0x5f6258d4c544155
```
代码实现参考开源项目:
https://github.com/eunomia-bpf/bpf-developer-tutorial
bpf-developer-tutorial/src/16-memleak
## 测试代码
`test_memleak.c`
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void * alloc_v3(int alloc_size)
{
void * ptr = malloc(alloc_size);
return ptr;
}
static void * alloc_v2(int alloc_size)
{
void * ptr = alloc_v3(alloc_size);
return ptr;
}
static void * alloc_v1(int alloc_size)
{
void * ptr = alloc_v2(alloc_size);
return ptr;
}
int main(int argc, char * argv[])
{
const int alloc_size = 4;
void * ptr = NULL;
int i = 0;
for (i = 0; ; i++)
{
ptr = alloc_v1(alloc_size);
sleep(2);
if (0 == i % 2)
{
free(ptr);
}
}
return 0;
}
```
编译:
```shell
gcc -g test_memleak.c -o test_memleak
```
## 内存泄露检测工具的第一个版本
结构体和maps
```c
#ifndef __MEMLEAK_H
#define __MEMLEAK_H
#define ALLOCS_MAX_ENTRIES 1000000
#define COMBINED_ALLOCS_MAX_ENTRIES 10240
struct alloc_info {
__u64 size;
int stack_id;
};
/* 为了节省内存和方便整形数据的原子操作,把 combined_alloc_info 定义为联合体
* 其中 total_size 占 40bit, number_of_allocs 占 24bit, 联合体总大小为 64bit
* 2个combined_alloc_info联合体的 bits 字段相加, 相当于对应的 total_size 相加,
* 和对应的 number_of_allocs 相加;
*/
union combined_alloc_info {
struct {
__u64 total_size : 40;
__u64 number_of_allocs : 24;
};
__u64 bits;
};
#endif /* __MEMLEAK_H */
```
```c
#define KERN_STACKID_FLAGS (0 | BPF_F_FAST_STACK_CMP)
#define USER_STACKID_FLAGS (0 | BPF_F_FAST_STACK_CMP | BPF_F_USER_STACK)
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, pid_t); // pid
__type(value, u64); // size for alloc
__uint(max_entries, 10240);
} sizes SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u64); /* alloc return address */
__type(value, struct alloc_info);
__uint(max_entries, ALLOCS_MAX_ENTRIES);
} allocs SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u64); /* stack id */
__type(value, union combined_alloc_info);
__uint(max_entries, COMBINED_ALLOCS_MAX_ENTRIES);
} combined_allocs SEC(".maps");
/* value stack id 对应的堆栈的深度
* max_entries: 最大允许存储多少个stack_id每个stack id都对应一个完整的堆栈
* 这2个值可以根据应用层的使用场景,在应用层的ebpf中open之后load之前动态设置
*/
struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
__type(key, u32); /* stack id */
//__type(value, xxx); memleak_bpf__open 之后再动态设置
//__uint(max_entries, xxx); memleak_bpf__open 之后再动态设置
} stack_traces SEC(".maps");
```
```c
static const int perf_max_stack_depth = 127;
static const int stack_map_max_entries = 10240;
```