Published at 2022-05-02 | Last Update 2022-05-02
本文是阅读一些 BPF 高级教程时所作的笔记。
平时学习和使用 BPF 时所整理。由于是笔记而非教程,因此内容不会追求连贯,有基础的 同学可作查漏补缺之用。
文中涉及的代码,如无特殊说明,均基于内核 5.10 版本。
本节将介绍的几种打印日志方式最终都会输出到 debugfs 路径 /sys/kernel/debug/tracing/trace
:
$ sudo tail /sys/kernel/debug/tracing/trace
# 字段说明 <taskname>-<pid> <cpuid> <opts> <timestamp> <fake by bpf> <log content>
telnet-470 [001] .N.. 419421.045894: 0x00000001: <formatted msg>
以上看到的是默认 trace 输出格式,
- 可通过
/sys/kernel/debug/tracing/trace_options
定制化 trace 输出格式(打印哪些列);- 另外还可参考
/sys/kernel/debug/tracing/README
,其中有更详细的说明。
字段说明:
telnet
:进程名;470
:进程 ID;001
:进程所在的 CPU;.N..
:每个字符表示一组配置选项,依次为,
TIF_NEED_RESCHED
和 PREEMPT_NEED_RESCHED
标志位;419421.045894
:时间戳;0x00000001
:BPF 使用的一个 fake value,for instruction pointer register;<formatted msg>
:日志内容。bpf_printk()
:kernel 5.2+
这是内核 libbpf 库提供的一个宏:
// https://github.com/torvalds/linux/blob/v5.10/tools/lib/bpf/bpf_helpers.h#L17
/* Helper macro to print out debug messages */
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), \
##__VA_ARGS__); \
})
使用非常方便,和 C 的 printf()
差不多,例如,
bpf_printk("tcp_v4_connect latency_us: %u", latency_us);
需要内核 5.2+,否则编译能通过,但执行时会报错:
map .rodata: map create: read- and write-only maps not supported (requires >= v5.2)
这个错误提示非常奇怪(实际上目前来说,大部分 BPF 错误提示都不那么直接)。
简单来说,BPF 的栈空间非常小,每次调用 bpf_printk()
都会动态声明一个 char ____fmt[] = fmt;
并放到栈上,导致性能很差。
5.2 引入了 BPF global (and static) 变量,因此 clang 在编译时
可以直接将这些变量放到 ELF 的只读区域(.rodata
,read-only data),libbpf
加载程序时将这些数据放到一个 .rodata
BPF map 中,程序在用到这些变量时,背后执行一次 map lookup 即可。
相比于每次都在栈上创建一个字符数组(字符串),这样更加快速和高效。
更多内容,见 Andrii Nakryiko 的博客 Improving bpf_printk()
。
最多只能带 3 个参数,即 bpf_printk(fmt, arg1, arg2, arg3)
。
这是由 bpf_trace_printk()
的限制决定的,下一节有具体解释。
前面已经看到 bpf_printk()
非常简单,只是单纯封装了一下 bpf_trace_printk()
,
后者定义在 include/uapi/linux/bpf.h
,具体实现见下文。
bpf_trace_printk()
对于 5.2 以下的内核,打印日志可以用 bpf_trace_printk()
,它比 bpf_printk()
要麻烦一点:要提前声明格式字符串 fmt
。
// https://github.com/torvalds/linux/blob/v5.10/include/uapi/linux/bpf.h#L772
/**
* long bpf_trace_printk(const char *fmt, u32 fmt_size, ...)
*/
功能与 printk()
类似,按指定格式将日志打印到 /sys/kernel/debug/tracing/trace
中;
但支持的格式比 printk() 少;
5.10
支持 %d
, %i
, %u
, %x
, %ld
, %li
, %lu
, %lx
, %lld
,
%lli
, %llu
, %llx
, %p
, %s
。不支持指定字符串或数字长度等,否则会返回
-EINVAL
(同时什么都不打印)。5.13
有进一步增强,见 Detecting full-powered bpf_trace_printk()。/sys/kernel/debug/tracing/trace
is open,日志会被丢弃,
可使用 /sys/kernel/debug/tracing/trace_pipe
来避免这种情况;fmt
格式串是否有默认换行:
5.9
之前没有,需要自己加 \n
;5.9+
会默认加一个换行符,patch 见 bpf: Use dedicated bpf_trace_printk event instead of trace_printk()。函数的返回值是写到 buffer 的字节数,出错时返回负的 error code。
例子:
char fmt[] = "tcp_v4_connect latency_us: %u";
bpf_printk(fmt, sizeof(fmt), latency_us);
fmt
和 fmt_size
已经占了两个了);5.13
有进一步改进,详见 Detecting full-powered bpf_trace_printk()。实现:
// https://github.com/torvalds/linux/blob/v5.10/kernel/trace/bpf_trace.c#L428
BPF_CALL_5(bpf_trace_printk, char *, fmt, u32, fmt_size, u64, arg1, u64, arg2, u64, arg3)
{
...
}
其中 BPF_CALL_5 的定义:
// https://github.com/torvalds/linux/blob/v5.10/include/linux/filter.h#L485
#define BPF_CALL_x(x, name, ...) \
static __always_inline u64 ____##name(__BPF_MAP(x, __BPF_DECL_ARGS, __BPF_V, __VA_ARGS__)); \
typedef u64 (*btf_##name)(__BPF_MAP(x, __BPF_DECL_ARGS, __BPF_V, __VA_ARGS__)); \
u64 name(__BPF_REG(x, __BPF_DECL_REGS, __BPF_N, __VA_ARGS__)); \
u64 name(__BPF_REG(x, __BPF_DECL_REGS, __BPF_N, __VA_ARGS__)) { \
return ((btf_##name)____##name)(__BPF_MAP(x,__BPF_CAST,__BPF_N,__VA_ARGS__));\
} \
static __always_inline u64 ____##name(__BPF_MAP(x, __BPF_DECL_ARGS, __BPF_V, __VA_ARGS__))
#define BPF_CALL_5(name, ...) BPF_CALL_x(5, name, __VA_ARGS__)
BPF trampoline 可以 作为内核函数之间、BPF 程序和其他 BPF 程序之间的桥梁。使用场景之一就是 tracing 其他 BPF 程序。这个功能来自 XDP 开发过程中的痛点。 现在能向任何网络类型的 BPF 程序 attach 类似 fentry/fexit 的 BPF 程序,因 此能够看到 XDP、TC、LWT、cgroup 等任何类型 BPF 程序中包的进进出出,而不会影 响到这些程序的执行,大大降低了基于 BPF 的网络排障难度。
一些 patch,如果感兴趣:
BPF trampoline 其他使用场景:
动态链接 BPF 程序(dynamicly link BPF programs)。
在 tracing、networking、cgroup BPF 程序中,中,是比 prog array 和 prog link list 更加通用的机制。 在很多情况下,可直接作为基于 bpf_tail_call 程序链的一种替代方案。
这些特性都需要 root 权限。
5.5+
bpf_dbg
(仅限 cBPF)