You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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);
```
structtask_struct*t= ...;
constchar*name;
interr;
err=BPF_CORE_READ_INTO(&name, t, mm, binfmt, executable, fpath.dentry, d_name.name);
if (err) { /* handle errors */ }
/* now `name` contains the pointer to the string */
if (bpf_core_field_exists(
((unionbpf_attr*)0)->link_create.perf_event.bpf_cookie) {
/* bpf_cookie is supported */
} else {
/* bpf_cookie is NOT supported */
}
在这里,如果主机内核的 union bpf_attr 中没有 link_create.perf_event.bpf_cookie,那么 if/else 的第一个分支中的代码将永远不会被执行(也不会被验证)。
存在性检查从不失败!,它返回 true/false(严格来说,在 C 中是 0 或 1)。
```c
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 的使用
*(例如,枚举 cgroup_subsys_id,见 BPF selftest 处理它),使得无法可靠地硬编码任何特定的值。
BTF
Compiler support
BPF loader (libbpf)
kernel
获取 kernel data
bpf_core_read
bpf_core_read(dst, sz, src)
,它将从 src 引用的字段中读取 sz 字节到 dst 指向的内存中。bpf_core_read_str
BPF_CORE_READ
bpf_core_read() 虽然允许大量的控制和谨慎的错误处理,但直接使用是一个相当大的负担,特别是在读取需要通过较长的指针解除引用链访问的字段时。
读取运行进程的主可执行文件名的例子。
如果你用 C 语言写一个普通的内核代码并想做到这一点,你必须做这样的事情。
注意这一连串的指针解除引用,与一些子结构的访问(即 fpath.dentry 和 d_name.name)混在一起。
用 bpf_core_read() 做这样的事情,很快就会变成一团糟。
当然,这是一个相当极端的例子,通常指针解除引用链不会那么长,但重点是:使用这种方法做起来很痛苦。
为了使这种多步骤读取更容易编写,libbpf 提供了 BPF_CORE_READ() 宏。
使用 BPF_CORE_READ() 后,上面的代码是如何简化的。
```c
struct task_struct *t = ...;
const char *name;
BPF_CORE_READ() 直接返回读值,并且不把错误传播回去。
如果任何一个指针是 NULL 或指向无效的内存,你将得到 0(或 NULL)的响应。
如果确实需要错误传播和处理,你必须使用低级别的 bpf_core_read() 原语并明确地处理错误。
这在实践中通常不是一个问题,也不是必须的。
BPF_CORE_READ_INTO
BPF_CORE_READ_STR_INTO()
处理内核变化和特性检测
bpf_core_field_exists()
bpf_core_type_exists()
bpf_core_enum_value_exists()
LINUX_KERNEL_VERSION
检测是否存在必要的功能的唯一方法是通过检查 Linux 内核的版本。
Libbpf 允许使用一个特殊的外部变量从 BPF 程序代码中做到这一点。
一旦声明,LINUX_KERNEL_VERSION 就会以与内核本身完全相同的方式对运行中的内核版本进行编码。
这样一个变量能像其他变量一样被使用:你能和它进行比较,打印它,记录和发送它到用户空间等等。
在所有这些情况下,BPF 验证器都知道它的精确值,因此它能检测到死代码,就像上面描述的基于类型系统的检查一样。
Libbpf 还提供了一个
方便的KERNEL_VERSION(major, minor, patch)宏
,用于与 LINUX_KERNEL_VERSION 进行比较。Kconfig extern variables
重定位枚举
守护可能失败的重新定位
在某些内核上丢失一些字段是很正常的。如果一个 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)。
```c
int id;
上面的例子在任何内核上都能正常工作,无论 cpu_cgrp_id 枚举器是否存在,尽管 bpf_core_enum_value() 在没有 cpu_cgrp_id 枚举器的内核上失败。
所有这些都是因为有适当的代码路径的保护。
#type/ebpf #libpf #type/linux #public
The text was updated successfully, but these errors were encountered: