我们在开发我们的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时候才能从内存地址中找到正确的数据类型。


loumosx
1 声望0 粉丝

一生只有一个职业, 那就是程序员


引用和评论

0 条评论