目录
代码分析
BPF 程序部分
功能说明
头文件引入说明
SEC 关键字说明
bpf_get_current_pid_tgid() >> 32
用户程序部分
功能说明
头文件引入介绍
libbpf_set_print
minimal_bpf__open
skel->bss->my_pid = getpid();
minimal_bpf__load(skel);
minimal_bpf__attach(skel);
minimal_bpf__destroy(skel);
执行效果
这个样例也是最简单的样例,会监测write 系统调用,当用户程序调用 "sys_write" 系统调用时,输出一条内核日志,输出自己是哪一个进程trigger 的
#include
#include
char LICENSE[] SEC("license") = "Dual BSD/GPL";
int my_pid = 0;
SEC("tp/syscalls/sys_enter_write")
int handle_tp(void *ctx)
{
int pid = bpf_get_current_pid_tgid() >> 32;
if (pid != my_pid)
return 0;
bpf_printk("BPF triggered from PID %d.\n", pid);
return 0;
}
在上述代码中,int pid = bpf_get_current_pid_tgid() >> 32; 将当前进程的 PID 存储在 pid 变量中,以便后续的处理。该代码位于 handle_tp 函数中,用于在 "sys_enter_write" 系统调用的 Tracepoint 中获取当前进程的 PID,并与另一个全局变量 my_pid 进行比较。如果当前进程的 PID 不等于 my_pid,则该函数将直接返回 0,否则将打印一条调试信息并返回 0。
bpf_get_current_pid_tgid():该函数用于获取当前进程的 PID 和 TGID(线程组 ID)。该函数定义在
bpf_printk():该函数用于在内核日志中打印一条调试信息。该函数定义在
SEC():该关键字用于指定一个 BPF 程序的安全属性。该关键字定义在
SEC 是 BPF(Berkeley Packet Filter)程序中的一个特殊关键字,用于指定一个程序的安全属性。在上述代码中,SEC 用于指定两个部分的安全属性,分别为 LICENSE 变量和 handle_tp 函数。
SEC 的作用可以在 BPF 程序的加载和验证过程中体现。在加载 BPF 程序时,内核需要验证程序的安全性,以确保程序不会对系统造成损害。SEC 可以用于指定程序的安全属性,以供内核进行验证。例如,如果一个程序需要在内核中执行敏感操作,那么开发人员可以使用 SEC 来指定该程序的安全属性为 "trusted",以确保程序能够被正确加载和验证。
在本例中,SEC("tp/syscalls/sys_enter_write") 指定了 handle_tp 函数的安全属性为 "tp/syscalls/sys_enter_write",即该函数将会在 "sys_enter_write" 系统调用的 Tracepoint 中执行。同时,该函数也被指定为一个 BPF Program 类型的 SEC,因为它是一个 BPF 程序的一部分。这些安全属性将会在 BPF 程序的加载和验证过程中得到验证,以确保程序的正确性和安全性。
bpf_get_current_pid_tgid() >> 32 的作用是将 bpf_get_current_pid_tgid() 函数返回的 64 位整数右移 32 位,得到当前进程的 PID。在 BPF 程序中,由于上下文环境的限制,只能使用 32 位的寄存器,因此需要使用右移操作将返回值转换为 32 位的整数,以便后续的处理。
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include
#include
#include
#include
#include "minimal.skel.h"
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
int main(int argc, char **argv)
{
struct minimal_bpf *skel;
int err;
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Open BPF application */
skel = minimal_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
/* ensure BPF program only handles write() syscalls from our process */
skel->bss->my_pid = getpid();
/* Load & verify BPF programs */
err = minimal_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
/* Attach tracepoint handler */
err = minimal_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
for (;;) {
/* trigger our BPF program */
fprintf(stderr, ".");
sleep(1);
}
cleanup:
minimal_bpf__destroy(skel);
return -err;
}
这里有一个在源码中没有见过的头文件出现了
#include "minimal.skel.h"
这个部分暂时不去分析,等到将libbpf-bootstrap 框架用起来之后,再去探索框架本身的奥秘。
这是一个用户态的bpf 程序,主要的作用就是把bpf程序加载进来,然后绑定到系统的tracepoint上,之后利用一个死循环不断地触发write 系统调用,用来让内核中的bpf程序进行工作。
后面还有一个cleanup 会在程序结束之后,清理bpf程序环境。
其中minimal.skel.h 这个文件是由libbpf bootstrap 框架自动生成的,后面会详细讲解这个文件中会包含哪些api
libbpf_set_print() 函数用于设置 BPF 相关库的输出信息和调试信息的回调函数。在这段代码中,调用 libbpf_set_print() 函数将自定义的回调函数 libbpf_print_fn() 传递给 BPF 相关库,以便在 BPF 相关库输出信息时将信息重定向到标准错误流中。
具体来说,libbpf_print_fn() 函数是一个静态函数,接受三个参数:输出日志的级别、输出日志的格式字符串和格式字符串的参数列表。在函数内部,调用 vfprintf() 函数将格式化的字符串和参数列表输出到标准错误流中。因此,调用 libbpf_set_print(libbpf_print_fn) 函数后,BPF 相关库的输出信息将被重定向到标准错误流中,以便在控制台上查看。
minimal_bpf__open() 函数是预先生成的 BPF 程序的接口之一,用于打开 BPF 程序的对象并返回一个指向该对象的指针。在这段代码中,调用 minimal_bpf__open() 函数打开预先生成的 BPF 程序对象,并将返回的指针赋值给 skel 变量。
在调用 minimal_bpf__open() 函数时,会自动进行一些初始化工作,例如动态加载 BPF 程序的 ELF 文件、创建 BPF 程序的对象等。如果打开成功,则返回一个指向 BPF 程序对象的指针;否则返回 NULL 指针表示打开失败。
首先通过 getpid() 函数获取当前进程的 PID,并将 PID 存储到 BPF 程序对象中定义的全局变量 my_pid 中。然后在 BPF 程序中,会以 my_pid 的值为条件判断系统调用事件的发起进程是否为当前进程,如果是则进行处理,否则忽略该事件。
minimal_bpf__load() 函数是预先生成的 BPF 程序的接口之一,用于加载和验证 BPF 程序。在加载和验证 BPF 程序时,minimal_bpf__load() 函数会执行以下操作:
如果以上操作都执行成功,则返回 0;否则返回一个负数,表示加载和验证失败。
minimal_bpf__attach() 函数是预先生成的 BPF 程序的接口之一,用于将 BPF 程序挂载到指定的 Tracepoint 上。在将 BPF 程序挂载到 Tracepoint 上时,minimal_bpf__attach() 函数会执行以下操作:
PS:其实这里就可以看出,所谓的框架生成的一些api,本质上就是对于原生的libbpf 程序接口的一些封装。
minimal_bpf__destroy() 函数是预先生成的 BPF 程序的接口之一,用于销毁预先生成的 BPF 程序对象。在销毁 BPF 程序对象时,minimal_bpf__destroy() 函数会执行以下操作:
调用 bpf_object__close() 函数关闭 BPF 程序对象,释放对象相关联的资源。
PS: 如果希望可以打印出参数信息可以使用如下代码
SEC("tp/syscalls/sys_enter_write")
int handle_tp(struct trace_event_raw_sys_enter *args)
{
int pid = bpf_get_current_pid_tgid() >> 32;
if (pid != my_pid)
return 0;
bpf_printk("BPF triggered from PID %d.\n", pid);
bpf_printk("write called with fd = %d and count = %d and content = %s\n", args->args[0], args->args[2],args->args[1]);
return 0;
}
关于struct trace_event_raw_sys_enter 的信息,后面会详细介绍