Skip to the content.

libbpf-tools

上游源码:https://github.com/iovisor/bcc/tree/master/libbpf-tools

基本原理

以 opensnoop 为例:

libbpf-tools/Makefile 中相应的构建链是:

opensnoop.bpf.c -> opensnoop.bpf.o -> opensnoop.skel.h
                                             |
opensnoop.c + opensnoop.h + static libbpf ----+-> opensnoop

bpftool 可以单独加载简单的 .bpf.o,但不会替 opensnoop.c 解析参数、选择兼容 hook、轮询 ring/perf buffer 和格式化文件名。因此完整的 libbpf-tool 仍然需要配套的 用户态程序;它不是只有一个可随处加载的 BPF object。

libbpf-tools 可以平替吗 bcc-tools 吗?

BCC checkout a3dcb9a53f28 的机械统计,不把示例、旧工具和 非 Python frontend 算进去:

统计项 数量
tools/*.py 中的 BCC 工具 105
libbpf-tools/*.bpf.c 中的 libbpf 工具 57
两边名字完全相同 47
加上 fsdist、fsslower 和 sigsnoop 所提供的 BCC 同功能别名 约 59/105

fsdist、fsslower 是通用实现,构建系统会创建 ext4dist、xfsdist、 nfsdist、ext4slower、xfsslower 等别名;sigsnoop 也会提供 killsnoop 别名。因此只比较同名文件会低估覆盖率。不过即使按功能别名计算,也只有约 56%, 远没有一一对应。这个比例只是目录覆盖率,不代表功能或参数已经完全等价。

已覆盖得比较好的领域

仍只在 BCC 一侧比较突出的工具

同名工具也可能有参数差距。例如当前 BCC execsnoop 有 PPID、CPU、父进程名等 过滤或输出选项,而本地 libbpf 版本没有完全相同的参数集合。替换生产脚本前必须分别 查看两个版本的 --help,不能只替换可执行文件路径。

本地构建

https://github.com/iovisor/bcc/tree/master/libbpf-tools - 这里提供了一些有用的链接的:

ubuntu 中可以 libbpf-tools ,但是 nixos 中没有

sudo apt install libbpf-tools
cd /home/martins3/data/bcc
git submodule update --init --recursive

# 构建
cd /home/martins3/data/bcc/libbpf-tools
nix-shell -p elfutils zlib pkg-config
make USE_BLAZESYM=0 BPFTOOL=bpftool -j"$(nproc)"

这里的两个显式选项用于简化当前机器上的构建:

bcc 真的会编译一次

经典 BCC 工具确实会在每次启动时编译 BPF C,不过通常不是启动外部 clang 进程,而是在 libbcc 进程内调用 Clang/LLVM 库。

以 execsnoop 为例,完整调用链是:

  tools/execsnoop.py
    BPF(text=bpf_text)
      -> Python BPF.__init__()
      -> bpf_module_create_c_from_string()
      -> BPFModule::load_string()
      -> BPFModule::load_cfile()
      -> ClangLoader::parse()
      -> ClangLoader::do_compile()
      -> CompilerInstance::ExecuteAction()
      -> EmitLLVMOnlyAction
      -> BPFModule::finalize()
      -> LLVM BPF backend 生成 eBPF 指令

关键源码位置:

三个阶段分别完成 tracepoint 结构处理、BCC C 源码重写,以及生成 LLVM IR。

随后 /home/martins3/data/bcc/src/cc/bpf_module.cc 中 BPFModule::finalize() 设置:

mod->setTargetTriple(“bpf-pc-linux”); builder.setMArch(“bpf”); engine_->finalizeObject();

LLVM BPF 后端在这里把 LLVM IR 生成最终的 eBPF 指令。

之所以不产生 clang 子进程,是因为构建时已经将 Clang 库链接进 libbcc:

源码中真正通过 subprocess 启动外部 clang 的地方只有 /home/martins3/data/bcc/src/python/bcc/init.py 中 LoongArch 特殊 处理:

subprocess.check_output([“clang”, “-print-file-name=include”])

但它只是查询 Clang include 路径,不是编译。

因此结论是:

为什么 bcc 依赖 kheaers ,但是 Libbpf-tools 不需要

阶段 BCC Python 工具 libbpf-tools ━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Clang 编译 每次启动时 构建工具时 ────────────────── ───────────────────────────── ─────────────────────────────── 内核类型来源 当前内核 headers 构建期 vmlinux.h ────────────────── ───────────────────────────── ─────────────────────────────── 字段偏移适配 编译时确定 加载时通过目标内核 BTF 重定位 ────────────────── ───────────────────────────── ─────────────────────────────── 目标机需要 匹配的 kheaders 通常需要内核 BTF ────────────────── ───────────────────────────── ─────────────────────────────── 目标机需要 Clang 通过 libbcc 内置 Clang 前端 不需要

经典 execsnoop 的 BPF C 包含:

#include <linux/sched.h>

struct task_struct *task; task = (struct task_struct *)bpf_get_current_task(); data.ppid = task->real_parent->tgid;

Clang 在编译 task->real_parent->tgid 时必须知道:

因此 /home/martins3/data/bcc/src/cc/frontends/clang/loader.cc 中 ClangLoader::parse() 会查找:

/lib/modules/$(uname -r)/build /lib/modules/$(uname -r)/source

找不到时,再由 get_proc_kheaders() 尝试使用:

/sys/kernel/kheaders.tar.xz

/home/martins3/data/bcc/src/cc/frontends/clang/kbuild_helper.cc 中 KBuildHelper::get_flags() 会生成类似内核构建的参 数:

-Iarch/…/include -Iinclude -Iinclude/generated/uapi -include include/linux/kconfig.h -D__KERNEL__

所以 BCC 依赖的不是几个普通 UAPI 头文件,而是与运行内核匹配的内核构建头文件。

libbpf-tools 的 execsnoop 则写成:

#include #include <bpf/bpf_core_read.h>

event->ppid = BPF_CORE_READ(task, real_parent, tgid);

源码见 /home/martins3/data/bcc/libbpf-tools/execsnoop.bpf.c。

构建时,/home/martins3/data/bcc/libbpf-tools/Makefile 执行:

clang -target bpf … -c execsnoop.bpf.c -o execsnoop.bpf.o bpftool gen skeleton execsnoop.bpf.o

vmlinux.h 只为 Clang 提供一套可以完成编译的内核类型定义。BPF_CORE_READ() 同时在 .bpf.o 的 BTF.ext 中留下类似这样的 CO-RE 重定位信息:

需要读取 struct task_struct.real_parent.tgid

运行到另一台机器时,libbpf读取 /sys/kernel/btf/vmlinux,找到目标内核中这些字段的实际偏移,然后修改 BPF 指令。因此目 标机器不再需要完整 kheaders,也不需要现场运行 Clang。

需要注意,libbpf-tools 并非没有内核信息依赖,而是把依赖从:

目标内核 headers + 运行时编译

换成了:

构建期 vmlinux.h + 运行时目标内核 BTF

如果目标内核没有 BTF,使用 CO-RE 的工具也可能无法运行。这个仓库通过 ensure_core_btf() 和 BTFHub/minimal BTF 提供了一 些外部 BTF 回退方案。

一句话总结:BCC 用当前内核 headers 在现场算字段偏移;libbpf-tools 把程序提前编译,并用目标内核 BTF 在加载时修正字段 偏移。

] bpf 实现 profile 如何实现的

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。