关于 ebpf
为编写、加载和调试 eBPF 程序的 Agent 提供指南,覆盖 libbpf、bpftrace、bpftool、XDP 与 CO-RE。
主要功能
- 01使用 bpftrace 跟踪内核事件
- 02通过 libbpf 构建 eBPF 程序
- 03介绍 eBPF map 类型与数据传递
- 04诊断 verifier 错误
- 05编写 XDP 网络程序
- 06使用 CO-RE 提高内核版本间可移植性
适用场景
- 01跟踪系统调用和内核事件
- 02排查 eBPF verifier 加载失败
- 03在内核与用户空间间传递事件
- 04构建 XDP 数据包过滤程序
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill ebpf安装后在 Claude Code 中输入/ebpf确认已加载。
附带文件references/ebpf-map-types.md
详细介绍
eBPF
用途
指导 Agent 使用 libbpf、bpftrace 和 bpftool 编写、加载和调试 eBPF 程序。涵盖映射类型、程序类型、验证器错误、XDP 网络和 CO-RE 可移植性。
触发场景
- “如何编写 eBPF 程序来跟踪系统调用?”
- “我的 eBPF 程序因验证器错误而失败”
- “如何使用 bpftrace 跟踪内核事件?”
- “如何在内核 eBPF 和用户空间之间共享数据?”
- “如何编写 XDP 程序来过滤数据包?”
- “如何让我的 eBPF 程序兼容不同内核版本(CO-RE)?”
工作流
1. 选择合适的工具
Goal?
├── One-liner kernel tracing / scripting → bpftrace
├── Production eBPF program with userspace → libbpf (C) or aya (Rust)
├── Inspect loaded programs and maps → bpftool
└── High-performance packet processing → XDP + libbpf
2. bpftrace — 快速跟踪内核
# Trace all execve calls with comm and args
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s %s\n", comm, str(args->filename)); }'
# Count syscalls by process
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
# Latency histogram for read() syscall
bpftrace -e '
tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_read { @us = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'
# List available tracepoints
bpftrace -l 'tracepoint:syscalls:*'
bpftrace -l 'kprobe:tcp_*'
3. libbpf 骨架 — 最简 C 程序
// counter.bpf.c — kernel-side
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u64);
__uint(max_entries, 1024);
} call_count SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_read")
int trace_read(struct trace_event_raw_sys_enter *ctx)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 *cnt = bpf_map_lookup_elem(&call_count, &pid);
if (cnt)
(*cnt)++;
else {
u64 one = 1;
bpf_map_update_elem(&call_count, &pid, &one, BPF_ANY);
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
// counter.c — userspace loader
#include "counter.skel.h"
int main(void) {
struct counter_bpf *skel = counter_bpf__open();
if (!skel || counter_bpf__load(skel))
return 1;
counter_bpf__attach(skel);
// read map, print results
counter_bpf__destroy(skel);
}
# Build with libbpf 1.x
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I/usr/include/bpf \
-c counter.bpf.c -o counter.bpf.o
bpftool gen skeleton counter.bpf.o > counter.skel.h
gcc -o counter counter.c -lbpf -lelf -lz
libbpf 1.x API 变更:
// Open and load (replaces older bpf_object__open/load split patterns)
struct counter_bpf *skel = counter_bpf__open();
counter_bpf__load(skel);
counter_bpf__attach(skel);
// Or explicit file open
struct bpf_object *obj = bpf_object__open_file("counter.bpf.o", NULL);
bpf_object__load(obj);
// Skeleton generation (always via bpftool)
// bpftool gen skeleton counter.bpf.o name counter > counter.skel.h
4. eBPF 映射类型
| 映射类型 | 键→值 | 用例 |
|---|---|---|
BPF_MAP_TYPE_HASH | 任意→任意 | 按 PID 计数、状态 |
BPF_MAP_TYPE_ARRAY | u32→固定值 | 配置、按 CPU 索引的指标 |
BPF_MAP_TYPE_PERCPU_HASH | 键→每 CPU 一个值 | 无需锁的高频计数 |
BPF_MAP_TYPE_RINGBUF | — | 高效的内核→用户空间事件 |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | — | 旧式 perf 事件输出 |
BPF_MAP_TYPE_LRU_HASH | 键→值 | 连接跟踪、限制大小 |
BPF_MAP_TYPE_PROG_ARRAY | u32→程序 | 尾调用、程序链式调用 |
BPF_MAP_TYPE_XSKMAP | — | AF_XDP 套接字重定向 |
新代码应使用 BPF_MAP_TYPE_RINGBUF 而非 PERF_EVENT_ARRAY——开销更低,且支持可变大小的记录。
5. 验证器错误排查
| 错误消息 | 根本原因 | 修复方法 |
|---|---|---|
invalid mem access 'scalar' | 解引用未受边界限制的指针 | 使用前先通过空值检查验证指针 |
R0 !read_ok | 返回前未设置 R0 | 确保所有路径都设置返回值 |
jump out of range | 分支目标超出程序末尾 | 重构条件语句 |
back-edge detected | 向后跳转(循环) | 使用 bpf_loop() 辅助函数(内核 ≥5.17)或有界循环 |
unreachable insn | return 后存在死代码 | 移除死分支 |
invalid indirect read | 读取了栈上未初始化的字节 | 将结构体清零初始化:struct foo x = {} |
misaligned stack access | 指针运算导致地址未对齐 | 将读取对齐到 __u64 边界 |
# Get detailed verifier log
bpftool prog load prog.bpf.o /sys/fs/bpf/prog type kprobe \
2>&1 | head -100
# Check loaded programs
bpftool prog list
bpftool prog dump xlated id 42
6. XDP 程序
// xdp_drop_icmp.bpf.c
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
SEC("xdp")
int xdp_filter(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP)
return XDP_DROP;
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
# Attach XDP program to interface
ip link set dev eth0 xdp obj xdp_drop_icmp.bpf.o sec xdp
# Remove
ip link set dev eth0 xdp off
# Use native (driver) mode for best performance
ip link set dev eth0 xdp obj prog.bpf.o sec xdp mode native
XDP 返回码:XDP_PASS、XDP_DROP、XDP_TX(回环发送)、XDP_REDIRECT。
7. CO-RE — 编译一次,到处运行
CO-RE(Compile Once - Run Everywhere,编译一次,到处运行)使用 BTF 类型信息,在加载时重定位字段访问。
// Use BTF-based field access (CO-RE aware)
#include <vmlinux.h> // generated from running kernel's BTF
#include <bpf/bpf_core_read.h>
SEC("kprobe/tcp_connect")
int trace_connect(struct pt_regs *ctx)
{
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
u16 dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
// BPF_CORE_READ relocates the field offset at load time
bpf_printk("connect to port %d\n", bpf_ntohs(dport));
return 0;
}
# Generate vmlinux.h from running kernel
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# Verify BTF is enabled
ls /sys/kernel/btf/vmlinux
8. BPF 环形缓冲区与 perf 缓冲区
// Ring buffer (preferred for new programs — better perf, no per-CPU loss)
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(".maps");
SEC("kprobe/sys_open")
int handle_open(struct pt_regs *ctx)
{
struct event *e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_ringbuf_submit(e, 0);
// bpf_ringbuf_discard(e, 0) on error paths
return 0;
}
| 特性 | 环形缓冲区 | perf 缓冲区 |
|---|---|---|
| API | bpf_ringbuf_reserve/submit | bpf_perf_event_output |
| 反压 | 缓冲区已满时预留失败 | 可能丢弃事件 |
| 用户空间 | ring_buffer__poll(libbpf) | perf_buffer__poll |
| 多生产者 | 是 | 每 CPU 一个缓冲区 |
9. BPF 迭代器
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 1);
} iter_prog SEC(".maps");
SEC("iter/task")
int dump_tasks(struct bpf_iter__task *ctx)
{
struct task_struct *task = ctx->task;
if (task)
bpf_seq_printf(ctx->meta->seq, "%d %s\n", task->tgid, task->comm);
return 0;
}
# Read iterator output from userspace
bpftool prog tracelog # or attach iter to seq_file reader
cat /sys/kernel/debug/tracing/trace_pipe
迭代器遍历内核数据结构(任务、映射、TCP 套接字),无需为每个元素承担 kprobe 开销。
10. BPF 原子操作
// GCC/Clang atomic builtins in BPF programs (kernel 5.12+)
static __always_inline void inc_counter(__u32 *counter)
{
__sync_fetch_and_add(counter, 1);
}
// Use for per-CPU or map-backed counters under concurrent probes
高频计数优先使用每 CPU 数组映射;将数据聚合到单个映射值时使用原子操作。
完整的映射类型参考请参阅 references/ebpf-map-types.md。
相关技能
- Aya 框架 Rust eBPF 程序请使用
skills/observability/ebpf-rust - 无需 eBPF 的基于 perf 的跟踪请使用
skills/profilers/linux-perf - seccomp-bpf 系统调用过滤请使用
skills/runtimes/binary-hardening - 内核模块开发请使用
skills/low-level-programming/linux-kernel-modules - 将 XDP_REDIRECT 重定向到 AF_XDP 套接字请使用
skills/async-io/af-xdp
评论
支持 Markdown