目录
bpf_get_current_pid_tgid() >> 32
这个样例也是最简单的样例,会监测write 系统调用,当用户程序调用 "sys_write" 系统调用时,输出一条内核日志,输出自己是哪一个进程trigger 的
代码分析
BPF 程序部分
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
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。
头文件引入说明
- <linux/bpf.h>:该头文件包含了 Linux 内核中与 BPF 相关的定义和数据结构。例如,它定义了 BPF 程序中常用的指令、寄存器、内存布局等等。开发人员可以使用这些定义和数据结构来编写 BPF 程序。
- <bpf/bpf_helpers.h>:该头文件包含了一些辅助函数和宏,用于在 BPF 程序中进行常见操作。例如,它定义了一个名为 bpf_map_lookup_elem() 的函数,用于在 BPF Map 中查找元素;还定义了一个名为 SEC() 的宏,用于指定 BPF 程序的安全属性。
bpf_get_current_pid_tgid():该函数用于获取当前进程的 PID 和 TGID(线程组 ID)。该函数定义在 <bpf/bpf_helpers.h> 头文件中,用于在 BPF 程序中获取当前进程的标识符。
bpf_printk():该函数用于在内核日志中打印一条调试信息。该函数定义在 <bpf/bpf_helpers.h> 头文件中,用于在 BPF 程序中输出调试信息。
SEC():该关键字用于指定一个 BPF 程序的安全属性。该关键字定义在 <bpf/bpf_helpers.h> 头文件中,用于在 BPF 程序中指定程序的安全属性。
SEC 关键字说明
SEC 是 BPF(Berkeley Packet Filter)程序中的一个特殊关键字,用于指定一个程序的安全属性。在上述代码中,SEC 用于指定两个部分的安全属性,分别为 LICENSE 变量和 handle_tp 函数。
- LICENSE 变量的安全属性:char LICENSE[] SEC("license") = "Dual BSD/GPL"; 这行代码将 LICENSE 字符串常量指定为 "license" 类型的 SEC。这意味着在加载和验证这个 BPF 程序时,内核将会检查该程序中所有标记为 "license" 类型的 SEC,并验证它们的安全性。在本例中,SEC("license") 指定了 LICENSE 常量的安全属性为 "license"。
- handle_tp 函数的安全属性:SEC("tp/syscalls/sys_enter_write") 这行代码将 handle_tp 函数指定为 "tp/syscalls/sys_enter_write" 类型的 SEC。这意味着该函数将会在与 "tp/syscalls/sys_enter_write" 相关的 hook 点处执行,并具有相应的安全属性。在本例中,handle_tp 函数被指定为在 "sys_enter_write" 系统调用的 Tracepoint 中执行。
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() >> 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 <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#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程序环境。
头文件引入介绍
- stdio.h:包含标准输入输出库的函数和变量,例如 fprintf()、printf()、scanf() 等。
- unistd.h:包含系统调用相关库的函数和变量,例如 getpid()、sleep() 等。
- sys/resource.h:包含资源管理相关库的函数和变量,例如 getrusage() 等。
- bpf/libbpf.h:包含 BPF 相关库的函数和变量,例如 libbpf_set_print()、libbpf_strerror() 等。
- minimal.skel.h:预先生成的 BPF 程序头文件,包含 BPF 程序的结构体、函数和变量等。
其中minimal.skel.h 这个文件是由libbpf bootstrap 框架自动生成的,后面会详细讲解这个文件中会包含哪些api
libbpf_set_print
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
minimal_bpf__open() 函数是预先生成的 BPF 程序的接口之一,用于打开 BPF 程序的对象并返回一个指向该对象的指针。在这段代码中,调用 minimal_bpf__open() 函数打开预先生成的 BPF 程序对象,并将返回的指针赋值给 skel 变量。
在调用 minimal_bpf__open() 函数时,会自动进行一些初始化工作,例如动态加载 BPF 程序的 ELF 文件、创建 BPF 程序的对象等。如果打开成功,则返回一个指向 BPF 程序对象的指针;否则返回 NULL 指针表示打开失败。
skel->bss->my_pid = getpid();
首先通过 getpid() 函数获取当前进程的 PID,并将 PID 存储到 BPF 程序对象中定义的全局变量 my_pid 中。然后在 BPF 程序中,会以 my_pid 的值为条件判断系统调用事件的发起进程是否为当前进程,如果是则进行处理,否则忽略该事件。
minimal_bpf__load(skel);
minimal_bpf__load() 函数是预先生成的 BPF 程序的接口之一,用于加载和验证 BPF 程序。在加载和验证 BPF 程序时,minimal_bpf__load() 函数会执行以下操作:
- 调用 bpf_object__load() 函数加载 BPF 程序的 ELF 文件,并将 BPF 程序对象与 ELF 文件相关联。
- 调用 bpf_program__set_type() 函数设置 BPF 程序的类型。
- 调用 bpf_program__set_expected_attach_type() 函数设置 BPF 程序的预期挂载类型。
- 调用 bpf_object__attach_programs() 函数挂载 BPF 程序到指定的 Tracepoint 上。
- 调用 bpf_program__verify_attach() 函数验证 BPF 程序是否能够成功挂载到指定的 Tracepoint 上。
如果以上操作都执行成功,则返回 0;否则返回一个负数,表示加载和验证失败。
minimal_bpf__attach(skel);
minimal_bpf__attach() 函数是预先生成的 BPF 程序的接口之一,用于将 BPF 程序挂载到指定的 Tracepoint 上。在将 BPF 程序挂载到 Tracepoint 上时,minimal_bpf__attach() 函数会执行以下操作:
- 调用 bpf_object__find_program_by_name() 函数查找 BPF 程序对象中的指定程序。
- 调用 bpf_program__attach_tracepoint() 函数将 BPF 程序挂载到指定的 Tracepoint 上。
PS:其实这里就可以看出,所谓的框架生成的一些api,本质上就是对于原生的libbpf 程序接口的一些封装。
minimal_bpf__destroy(skel);
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 的信息,后面会详细介绍