使用 bpftrace 编写 eBPF 程序
bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }'
使用libbpf库来开发eBPF程序
前置条件:
内核编译选项支持 CONFIG_DEBUG_INFO_BTF=y
依赖安装:
sudo apt install clang llvm bpftool build-essential libbpf1 libbpf-dev
在没有 BTF 信息的情况下,eBPF 程序需要通过字节偏移和硬编码的方式来解析内核数据结构,这样做既繁琐又容易出错。BTF 让 eBPF 程序能够自动地根据类型信息对数据进行正确的解析,减少错误并提高开发效率。比如,内核中的 struct task_struct 或 struct net_device 等复杂数据结构,在没有 BTF 的支持下,eBPF 程序只能通过地址和偏移来手动处理。而有了 BTF 数据,eBPF 程序能够直接理解这些结构体的字段和内存布局。
许多 eBPF 功能,如 BPF Type-Safe Maps 和 BPF Type-Safe Helper 需要 BTF 支持才能正确工作。BTF 让这些特性能够获取内核类型信息,从而增强类型安全,避免类型不匹配等问题。
说白了,BTF 就是提高写和调试 BPF 程序的一种方法。
确认内核支持btf:
ls -la /sys/kernel/btf/vmlinux
生成vmlinux.h :
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
命令生成的 vmlinux.h 文件是一个包含内核数据结构和类型信息的头文件,它对于开发、调试和优化 eBPF 程序至关重要。通过这个文件,开发者可以更容易地与内核数据进行交互,编写更加高效、安全的 BPF 程序,并减少因类型不匹配而引发的错误。
用户空间编写 eBPF 程序
hello.bpf.c
/**
* 通过使用 kprobe(内核探针)在do_nanosleep函数的入口处放置钩子,实现对该系统调用的跟踪
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
// 定义一个名为do_nanosleep的 kprobe,当进入do_nanosleep时,它会被触发
SEC("kprobe/do_nanosleep")
int BPF_KPROBE(do_nanosleep)
{
pid_t pid;
// 获取当前进程的 PID(进程标识符)
pid = bpf_get_current_pid_tgid() >> 32;
// 使用bpf_printk函数在内核日志中打印 PID
bpf_printk("hello: pid = %d\n", pid);
return 0;
}
// 定义许可证,以允许程序在内核中运行
char LICENSE[] SEC("license") = "Dual BSD/GPL";
生成 ebpf 字节码
clang -g -Wall -D__x86_64__ -O2 -target bpf -I ./vmlinux.h -c hello.bpf.c -o hello.bpf.o
生成BPF 脚手架(skeleton)文件:
bpftool gen skeleton hello.bpf.o > hello.skel.h
命令用于生成 eBPF 程序的 C 语言脚手架代码,极大地简化了 BPF 程序的开发过程。通过生成的 hello.skel.h 文件,开发者可以快速获得一个与内核交互的基础框架,专注于 BPF 程序的核心逻辑,而不必去编写繁琐的底层交互代码。BPF maps 是 eBPF 程序中的核心概念之一,脚手架代码会为你自动生成与 BPF map 相关的定义和操作代码。这样,你可以通过脚手架文件提供的接口轻松管理 map 数据结构,而不必手动进行 map 的创建、更新和读取操作。
- 用户侧编写用户空间程序: 用于加载ebpf程序到内核
hello.c
/**
* ebpf 用户空间程序(loader、read ringbuffer)
*/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "hello.skel.h"
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static volatile sig_atomic_t stop;
static void sig_int(int signo)
{
stop = 1;
}
int main(int argc, char **argv)
{
struct hello_bpf *skel;
int err;
/* 设置libbpf错误和调试信息回调 */
libbpf_set_print(libbpf_print_fn);
/* 加载并验证 hello.bpf.c 应用程序 */
skel = hello_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
/* 附加 hello.bpf.c 程序到跟踪点 */
err = hello_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
/* Control-C 停止信号 */
if (signal(SIGINT, sig_int) == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
while (!stop) {
fprintf(stderr, ".");
sleep(1);
}
cleanup:
/* 销毁挂载的ebpf程序 */
hello_bpf__destroy(skel);
return -err;
}
- 程序的加载及运行
gcc hello.c -lbpf -o hello
所以运行的时候直接 ./hello 就行了吗?是的,上面的过程会把之前写的 hello.bpf.o 加载进去。