struct task_struct *t = ...;
const char *name;
name = BPF_CORE_READ(t, mm, exe_file, fpath.dentry, d_name.name);
/* now read string contents with bpf_probe_read_kernel_str() */
/* direct pointer dereference */
name = t->mm->exe_file->fpath.dentry->d_name.name;
// 等价
/* using BPF_CORE_READ() helper */
name = BPF_CORE_READ(t, mm, exe_file, fpath.dentry, d_name.name);
#include <bpf/bpf_helpers.h>
extern int LINUX_KERNEL_VERSION __kconfig;
...
if (LINUX_KERNEL_VERSION > KERNEL_VERSION(5, 15, 0)) {
/* we are on v5.15+ */
}
Kconfig extern variables
libbpf 允许为任何内核配置(Kconfig)值声明特殊的外部变量。
只有当内核通过/proc/config.gz 公开其内核配置时才支持,幸运的是,这在现代 Linux 发行版中是非常常见的情况。
int id;
if (bpf_core_enum_value_exists(enum cgroup_subsys_id, cpu_cgrp_id))
id = bpf_core_enum_value(enum cgroup_subsys_id, cpu_cgrp_id);
else
id = -1; /* fallback value */
/* use id even if cpu_cgrp_id isn't defined */
参考资料
总结
BPF CO-RE 的使用
BTF
Compiler support
BPF loader (libbpf)
kernel
获取 kernel data
bpf_core_read
以 CO-RE 可重定位的方式读取字段的最基本辅助工具是
bpf_core_read(dst, sz, src)
,它将从 src 引用的字段中读取 sz 字节到 dst 指向的内存中。bpf_core_read_str
为了简洁,忽略了错误处理。
BPF_CORE_READ
如果你用 C 语言写一个普通的内核代码并想做到这一点,你必须做这样的事情。
用 bpf_core_read() 做这样的事情,很快就会变成一团糟。
使用 BPF_CORE_READ() 后,上面的代码是如何简化的。
BPF_CORE_READ_INTO
例如,直接返回值不起作用的常见情况是,
将上一个例子转换为 BPF_CORE_READ_INTO(),得到
BPF_CORE_READ_STR_INTO()
处理内核变化和特性检测
bpf_core_field_exists()
作为一个具体的例子,检测内核是否支持基于 perf 的 BPF 程序类型
bpf_core_type_exists()
bpf_core_enum_value_exists()
LINUX_KERNEL_VERSION
Libbpf 还提供了一个
方便的KERNEL_VERSION(major, minor, patch)宏
,用于与 LINUX_KERNEL_VERSION 进行比较。Kconfig extern variables
下面是一个声明和使用不同类型的 Kconfig 外部变量的例子
重定位枚举
守护可能失败的重新定位
在某些内核上丢失一些字段是很正常的。如果一个 BPF 程序试图用 BPF_CORE_READ() 读取一个缺失的字段,它将在 BPF 验证期间导致一个错误。
同样地,当获得在宿主内核中不存在的枚举器(或类型)的枚举值(或类型大小)时,CO-RE 重定位将失败。
如果你遇到与下面类似的错误,要知道这是因为 CO-RE 重定位未能找到相应的字段/类型/枚举。
导致 CO-RE 重定位失败的指令。libbpf 不立即报告此类错误的原因是,如果需要的话,缺失的字段/类型/枚举和相应的失败的 CO-RE 重定位能由BPF 应用程序优雅地处理。
这使得只用一个 BPF 应用程序就能适应内核类型的巨大变化(这是 "一次编译 - 到处运行"理念的一个重要目标)。
当某些字段/类型/枚举有可能丢失时,你能用处理内核变化一节中描述的检查来保护这种代码路径。
如果防护得当,BPF 验证器将知道这样的代码路径在那个特定的内核中是不可能被击中的,因此将把它作为死代码消除。
如果实际运行的内核确实有这些信息,这种方法允许选择性地捕捉内核信息的碎片。否则,BPF 应用程序能干净利落地退回到一个替代逻辑,并优雅地处理丢失的功能或数据。
只要对可能失败的 CO-RE 重定位进行适当的保护,所有这些都很有效。
这里的CO-RE 重定位是指对 BPF_CORE_READ() 系列宏的任何使用,类型/字段大小的重定位,或枚举器值的捕获。
如果目标字段/类型/枚举不存在或者有一些不兼容的定义,那么任何东西都没有意义。
继续前面 cpu_cgrp_id 枚举值的例子,为了处理可能没有定义这种枚举器的内核(例如,由于没有设置 CONFIG_CGROUP_PIDS Kconfig toggle),能使用 bpf_core_enum_value_exists() 检查
存在性检查从不失败!,它返回 true/false(严格来说,在 C 中是 0 或 1)。
上面的例子在任何内核上都能正常工作,无论 cpu_cgrp_id 枚举器是否存在,尽管 bpf_core_enum_value() 在没有 cpu_cgrp_id 枚举器的内核上失败。
所有这些都是因为有适当的代码路径的保护。
type/ebpf #libpf #type/linux #public