bpftool cheatsheet
Contents
背景
bpftool 是每一个搞 BPF 的人都必须熟练掌握的工具。本文总结了一些常用的命令(参考文档 [2] 列举得更为翔实),以方便大家查询。
本文将不定期持续更新。
本文 bpftool 版本信息:
|
|
bpftool 的代码源自内核,有兴趣的同学可以参考 bpftool-code。
bpftool 目前支持以下几种 subcommand,如下图所示:
从实战角度来看,prog 和 map 最为常用。
测试代码
为了验证 bpftool 的一些能力,我们提供了一个极其简单的 BPF 程序,可参考:bpf-workshop。
bpftool-prog
-
List 当前系统中所有的 BPF Prog
1 2 3 4 5
# bpftool prog # bpftool prog list # bpftool prog show
这三种用法等价。
使用
-j
选项可以以 JSON 形式打印,而用-p
则可以打印出可读性更强的 JSON 格式,不过还是更推荐结合jq
来使用:1
# bpftool prog -j | jq
-
只获取某一个或某一组 BPF Prog
1 2 3 4 5
# bpftool prog list id 1734 1734: lsm name hello tag 2649db08deb3a2b3 gpl loaded_at 2021-12-11T14:30:06+0000 uid 0 xlated 7848B jited 4954B memlock 8192B map_ids 2201,2203,2207,2204,2205,2208,2206,2202,2209 btf_id 1621
根据 Linux 内核版本的不同,对于的输出也会有所不同。
-
输出 BPF Prog 的二进制指令
1 2 3 4 5 6 7 8 9
# bpftool prog dump xlated id 1529 0: (bf) r6 = r1 1: (69) r7 = *(u16 *)(r6 +176) 2: (b4) w8 = 0 3: (44) w8 |= 2 4: (b7) r0 = 1 5: (55) if r8 != 0x2 goto pc+1 6: (b7) r0 = 0 7: (95) exit
xlated 即 translated 之意。默认会将指令反汇编之后以可读性方式打印。
如果想同时打印出二进制,可以加上
opcodes
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# bpftool prog dump xlated id 1529 opcodes 0: (bf) r6 = r1 bf 16 00 00 00 00 00 00 1: (69) r7 = *(u16 *)(r6 +176) 69 67 b0 00 00 00 00 00 2: (b4) w8 = 0 b4 08 00 00 00 00 00 00 3: (44) w8 |= 2 44 08 00 00 02 00 00 00 4: (b7) r0 = 1 b7 00 00 00 01 00 00 00 5: (55) if r8 != 0x2 goto pc+1 55 08 01 00 02 00 00 00 6: (b7) r0 = 0 b7 00 00 00 00 00 00 00 7: (95) exit 95 00 00 00 00 00 00 00
如果想将二进制写入文件,可以用:
1
# bpftool prog dump xlated id 1529 file /tmp/foo
可用一些二进制编辑工具(比如
hexdump
)进行查看。这个命令其实也等价于用
llvm-objdump
:1
# llvm-objdump -d example.bpf.o
但是二者的输出不完全相同。Linux 内核加载了 BPF 二进制之后估计会对一些跳转指令做重定位,所以与编译好但未加载的 BPF 二进制的反汇编指令会有些差异。
如果想构建 CFG(至于 BPF 为什么是 CFG,可以看看这篇文章),可以加上
visual
指令:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
# bpftool prog dump xlated id 1529 visual digraph "DOT graph for eBPF program" { subgraph "cluster_0" { style="dashed"; color="black"; label="func_0 ()"; fn_0_bb_0 [shape=Mdiamond,style=filled,label="ENTRY"]; fn_0_bb_2 [shape=record,style=filled,label="{ 0: (bf) r6 = r1\l\ | 1: (69) r7 = *(u16 *)(r6 +176)\l\ | 2: (b4) w8 = 0\l\ | 3: (44) w8 \|= 2\l\ | 4: (b7) r0 = 1\l\ | 5: (55) if r8 != 0x2 goto pc+1\l\ }"]; fn_0_bb_3 [shape=record,style=filled,label="{ 6: (b7) r0 = 0\l\ }"]; fn_0_bb_4 [shape=record,style=filled,label="{ 7: (95) exit\l\ }"]; fn_0_bb_1 [shape=Mdiamond,style=filled,label="EXIT"]; fn_0_bb_0:s -> fn_0_bb_2:n [style="solid,bold", color=black, weight=10, constraint=true]; fn_0_bb_2:s -> fn_0_bb_3:n [style="solid,bold", color=black, weight=10, constraint=true]; fn_0_bb_2:s -> fn_0_bb_4:n [style="solid,bold", color=black, weight=10, constraint=true]; fn_0_bb_3:s -> fn_0_bb_4:n [style="solid,bold", color=black, weight=10, constraint=true]; fn_0_bb_4:s -> fn_0_bb_1:n [style="solid,bold", color=black, weight=10, constraint=true]; fn_0_bb_0:s -> fn_0_bb_1:n [style="invis", constraint=true]; } }
可将输出在 GraphvizOnline 进行展示,将获得如下渲染图:
-
输出 BPF JIT 编译的指令
为了提升执行 BPF 指令的性能,内核为 BPF 指令设计了一个 JIT,可在运行时将 BPF 指令编译成 native 指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# bpftool prog dump jited id 1529 bpf_prog_6deef7357e7b4530: 0: nopl 0x0(%rax,%rax,1) 5: xchg %ax,%ax 7: push %rbp 8: mov %rsp,%rbp b: push %rbx c: push %r13 e: push %r14 10: mov %rdi,%rbx 13: movzwq 0xb0(%rbx),%r13 1b: xor %r14d,%r14d 1e: or $0x2,%r14d 22: mov $0x1,%eax 27: cmp $0x2,%r14 2b: jne 0x000000000000002f 2d: xor %eax,%eax 2f: pop %r14 31: pop %r13 33: pop %rbx 34: leaveq 35: retq
同样地也支持
opcodes
和file
命令,可参考dump xlated
。 -
将 trace pipe 的日志输出到终端
1
# bpftool prog tracelog
等价于:
1
# cat /sys/kernel/debug/tracing/trace_pipe
-
将 object 加载到 bpffs 中
1
# bpftool prog load ./example.bpf.o /sys/fs/bpf/example
load
只会加载 object file 中第一个 BPF prog。如果 object file 中存在多个 BPF prog,我们需要使用loadall
:1
# bpftool prog loadall ./example.bpf.o /sys/fs/bpf/example
如果此时没有 bpffs,可执行:
1
mount -t bpf none /sys/fs/bpf/
我们可以用这种方式简易地加载我们的二进制程序(通常是 ELF 格式)。
我们也可以使用
-d
来查看更详细的日志打印:1
# bpftool -d prog load example.bpf.o /sys/fs/bpf/example
加载成功后,我们可以用如下命令进行查看:
1
# bpftool prog list pinned /sys/fs/bpf/foo
卸载该程序只需要删除对应的文件:
1
# rm /sys/fs/bpf/fo
用这种方式加载的 BPF Prog 并不能正常工作,因为还没有 attatch,且相应的 map 也不会自动创建,但是可以测试一下 Verifier 是否能准许我们 BPF Prog 的加载。
bpftool-map
-
List 当前系统中所有的 BPF Map
1 2 3 4 5
# bpftool map # bpftool map list # bpftool map show
类比于
bpftool-prog
。 -
只获取某一个 BPF Map
1
# bpftool map show id 2369
-
创建一个 BPF Map
1
# bpftool map create /sys/fs/bpf/example type hash key 4 value 4 entries 8 name example
如上,我们创建了一个 Hash 类型,Key 和 Value 均为 4 bytes,最大 entries 为 8 的 BPF Map,并将其 Pin 在了
/sys/fs/bpf/example
。此时我们利用
pinned <path>
进行查询:1 2 3
# bpftool map show pinned /sys/fs/bpf/example 2502: hash name example flags 0x0 key 4B value 4B max_entries 8 memlock 4096B
正如我们所期待的,后面对于 Map 的读写也将以这个 Map 作为例子。
-
读写 BPF Map
承接上一个例子,如果我们想对
example
Map 写入一个 Key Value(注意 KV 大小均为 4 bytes),可以:1
# bpftool map update id 2502 key 0x01 0x02 0x03 0x04 value 0x05 0x06 0x07 0x08
我们还可以在后面加
UPDATE_FLAGS
来控制更新的行为:any
:如果 entry 存在则是更新操作,否则为创建操作;exist
:在 entry 存在的前提下执行更新操作;noexist
:只在 entry 不存在的前提下执行创建操作;
假如我们此时利用
exist
update 一个不存在的 entry:1 2
# bpftool map update id 2502 key 0x00 0x00 0x00 0x00 value 0x05 0x06 0x07 0x08 exist Error: update failed: No such file or directory
将会导致写入失败。
此时我们将进行查询:
1 2
# bpftool map lookup id 2502 key 0x01 0x02 0x03 0x04 key: 01 02 03 04 value: 05 06 07 08
如果我们想 dump 整个 Map,可用:
1 2 3
# bpftool map dump id 2502 key: 01 02 03 04 value: 05 06 07 08 Found 1 element
可配合使用
-j
或者-p
选项来得到相应的 JSON 输出。如果对应的 Map 有相应的 BTF struct,则默认 dump 的时候会结合 BTF 输出更友好的数据结构,否则 Map 的输出一般是比较原始的 Hex 格式。
我们将对应的 Key 进行删除动作:
1
# bpftool map delete id 2502 key 0x01 0x02 0x03 0x04
-
迭代访问数据
如果我们依次写入如下测试数据:
1 2 3
# bpftool map update id 2502 key 0x00 0x00 0x00 0x01 value 0x10 0x00 0x00 0x00 any # bpftool map update id 2502 key 0x00 0x00 0x00 0x02 value 0x20 0x00 0x00 0x00 any # bpftool map update id 2502 key 0x00 0x00 0x00 0x03 value 0x30 0x00 0x00 0x00 any
此时我们可以从
0x00 0x00 0x00 0x03
开始,获取 nextkey:1 2 3 4 5
# bpftool map getnext id 2502 key 0x00 0x00 0x00 0x03 key: 00 00 00 03 next key: 00 00 00 02
再从
00 00 00 02
继续执行:1 2 3 4 5
# bpftool map getnext id 2502 key 0x00 0x00 0x00 0x02 key: 00 00 00 02 next key: 00 00 00 01
最后我们从最后一个 key
0x00 0x00 0x00 0x01
执行,将返回错误:1 2
# bpftool map getnext id 2502 key 0x00 0x00 0x00 0x01 Error: can't get next key: No such file or directory
-
冻结 BPF Map
我们可以从用户层 freeze BPF Map,即变为只读状态:
1
# bpftool map show id 2502
此时我们在查看 Map 的元数据信息:
1 2 3 4
bpftool map show id 2502 2502: hash name example flags 0x0 key 4B value 4B max_entries 8 memlock 4096B frozen
可以看到有
frozen
标记。如果我们此时再执行写入动作:
1 2
# bpftool map update id 2502 key 0x00 0x00 0x00 0x00 value 0x05 0x06 0x07 0x08 any Error: update failed: Operation not permitted
将写入失败。
注意:这是一个不可逆的动作。Map 将会保持 immutable 直到被销毁。
bpftool-btf
-
List 当前系统中所有的 BTF
1
# bpftool btf show
每个加载入内核的 BTF 信息都会获得一个唯一 ID;
-
只获取某一个 BPF ID 信息
1
# bpftool btf show id 1823
-
从支持 BTF 的 Linux 系统出导出
vmlinux.h
1
# bpftool btf dump file /sys/kernel/btf/vmlinux format c
-
从某个 BPF 目标文件 dump 对应的 BTF 信息
1
# bpftool btf dump file foo.o
bpftool-perf
-
List 当前系统中 raw_tracepoint / tracepoint / kprobe 的 attachment
1 2
# bpftool perf pid 329190 fd 6: prog_id 1936 tracepoint sched_process_exec
bpftool-gen
-
生成 skeleton 头文件
1
# bpftool gen skeleton example.bpf.o > example.skel.h
skeleton 头文件必须和 libbpf 结合使用,为用户层的加载动作提供一些框架性的代码。