目录
代码
BPF程序部分
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#define BPF_NO_GLOBAL_DATA
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
typedef unsigned int u32;
typedef int pid_t;
char LICENSE[] SEC("license") = "Dual BSD/GPL";
/* Create an array with 1 entry instead of a global variable
* which does not work with older kernels */
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, pid_t);
} my_pid_map SEC(".maps");
SEC("tp/syscalls/sys_enter_write")
int handle_tp(void *ctx)
{
u32 index = 0;
pid_t pid = bpf_get_current_pid_tgid() >> 32;
pid_t *my_pid = bpf_map_lookup_elem(&my_pid_map, &index);
if (!my_pid || *my_pid != pid)
return 1;
bpf_printk("BPF triggered from PID %d.\n", pid);
return 0;
}
功能说明
与minimal 一致,在系统调用 write 开始时,检查当前进程的 PID 是否与 my_pid_map 中存储的 PID 匹配,如果匹配就打印一条信息。这可以用于追踪特定进程的 write 系统调用
头文件引入说明
- #include <linux/bpf.h>这个头文件定义了 BPF 程序中的核心数据类型和相关的宏。这些定义包括 BPF 程序类型(例如 BPF_PROG_TYPE_XDP、BPF_PROG_TYPE_SOCKET_FILTER 等)、BPF map 类型(例如 BPF_MAP_TYPE_HASH、BPF_MAP_TYPE_ARRAY 等)以及一些函数原型等。在代码中,__uint(type, BPF_MAP_TYPE_ARRAY); 和 __uint(max_entries, 1); 这两行代码使用了 <linux/bpf.h> 中定义的 __uint 宏和 BPF_MAP_TYPE_ARRAY 类型。
- #include <bpf/bpf_helpers.h>这个头文件定义了一组辅助函数,这些函数可以在 BPF 程序中使用,以便与内核进行交互。这些辅助函数包括用于操作 BPF maps 的函数(例如 bpf_map_lookup_elem、bpf_map_update_elem 等)、获取当前上下文信息的函数(例如 bpf_get_current_pid_tgid 等)以及一些其他功能的函数。在代码中,bpf_get_current_pid_tgid() 和 bpf_map_lookup_elem(&my_pid_map, &index); 这两行代码使用了 <bpf/bpf_helpers.h> 中定义的函数。
- #include <bpf/bpf_tracing.h>这个头文件包含了在 BPF 程序中使用的跟踪点(Tracepoints)相关的定义和宏。Tracepoints 可以用于在内核中的特定位置插入钩子,从而收集系统的运行信息。在代码中,SEC("tp/syscalls/sys_enter_write") 这一行使用了 <bpf/bpf_tracing.h> 中定义的 SEC 宏,该宏用于指定一个 Tracepoint 的位置。
my_pid_map SEC(".maps") 介绍
这是 BPF map,是 eBPF 程序和用户空间程序交互的主要方式。BPF map 是一种在内核中存储数据的数据结构,它可以被加载的 BPF 程序和用户空间的应用程序共享。
- __uint(type, BPF_MAP_TYPE_ARRAY); 这行定义了 BPF map 的类型为 BPF_MAP_TYPE_ARRAY。这是一个简单的数组类型的 map,基于索引来存储和查找元素。
- __uint(max_entries, 1); 这行定义了 BPF map 的最大条目数,也就是说,这个 map 可以存储的元素数量最多为 1。
- __type(key, u32); 这行定义了 BPF map 的键的类型为 u32(无符号的 32 位整数)。
- __type(value, pid_t); 这行定义了 BPF map 的值的类型为 pid_t(在这个程序中定义为 int,通常用来表示进程 ID)。
- my_pid_map SEC(".maps"); 这行定义了这个 BPF map 的名称为 my_pid_map,并且指定了这个 map 存储在 ".maps" 的 ELF section 中,这样 BPF loader 才能找到并加载这个 map。
以下是一些常见的 BPF map 类型:
- BPF_MAP_TYPE_HASH:这是一个基于哈希表的 map,可以使用任意类型的键进行访问。
- BPF_MAP_TYPE_ARRAY:这是一个基于数组的 map,使用无符号 32 位整数作为键进行访问。
- BPF_MAP_TYPE_PROG_ARRAY:这是一种特别的数组 map,它的值是其他 BPF 程序的文件描述符。这种 map 可以用于实现 BPF 程序的跳转。
- BPF_MAP_TYPE_PERF_EVENT_ARRAY:这种 map 用于存储 perf_event 文件描述符,常常用于与 perf 工具集成。
- BPF_MAP_TYPE_PERCPU_HASH:这是一个每 CPU 的哈希表 map,每个 CPU 都有自己的独立的哈希表。
- BPF_MAP_TYPE_PERCPU_ARRAY:这是一个每 CPU 的数组 map,每个 CPU 都有自己的独立的数组。
- BPF_MAP_TYPE_STACK_TRACE:这种 map 用于存储堆栈跟踪的结果。
- BPF_MAP_TYPE_CGROUP_ARRAY:这种 map 用于存储 cgroup 文件描述符,可以用于 cgroup 级别的 BPF 程序。
- BPF_MAP_TYPE_LRU_HASH:这是一个 LRU(最近最少使用)策略的哈希表 map。
- BPF_MAP_TYPE_LRU_PERCPU_HASH:这是一个每 CPU 的 LRU 策略的哈希表 map。
以下是一些常见的 ELF section:
- .text:这个 section 通常包含程序的机器代码。
- .rodata:这个 section 通常包含只读数据,例如字符串常量和其他常量。
- .bss:这个 section 通常包含未初始化的全局变量。在程序开始执行前,内核会把这个区域清零。
- .data:这个 section 通常包含已初始化的全局变量。
- .maps:这个 section 用于存放 BPF map 的定义。
- .license:这个 section 包含一个字符串,表示加载 BPF 程序时所需的许可证。
bpf_map_lookup_elem
void *bpf_map_lookup_elem(struct bpf_map *map, void *key);
作用是在 BPF map 中查找一个元素。在这个例子中,BPF map 的名字是 my_pid_map,而要查找的元素的键是 index(也就是0)。
这个函数接收两个参数:一个是 map 的引用,另一个是要查找的元素的键。如果找到了对应的元素,这个函数会返回指向该元素的指针;如果没有找到,或者发生了错误,这个函数会返回 NULL。
用户程序部分
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "minimal_legacy.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_legacy_bpf *skel;
int err;
pid_t pid;
unsigned index = 0;
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Load and verify BPF application */
skel = minimal_legacy_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
/* ensure BPF program only handles write() syscalls from our process */
pid = getpid();
err = bpf_map__update_elem(skel->maps.my_pid_map, &index, sizeof(index), &pid,
sizeof(pid_t), BPF_ANY);
if (err < 0) {
fprintf(stderr, "Error updating map with pid: %s\n", strerror(err));
goto cleanup;
}
/* Attach tracepoint handler */
err = minimal_legacy_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_legacy_bpf__destroy(skel);
return -err;
}
功能说明
功能与minimal 是一致的,不同的是pid 的传入方式改为了使用一个 bpf程序和用户态程序共享的map 数组
err = bpf_map__update_elem(skel->maps.my_pid_map, &index, sizeof(index), &pid,
sizeof(pid_t), BPF_ANY);
bpf_map__update_elem
bpf_map__update_elem 是一个 BPF helper 函数,用于更新 BPF map 中的一个元素。这个函数接收三个参数:map 的文件描述符、要更新的元素的键和新的值。
int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags);
其中,fd 是 map 的文件描述符,key 是指向要更新的元素的键的指针,value 是指向新的值的指针,flags 是用于指定更新操作的行为的标志。在大多数情况下,flags 的值应该设为 BPF_ANY,这意味着如果 map 中已经存在键为 key 的元素,那么就更新这个元素的值,否则就插入一个新的元素。
函数的返回值是一个整数。如果更新成功,返回值为 0。如果更新失败,返回值为一个负的错误码。
当你调用 bpf_map_update_elem 更新 map 中的一个元素时,需要提供一个标志来指定更新操作的行为。这个标志可以是以下几个值之一:
- BPF_ANY:无论 key 是否存在,都插入或更新 map 中的元素。如果 key 已经存在,那么就更新相应的元素。如果 key 不存在,那么就创建一个新的元素。
- BPF_NOEXIST:只有当 key 不存在时,才插入一个新的元素。如果 key 已经存在,那么操作会失败。
- BPF_EXIST:只有当 key 已经存在时,才更新相应的元素。如果 key 不存在,那么操作会失败。
执行效果
minimal_legacy-29334 [012] d..31 22642.892062: bpf_trace_printk: BPF triggered from PID 29334.
minimal_legacy-29334 [012] d..31 22643.892624: bpf_trace_printk: BPF triggered from PID 29334.
minimal_legacy-29334 [012] d..31 22644.893188: bpf_trace_printk: BPF triggered from PID 29334.
minimal_legacy-29334 [012] d..31 22645.893750: bpf_trace_printk: BPF triggered from PID 29334.