我们在开发我们的eBPF程序的时候, 第一部分通常是去定义我们的数据结构, 因为我们需要相应的指标数据, 其次定义数据结构的时候需要注意值的大小不可以超过我们eBPF程序指定的大小。
第二步是定义我们的事件映射, 我们使用eBPF需要去监听对应的事件, 在BCC中我们使用BPF_PERF_OUTPUT
这个函数, 而在python中我们将对应的结构与我们的事件相互绑定。
BPF_PERF_OUTPUT(events)
event = bpf["events"].event(data);
#从event中取出数据
event.pid
第三步是定义我们hash映射, 在BPF程序中, BPF用于提供大块的键值存储, 这些存储可以被用户空间程序访问, 用来获取eBPF程序的运行状态, 并且不同的eBPF程序可以通过相同的映射来共享状态。
我们通常会使用BPF_HASH(name, key_type=u64, leaf_type=u64, size=10240)
这个函数来完成
BPF_HASH(tasks, u64, data_t);
//更新
tasks.update(&pid, &data);
//删除
tasks.delete(&data);
//查找
tasks.lookup(&data);
定义完成相应的事件映射, 和hash映射之后就是设置我们的跟踪点了, 设置跟踪点一般分为两个部分, 第一个部分是跟踪点的入口, 第二部分是跟踪点的出口, 在BCC中对应的是:
TRACEPOINT_PROBE(syscalls, sys_enter_execve) //定义监听函数的入口点
{//处理逻辑}
在入口点中, 我们通常会去获取一些参数, 比如进程的号, 进程的名称, 这些函数在eBPF中表示形式如下:
bpf_get_current_pid_tgid();
bpf_get_current_comm(&data->comm, size); //因为名称一般都是字符串类型
bpf_probe_read_user_str(); //从内存指针中读取字符串
这里我们需要注意其中的bpf_probe_read_user_str
这个函数, 因为eBPF的内存空间只有寄存器和栈, 所以要访问其它的内核空间或用户空间地址
BCC将所有参数都放到了args
参数中, 你可以使用args->argv
来访问参数列表
const char **argv = (const char **)(args->argv);
但是argv
是一个用户空间的字符串数组, 指针数组, 这就需要调用bpf_probe_read
系列的辅助函数, 去这些指针中读取数据, 但是需要注意的是, 字符串的数量和每个字符串的长度都是未知的, 由于eBPF栈大小只有512字节, 如果想要把它们读入一个临时的字符数组中, 就必须要保证不能超过eBPF的栈空间
下面是关于入口点的跟踪逻辑:
// 引入内核头文件
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>
// 定义sys_enter_execve跟踪点处理函数.
TRACEPOINT_PROBE(syscalls, sys_enter_execve)
{
unsigned int ret = 0;
const char **argv = (const char **)(args->argv);
// 获取进程PID和进程名称
struct data_t data = { };
u32 pid = bpf_get_current_pid_tgid();
data.pid = pid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
// 获取第一个参数(即可执行文件的名字)
if (__bpf_read_arg_str(&data, (const char *)argv[0]) < 0) {
goto out;
}
// 获取其他参数(限定最多5个)
#pragma unrollfor (int i = 1; i < TOTAL_MAX_ARGS; i++) {
if (__bpf_read_arg_str(&data, (const char *)argv[i]) < 0) {
goto out;
}
}
out:
// 存储到哈希映射中
tasks.update(&pid, &data);
return 0;
}
在安装BCC工具的时候, 你可能也会注意到, 内核头文件linux-headers-$(uname-r)
也是一个安装项目, 这是因为BCC在编译eBPF程序的时候, 需要从内核头文件中找到对应的内核数据结构定义, 这样你在调用bpf_probe_read时候才能从内存地址中找到正确的数据类型。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。