共计 5467 个字符,预计需要花费 14 分钟才能阅读完成。
经典 libbpf 范例: bootstrap 剖析 – eBPF 基础知识 Part3
《eBPF 基础知识》系列简介:
《eBPF 基础知识》系列指标是整顿一下 BPF 相干的基础知识。次要聚焦程序与内核互动接口局部。文章应用了 libbpf,但如果你不间接应用 libbpf,看本系列还是有肯定意义的,因为它聚焦于程序与内核互动接口局部,而非 libbpf 封装自身。而所有 bpf 开发框架,都要以类似的形式跟内核互动。甚至框架自身就是基于 libbpf。哪怕是 golang/rust/python/BCC/bpftrace。
- 《ELF 格局简述 – eBPF 基础知识 Part1》
- 《BPF 零碎接口 与 libbpf 示例剖析 – eBPF 基础知识 Part2》
上期《BPF 零碎接口 与 libbpf 示例剖析 – eBPF 基础知识 Part2》介绍了一个最简的 BPF 程序如何与内核互动。
这期,将图解剖析一个更为事实的实用的 BPF 程序与内核的互动过程。国内习惯:尽量多图少文字。以下假如读者曾经对 BPF 有肯定的理解,或者浏览过之前的《eBPF 基础知识》系列文章。
libbpf 提供了一个应用 libbpf 的示例:https://github.com/libbpf/libbpf-bootstrap。其中的 bootstrap 程序示范了一个最简略但事实实用的 BPF 程序加载、运行、与内核互动的过程。上面将图解剖析这个程序与内核的互动过程。
动机:为何我想学习 BPF
开始剖析前,我想说几句废话:为何我想学习 BPF?
因为是热点啊 :)。看,当年的 Block-Chain、AI、CloudNative。现在的 ChatGPT。我抵赖,35 岁前的我真会这样思考问题。而且,如果让我带着当初的认知回到 35 岁前的身材上,说不定心底也会是这个答案。
但当初,我更想使用 BPF 来有个更深远的底层技术观察力:
-
减速内核常识学习
Linux 内核当初曾经是个很简单的怪兽。很难简略间接浏览源码去了解其中的设计思维了。但只看书的话,你有会一种“书上得来终觉淺”的感觉。BPF trace 内核,keep your hands dirty。是比拟好的两头办法。能够放弃学习的趣味,让学习不失实用性。
-
内核可察看性
这个不必多说,BPF 的强项。Cloud Native + 网络监控 + 平安 将会是 BPF 的杀手利用。
- 利用可察看性
bootstrap 程序性能
bootstrap 程序自身相似经典的 execsnoop。监听内核的 exec 调用,输入到程序终端:
$ sudo ./bootstrap -d 50
TIME EVENT COMM PID PPID FILENAME/EXIT CODE
19:18:32 EXIT timeout 3817109 402466 [0] (126ms)
19:18:32 EXIT sudo 3817117 3817111 [0] (259ms)
19:18:32 EXIT timeout 3817110 402466 [0] (264ms)
19:18:33 EXIT python3.7 3817083 1 [0] (1026ms)
19:18:38 EXIT python3 3817429 3817424 [1] (60ms)
19:18:38 EXIT sh 3817424 3817420 [0] (79ms)
19:18:38 EXIT timeout 3817420 402466 [0] (80ms)
...
程序架构
bootstrap 与内核互动概述
如上图排版有问题,请点这里用 Draw.io 关上。局部带互动链接和 hover tips
上图 file descriptor
之间的连线,反映了它们之间的关联。这里简略列一下上图的流程:
- 其它过程调用的 exec,触发
sched_process_exec
tracepoint - 内核调用相干的 BPF 程序,更新 exec_start MAP
- BPF 程序把事件提交到 ringbuffer MAP
- 利用 (bootstrap(user space)) 读取 ringbuffer
内核态 BPF
在 make 的过程中,实际上是执行了:
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I.output -I../../libbpf/include/uapi -I../../vmlinux/x86/
-idirafter /usr/lib/llvm-14/lib/clang/14.0.0/include -idirafter /usr/local/include -idirafter
/usr/include/x86_64-linux-gnu -idirafter /usr/include -c minimal.bpf.c -o .output/bootstrap.bpf.o
llvm-strip -g .output/bootstrap.bpf.o
最初一行就是重点。输出是 bootstrap.bpf.c
。输入是 bootstrap.bpf.o
。这是一个 ELF 格局的文件。这个文件将会嵌入到利用中。bootstrap.bpf.o
section 如下:
$ readelf -aW examples/c/.output/bootstrap.bpf.o
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[0] NULL 0000000000000000 000000 000000 00 0 0 0
[1] .strtab STRTAB 0000000000000000 008936 0000f2 00 0 0 1
[2] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 4
[3] tp/sched/sched_process_exec PROGBITS 0000000000000000 000040 0001f8 00 AX 0 0 8
[4] .reltp/sched/sched_process_exec REL 0000000000000000 008370 000030 10 I 15 3 8
[5] tp/sched/sched_process_exit PROGBITS 0000000000000000 000238 0002a8 00 AX 0 0 8
[6] .reltp/sched/sched_process_exit REL 0000000000000000 0083a0 000050 10 I 15 5 8
[7] license PROGBITS 0000000000000000 0004e0 00000d 00 WA 0 0 1
[8] .rodata PROGBITS 0000000000000000 0004f0 000008 00 A 0 0 8
[9] .maps PROGBITS 0000000000000000 0004f8 000030 00 WA 0 0 8
[10] .BTF PROGBITS 0000000000000000 000528 0077a9 00 0 0 4
[11] .rel.BTF REL 0000000000000000 0083f0 000040 10 I 15 10 8
[12] .BTF.ext PROGBITS 0000000000000000 007cd4 00054c 00 0 0 4
[13] .rel.BTF.ext REL 0000000000000000 008430 000500 10 I 15 12 8
[14] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 008930 000006 00 E 0 0 1
[15] .symtab SYMTAB 0000000000000000 008220 000150 18 1 8 8
如果你不太理解 ELF 格局,倡议先看看,因为了解这个格局很重要。能够参考我的《ELF 格局简述 – eBPF 基础知识》
make 用户态利用
这里次要讲 skeleton 局部了。以前做过旧 RPC 的同学可能比拟理解。用一些数据去生成一个 skeleton(骨架) 代码(次要是一些数据结构和函数定义),不便使用者基于这些 skeleton 再开发程序。对于 libbpf,也是一样的。
$ bpftool gen skeleton .output/bootstrap.bpf.o
Successfully remade target file '.output/bootstrap.skel.h'
bpftool 剖析 BPF 内核态的 ELF 文件(bootstrap.bpf.o
),生成 skeleton 代码。利用就能够基于这个 skeleton 去开发了。
须要留神的是,生成的 bootstrap.skel.h
其实嵌入了 bootstrap.bpf.o
:
static inline const void *bootstrap_bpf__elf_bytes(size_t *sz)
{
*sz = 2432;
return (const void *)"\
\x7f\x45\x4c\x46\x02\x01\x01\0\0\0\0\0\0\0\0\0\x01\0\xf7\0\x01\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\0\0\0\0\0\0\0\x40\0\0\0\0\0\x40\0\x0e\0\x01\
...
跟踪 make 过程的小技巧
因为 c/c++ 我曾经放下了快 20 年了。对 make 过程的 debug 曾经遗记了。还好,有搜索引擎。
要晓得 make 过程实际上产生了什么,执行了什么 clang/gcc 命令。你当然能够看 Makefile
。但如果我始终置信 trace > source code review
。如何 trace ? 我用了个老土的办法:
make clean && reset && make SHELL="/bin/bash -x" --debug=bvi 2>&1 | tee -a make.log
其中要害是 SHELL="/bin/bash -x"
了。
分析程序加载、运行、内核互动过程
终于回到我的初心了。
bootstrap 与内核互动过程
如上图排版有问题,请点这里用 Draw.io 关上。局部带互动链接和 hover tips
图中是我跟踪的后果。用 Draw.io 关上后,每一步均有 link,点击可看到代码。鼠标放到连接线上,会 hover 出 stack(调用堆栈)。
图中的阐明曾经比拟具体。其中包含重要的数据结构和步骤。
我 fork 了我的项目到这里:
https://github.com/labilezhu/libbpf-bootstrap/tree/20230226
vscode debug 配置
你能够看到我用 vscode debug,其中 .vscode/launch.json 配置如下:
{
"configurations": [
//bootstrap
{
"name": "gdb bootstrap",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/examples/c/bootstrap",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
],
// "preLaunchTask": "C/C++: gcc build active file",
"miDebuggerPath": "/usr/bin/sudo-gdb"
},
"version": "2.0.0"
}
因为 bpf 程序须要 root 权限,所以要加上 "miDebuggerPath": "/usr/bin/sudo-gdb"
。而 /usr/bin/sudo-gdb 内容如下:
$ cat /usr/bin/sudo-gdb
sudo /usr/bin/gdb "$@"
gdb 断点设置
因为目标是察看 bpf 加载过程相干的 syscall。能够配置 gdb 的 syscall 断点:
-exec catch syscall
其中 -exec
前缀是 vscode 对间接应用 gdb 命令要求加的前缀。
gdb 断点设置的一些坑
libbpf 本身时常会查看运行期内核的对 bpf 个性的反对状况。所以有一些 syscall 是要手工疏忽的。如 stack 中有 kernel_supports(…) 的均是能够疏忽的。有没办法让 gdb 加个断点条件?当然有:
-exec catch syscall
Catchpoint 3 (any syscall) // 这里留神,3 是断点的 id,上面的命令要援用这个 id。在您的环境可能数值不同。-exec condition 3 !$_any_caller_matches("get_kernel_version|kernel_supports|bpf_object__probe_loading|btf_parse_raw|handle_event", 20)
即,stack 中曾经蕴含 kernel_supports 等等的,就一直点。
后记
这个后记和本文没什么相干了,不喜可跳过。 只是最近情绪个别,春暖花开,本应该很高兴的。毕竟 blog 的实质是记录,所以也上图一张,记录一下情绪。