# libbpf-bootstrap **Repository Path**: Fj1225815367/libbpf-bootstrap ## Basic Information - **Project Name**: libbpf-bootstrap - **Description**: Scaffolding for BPF application development with libbpf and BPF CO-RE - **Primary Language**: Unknown - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-31 - **Last Updated**: 2024-03-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # libbpf-bootstrap: 使用 BPF 的应用程序 [![Github Actions](https://github.com/libbpf/libbpf-bootstrap/actions/workflows/build.yml/badge.svg)](https://github.com/libbpf/libbpf-bootstrap/actions/workflows/build.yml) ## minimal `minimal` 就是这样一个最小化的实用BPF应用程序示例。 它不使用或需要BPF CO-RE,因此可以在旧版本的内核上运行。 它安装了一个跟踪点处理程序,每秒触发一次。它使用`bpf_printk()` BPF 辅助函数与外界通信。 要查看其输出, 请以root身份阅读 `/sys/kernel/debug/tracing/trace_pipe`文件: ```shell $ cd examples/c $ make minimal $ sudo ./minimal $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: BPF triggered from PID 3840345. <...>-3840345 [010] d... 3220702.101265: bpf_trace_printk: BPF triggered from PID 3840345. ``` `minimal` 非常适合作为一个简单的实验场所,可以快速尝试新的想法或BPF功能。 ## minimal_ns `minimal_ns` 与 `minimal`相同,但适用于命名空间环境。 `minimal` 不能在有命名空间的环境上工作,如容器或WSL2,因 为命名空间中进程的感知标识符(pid)与实际进程的标识符(pid)不同。要在命名空间中 执行 `minimal`,你需要用 `minimal_ns`。 ```shell $ cd examples/c $ make minimal_ns $ sudo ./minimal_ns $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-3840345 [022] d...1 8804.331204: bpf_trace_printk: BPF triggered from PID 9087. <...>-3840345 [022] d...1 8804.331215: bpf_trace_printk: BPF triggered from PID 9087. ``` ## minimal_Legacy 这个版本的`minimal`被修改以允许在甚至不允许全局变量的旧版本内核上运行。 除非在包含bpf_helpers.h之前定义了BPF_NO_GLOBAL_DATA,否则bpf_printk将使用全局变量。 此外,为了保存进程的标识符(pid),全局变量my_pid已被替换为一个元素的数组。 ``` $ cd examples/c $ make minimal_legacy $ sudo ./minimal_legacy $ sudo cat /sys/kernel/debug/tracing/trace_pipe minimal_legacy-52030 [001] .... 491227.784078: 0x00000001: BPF triggered from PID 52030. minimal_legacy-52030 [001] .... 491228.840571: 0x00000001: BPF triggered from PID 52030. minimal_legacy-52030 [001] .... 491229.841643: 0x00000001: BPF triggered from PID 52030. minimal_legacy-52030 [001] .... 491230.842432: 0x00000001: BPF triggered from PID 52030. ``` ## bootstrap `bootstrap` 是一个简单(但现实的)BPF应用程序的应用。 它追踪进程启动(确切地说是exec()系统调用系列)和退出,并发出关于文件名(filename)、标识符(PID)和父标识符(parent PID)、 以及退出状态(exit status)和进程生命周期持续时间(duration)的数据。 使用`-d ` 参数,您可以指定要记录的进程的最小持续时间。 在这种模式下,进程启动(技术上,`exec()`)事件不会输出(请参见下面的示例输出)。 `bootstrap`的创建灵感与BCC包中的[libbpf-tools](https://github.com/iovisor/bcc/tree/master/libbpf-tools)类似, 但设计为更加独立,并且具有更简单的Makefile,以简化对用户特定需求的适应。 它展示了典型的BPF功能的使用: - 协作的BPF程序(在这种特定情况下,是用于进程`exec`和`exit`事件的跟踪点处理程序); - BPF映射用于维护状态; - BPF环形缓冲区用于将数据发送到用户空间; - 全局变量用于应用程序行为参数化。 - 它利用BPF CO-RE和vmlinux.h从内核的`struct task_struct`中读取额外的进程信息。 `bootstrap`旨在成为您自己的BPF应用程序的起点,其中包括BPF CO-RE和vmlinux.h、消耗BPF环形缓冲区数据、 命令行参数解析、优雅的Ctrl-C处理等等。所有这些都已为您处理,这些关键但乏味的任务并不有趣,但是为了能够做任何有用的事情是必要的。 只需复制/粘贴并进行简单的重命名,即可开始。 以下是最小进程持续时间模式下的示例输出: ```shell $ 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) 19:18:43 EXIT timeout 3817610 402466 [0] (70ms) 19:18:43 EXIT grep 3817619 3817617 [1] (271ms) 19:18:43 EXIT timeout 3817609 402466 [0] (321ms) 19:18:44 EXIT iostat 3817585 3817531 [0] (3006ms) 19:18:44 EXIT tee 3817587 3817531 [0] (3005ms) ... ``` ## uprobe `uprobe`是处理用户空间进入和退出(返回)探针的示例,这在libbpf术语中称为`uprobe`和`uretprobe`。 它将`uprobe`和`uretprobe` BPF程序附加到自己的函数(`uprobed_add()`和`uprobed_sub()`), 分别使用`bpf_printk()`宏记录输入参数和返回结果。用户空间函数每秒触发一次: ```shell $ sudo ./uprobe libbpf: loading object 'uprobe_bpf' from buffer ... Successfully started! ........... ``` 你可以在 `/sys/kernel/debug/tracing/trace_pipe`中查看`uprobe`演示输出: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe uprobe-1809291 [007] .... 4017233.106596: 0: uprobed_add ENTRY: a = 0, b = 1 uprobe-1809291 [007] .... 4017233.106605: 0: uprobed_add EXIT: return = 1 uprobe-1809291 [007] .... 4017233.106606: 0: uprobed_sub ENTRY: a = 0, b = 0 uprobe-1809291 [007] .... 4017233.106607: 0: uprobed_sub EXIT: return = 0 uprobe-1809291 [007] .... 4017234.106694: 0: uprobed_add ENTRY: a = 1, b = 2 uprobe-1809291 [007] .... 4017234.106697: 0: uprobed_add EXIT: return = 3 uprobe-1809291 [007] .... 4017234.106700: 0: uprobed_sub ENTRY: a = 1, b = 1 uprobe-1809291 [007] .... 4017234.106701: 0: uprobed_sub EXIT: return = 0 ``` ## usdt `usdt`是处理USDT探针的示例。 它将USDT BPF程序附加到[libc:setjmp](https://www.gnu.org/software/libc/manual/html_node/Non_002dlocal-Goto-Probes.html)探针, 该探针在用户空间程序中每秒调用一次`setjmp`时触发,并使用`bpf_printk()`宏记录USDT参数。 ```shell $ sudo ./usdt libbpf: loading object 'usdt_bpf' from buffer ... Successfully started! ........... ``` 你可以在`/sys/kernel/debug/tracing/trace_pipe`查看`usdt`演示输出: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe usdt-1919077 [005] d..21 537310.886092: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d03d6a42a0, arg2 = 0, arg3 = 55d03d65e54e usdt-1919077 [005] d..21 537310.886105: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d03d6a42a0, arg2 = 0, arg3 = 55d03d65e54e usdt-1919077 [005] d..21 537311.886214: bpf_trace_printk: USDT auto attach to libc:setjmp: arg1 = 55d03d6a42a0, arg2 = 0, arg3 = 55d03d65e54e usdt-1919077 [005] d..21 537311.886227: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d03d6a42a0, arg2 = 0, arg3 = 55d03d65e54e ``` ## fentry `fentry`是一个示例,使用fentry和fexit BPF程序进行跟踪。 它将`fentry`和`fexit`跟踪附加到`do_unlinkat()`上,当删除文件时会调用该函数,并将返回值、PID和文件名(filename)记录到跟踪管道中。 与kprobes相比,重要的区别在于性能和可用性的提高。 在这个例子中,更好的可用性表现在能够直接解引用指针参数,就像在普通的C语言中一样,而不是使用各种读取助手。 **fexit**和**kretprobe**程序之间的主要区别在于,fexit程序可以访问输入参数和返回结果,而kretprobe只能访问返回结果。 fentry和fexit程序从内核版本5.5开始可用。 ```shell $ sudo ./fentry libbpf: loading object 'fentry_bpf' from buffer ... Successfully started! .......... ``` 在`/sys/kernel/debug/tracing/trace_pipe`中,`fentry`的输出应该类似于以下内容: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-9290 [004] d..2 4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file rm-9290 [004] d..2 4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file, ret = 0 rm-9290 [004] d..2 4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file2 rm-9290 [004] d..2 4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file2, ret = 0 ``` ## kprobe `kprobe`是处理内核空间进入和退出(返回)探针的示例,即libbpf术语中的`kprobe`和`kretprobe`。 它将`kprobe`和`kretprobe` BPF程序附加到`do_unlinkat()`函数上,并分别使用`bpf_printk()`宏记录PID、文件名和返回结果。 ```shell $ sudo ./kprobe libbpf: loading object 'kprobe_bpf' from buffer ... Successfully started! ........... ``` `kprobe`示例在`/sys/kernel/debug/tracing/trace_pipe`中的输出应该类似于以下内容: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-9346 [005] d..3 4710.951696: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test1 rm-9346 [005] d..4 4710.951819: bpf_trace_printk: KPROBE EXIT: ret = 0 rm-9346 [005] d..3 4710.951852: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test2 rm-9346 [005] d..4 4710.951895: bpf_trace_printk: KPROBE EXIT: ret = 0 ``` ## xdp `xdp` 是用 Rust 编写的示例(使用 libbpf-rs)。它附着在 网络设备的入口路径,并记录每个数据包的大小, 返回 `XDP_PASS` 以允许将数据包传递到内核的 网络堆栈。 ```shell $ sudo ./target/release/xdp 1 .......... ``` `xdp`示例在`/sys/kernel/debug/tracing/trace_pipe`中的输出应该类似于以下内容: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-823887 [000] d.s1 602386.079100: bpf_trace_printk: packet size: 75 <...>-823887 [000] d.s1 602386.079141: bpf_trace_printk: packet size: 66 <...>-2813507 [000] d.s1 602386.696702: bpf_trace_printk: packet size: 77 <...>-2813507 [000] d.s1 602386.696735: bpf_trace_printk: packet size: 66 ``` ## tc `tc`(Traffic Control 的缩写)是处理入站网络流量的一个示例。 它在 `lo` 接口上创建一个队列规则(qdisc),并将 `tc_ingress` BPF 程序附加到该规则上。 它报告进入 `lo` 接口的 IP 数据包的元数据。 ```shell $ sudo ./tc ... Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF program. ...... ``` `tc`示例在`/sys/kernel/debug/tracing/trace_pipe`中的输出应该类似于以下内容: ``` $ sudo cat /sys/kernel/debug/tracing/trace_pipe node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64 sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64 sshd-1254728 [006] ..s1 8737831.674349: 0: Got IP packet: tot_len: 72, ttl: 64 node-1254811 [007] ..s1 8737831.674550: 0: Got IP packet: tot_len: 71, ttl: 64 ``` ## profile `profile` 是一个使用 Rust 和 C 语言编写的示例程序,它利用了[`blazesym`](https://github.com/libbpf/blazesym) 符号化库。 该程序通过附加到 perf 事件来工作,定期在每个处理器上进行采样。如果可用的话,它会显示堆栈跟踪的地址(addresses)、符号(symbols)、文件名(file names)和行号(line numbers)。 ```shell $ sudo ./target/release/profile COMM: swapper/2 (pid=0) @ CPU 2 Kernel: 0xffffffffb59141f8: mwait_idle_with_hints.constprop.0 @ 0xffffffffb59141b0+0x48 0xffffffffb5f731ce: intel_idle @ 0xffffffffb5f731b0+0x1e 0xffffffffb5c7bf09: cpuidle_enter_state @ 0xffffffffb5c7be80+0x89 0xffffffffb5c7c309: cpuidle_enter @ 0xffffffffb5c7c2e0+0x29 0xffffffffb516f57c: do_idle @ 0xffffffffb516f370+0x20c 0xffffffffb516f829: cpu_startup_entry @ 0xffffffffb516f810+0x19 0xffffffffb5075bfa: start_secondary @ 0xffffffffb5075ae0+0x11a 0xffffffffb500015a: secondary_startup_64_no_verify @ 0xffffffffb5000075+0xe5 No Userspace Stack ``` C 语言版本和 Rust 语言版本的程序显示相同的内容。它们都使用 `blazesym` 库来对堆栈跟踪进行符号化。 ## sockfilter `sockfilter` 是一个示例程序,用于监控数据包并处理 `__sk_buff` 结构。 它通过将 `socket`BPF(Berkeley Packet Filter)程序附加到 `sock_queue_rcv_skb()` 函数上, 并从 `BPF_MAP_TYPE_RINGBUF` 类型的 BPF 映射中检索信息。然后,它将协议(protocol)、源 IP 地址(src IP)、源端口(src port)、 目标 IP 地址(dst IP)和目标端口(dst port)打印到标准输出中。 目前,该程序支持 `uapi/linux/in.h` 中定义的大多数 IPv4 协议。 要查看支持的具体协议,请查阅 `examples/c/sockfilter.c`文件中的 `ipproto_mapping`。 ```shell $ sudo ./sockfilter -i interface:lo protocol: UDP 127.0.0.1:51845(src) -> 127.0.0.1:53(dst) interface:lo protocol: UDP 127.0.0.1:41552(src) -> 127.0.0.1:53(dst) ``` ## task_iter `task_iter` 是一个使用 [BPF Iterators](https://docs.kernel.org/bpf/bpf_iterators.html) 的示例。 这个示例遍历主机上的所有任务,并获取它们的进程 ID(pid)、进程名(process name)、内核堆栈(kernel stack)以及状态(state)。 注意:你可以使用 BlazeSym 来符号化内核堆栈跟踪(就像在 `profile` 中那样),但为了简化代码,这部分被省略了。 ```shell $ sudo ./task_iter Task Info. Pid: 3647645. Process Name: TTLSFWorker59. Kernel Stack Len: 3. State: INTERRUPTIBLE Task Info. Pid: 1600495. Process Name: tmux: client. Kernel Stack Len: 6. State: INTERRUPTIBLE Task Info. Pid: 1600497. Process Name: tmux: server. Kernel Stack Len: 0. State: RUNNING Task Info. Pid: 1600498. Process Name: bash. Kernel Stack Len: 5. State: INTERRUPTIBLE ``` ## lsm `lsm` 是一个利用 [LSM BPF](https://docs.kernel.org/bpf/prog_lsm.html) 的示例程序。 在这个例子中,`bpf()` 系统调用被有效地阻止。一旦 `lsm` 程序开始运行,可以使用 `bpftool prog list` 命令来确认其成功执行。 ```shell $ sudo ./lsm libbpf: loading object 'lsm_bpf' from buffer ... Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF programs. .......... ``` `lsm` 在`/sys/kernel/debug/tracing/trace_pipe` 中的输出预期类似于以下内容: ````shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe bpftool-70646 [002] ...11 279318.416393: bpf_trace_printk: LSM: block bpf() worked bpftool-70646 [002] ...11 279318.416532: bpf_trace_printk: LSM: block bpf() worked bpftool-70646 [002] ...11 279318.416533: bpf_trace_printk: LSM: block bpf() worked ```` 当 `bpf()` 系统调用被阻止时,`bpftool prog list` 命令将产生以下输出: ```shell $ sudo bpftool prog list Error: can't get next program: Operation not permitted ``` # 构建 libbpf-bootstrap 支持多种执行相同任务的构建系统。这对于来自不同背景的开发人员来说,可以作为一个交叉参考。 ## 安装依赖项 为了构建示例程序,您需要 `clang`(至少版本11或更高)、`libelf` 和 `zlib`,这些包的名称可能会因不同的发行版而有所不同。 在 Ubuntu/Debian, 你需要: ```shell $ apt install clang libelf1 libelf-dev zlib1g-dev ``` 在 CentOS/Fedora, 你需要: ```shell $ dnf install clang elfutils-libelf elfutils-libelf-devel zlib-devel ``` ## 获取源代码 下载 Git 仓库并检出子模块: ```shell $ git clone --recurse-submodules https://github.com/libbpf/libbpf-bootstrap ``` ## C语言示例 Makefile 构建: ```shell $ git submodule update --init --recursive # check out libbpf $ cd examples/c $ make $ sudo ./bootstrap TIME EVENT COMM PID PPID FILENAME/EXIT CODE 00:21:22 EXIT python3.8 4032353 4032352 [0] (123ms) 00:21:22 EXEC mkdir 4032379 4032337 /usr/bin/mkdir 00:21:22 EXIT mkdir 4032379 4032337 [0] (1ms) 00:21:22 EXEC basename 4032382 4032381 /usr/bin/basename 00:21:22 EXIT basename 4032382 4032381 [0] (0ms) 00:21:22 EXEC sh 4032381 4032380 /bin/sh 00:21:22 EXEC dirname 4032384 4032381 /usr/bin/dirname 00:21:22 EXIT dirname 4032384 4032381 [0] (1ms) 00:21:22 EXEC readlink 4032387 4032386 /usr/bin/readlink ^C ``` CMake 构建: ```shell $ git submodule update --init --recursive # check out libbpf $ mkdir build && cd build $ cmake ../examples/c $ make $ sudo ./bootstrap <...> ``` XMake 构建 (Linux): ```shell $ git submodule update --init --recursive # check out libbpf $ cd examples/c $ xmake $ xmake run bootstrap ``` XMake 构建 (Android): ```shell $ git submodule update --init --recursive # check out libbpf $ cd examples/c $ xmake f -p android $ xmake ``` 下载 [Xmake](https://github.com/xmake-io/xmake) ```shell $ bash <(wget https://xmake.io/shget.text -O -) $ source ~/.xmake/profile ``` ## Rust语言示例 下载 `libbpf-cargo`: ```shell $ cargo install libbpf-cargo ``` 使用`cargo`构建: ```shell $ cd examples/rust $ cargo build --release $ sudo ./target/release/xdp 1 <...> ``` # 故障解决 Libbpf的调试日志对于确定问题的确切来源非常有帮助,因此在开始调试或在线提问之前,查看这些日志通常是个好主意。 `./minimal` 总是以开启 libbpf 调试日志的方式运行。 对于 `./bootstrap`,以详细模式(`-v`)运行它以查看 libbpf 调试日志: ```shell $ sudo ./bootstrap -v libbpf: loading object 'bootstrap_bpf' from buffer libbpf: elf: section(2) tp/sched/sched_process_exec, size 384, link 0, flags 6, type=1 libbpf: sec 'tp/sched/sched_process_exec': found program 'handle_exec' at insn offset 0 (0 bytes), code size 48 insns (384 bytes) libbpf: elf: section(3) tp/sched/sched_process_exit, size 432, link 0, flags 6, type=1 libbpf: sec 'tp/sched/sched_process_exit': found program 'handle_exit' at insn offset 0 (0 bytes), code size 54 insns (432 bytes) libbpf: elf: section(4) license, size 13, link 0, flags 3, type=1 libbpf: license of bootstrap_bpf is Dual BSD/GPL ... ```