分享两条 bpfsnoop
(emm 从 btrace
改名而来)使用 tp_btf
支持动态追踪 tracepoint 的经验。
- 如何确定 tracepoint 对应的参数?
- 如何绕过 verifier 校验
tp_btf
prog 的 BUG?
bpfsnoop
里的解法请参考:PR tp: Trace tracepoints。
tp_btf 简介
tp_btf
是 bpf
子系统在 v5.5
内核引入的新特性,方便在 tp_btf
prog 里直接使用 tracepoint 的参数。
tp_btf
是 raw_tracepoint
的 BTF 版本;直接讲解 tp_btf
的资料较少,可以参考 raw_tracepoint
的资料:
如何确定 tracepoint 对应的参数?
tp_btf
prog 里使用的参数指 /sys/kernel/debug/tracing/events
下的 tracepoint 参数吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
$ cat /sys/kernel/debug/tracing/events/sched/sched_process_fork/format
name: sched_process_fork
ID: 324
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:char parent_comm[16]; offset:8; size:16; signed:0;
field:pid_t parent_pid; offset:24; size:4; signed:1;
field:char child_comm[16]; offset:28; size:16; signed:0;
field:pid_t child_pid; offset:44; size:4; signed:1;
print fmt: "comm=%s pid=%d child_comm=%s child_pid=%d", REC->parent_comm, REC->parent_pid, REC->child_comm, REC->child_pid
|
不是的,完全不是这么回事。
先来看下正确的 tracepoint 参数:
1
2
3
|
$ sudo ./bpfsnoop -t sched_process_fork --show-func-proto
Kernel tracepoints: (total 1)
void sched_process_fork(struct task_struct *parent, struct task_struct *child);
|
看起来,完全两码事。
先看下 bpf verifier 是怎么获取 tracepoint 参数的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// kernel/bpf/verifier.c
int bpf_check_attach_target(struct bpf_verifier_log *log,
const struct bpf_prog *prog,
const struct bpf_prog *tgt_prog,
u32 btf_id,
struct bpf_attach_target_info *tgt_info)
{
// ...
/* The func_proto of "btf_trace_##tname" is generated from typedef without argument
* names. Thus using bpf_raw_event_map to get argument names.
*/
btp = bpf_get_raw_tracepoint(tname);
if (!btp)
return -EINVAL;
fname = kallsyms_lookup((unsigned long)btp->bpf_func, NULL, NULL, NULL,
trace_symbol);
bpf_put_raw_tracepoint(btp);
if (fname)
ret = btf_find_by_name_kind(btf, fname, BTF_KIND_FUNC);
// ...
}
|
啊哈,原来是:
- 根据 tracepoint 的名字查找到内部信息;
- 查找
bpf_func
字段对应的符号信息;
- 根据符号信息查找到 BTF 信息。
以下代码片段就是根据 tracepoint 的名字查找到内部信息的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// kernel/trace/bpf_trace.c
extern struct bpf_raw_event_map __start__bpf_raw_tp[];
extern struct bpf_raw_event_map __stop__bpf_raw_tp[];
struct bpf_raw_event_map *bpf_get_raw_tracepoint(const char *name)
{
struct bpf_raw_event_map *btp = __start__bpf_raw_tp;
for (; btp < __stop__bpf_raw_tp; btp++) {
if (!strcmp(btp->tp->name, name))
return btp;
}
return bpf_get_raw_tracepoint_module(name);
}
|
在 bpfsnoop
里,如法炮制,使用同样的方式读取到所有 tracepoint 的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
static __noinline void
probe_tp_info(struct bpf_raw_event_map *btp, int i)
{
struct tp_info *tp = &tps[i];
const char *str;
str = BPF_CORE_READ(btp, tp, name);
bpf_probe_read_kernel_str(tp->name, sizeof(tp->name), str);
BPF_CORE_READ_INTO(&tp->func_proto_symbol, btp, bpf_func);
BPF_CORE_READ_INTO(&tp->num_args, btp, num_args);
}
SEC("fentry/__x64_sys_nanosleep")
int probe(struct pt_regs *regs)
{
struct bpf_raw_event_map *btp = (typeof(btp)) __start;
if (run)
return BPF_OK;
run = true;
for (int i = 0; i < TP_MAX; i++) {
if (i >= nr_tps)
break;
probe_tp_info(btp, i);
btp++;
}
return BPF_OK;
}
|
接着,在 Go 里:
- 到
/proc/kallsyms
里查找 func_proto_symbol
对应的符号信息;
- 使用符号名称到内核 BTF 里查找到对应的 BTF 信息,即
*btf.Func
;
- 根据
*btf.Func
里的 *btf.FuncProto
信息打印出参数信息。
需要使用 tp_btf
的时候,欢迎使用 bpfsnoop -t <tracepoint> --show-func-proto
查看参数信息。
如何绕过 verifier 校验 tp_btf
prog 的 BUG?
如果直接使用上述方法找到的参数,可能会遇到如下 BUG:
1
2
3
4
5
6
7
8
9
10
|
func 'xdp_redirect_err' arg0 has btf_id 6973 type STRUCT 'net_device'
14: R0_w=trusted_ptr_net_device() R1=ctx()
14: (7b) *(u64 *)(r10 -96) = r0 ; R0_w=trusted_ptr_net_device() R10=fp0 fp-96_w=trusted_ptr_net_device()
15: (79) r0 = *(u64 *)(r1 +8)
func 'xdp_redirect_err' arg1 has btf_id 6845 type STRUCT 'bpf_prog'
16: R0_w=trusted_ptr_bpf_prog() R1=ctx()
16: (7b) *(u64 *)(r10 -88) = r0 ; R0_w=trusted_ptr_bpf_prog() R10=fp0 fp-88_w=trusted_ptr_bpf_prog()
17: (79) r0 = *(u64 *)(r1 +16)
func 'xdp_redirect_err' arg2 type UNKNOWN is not a struct
invalid bpf_context access off=16 size=8
|
这是在动态追踪 xdp_redirect_err
tracepoint 时遇到的问题。
如下是 xdp_redirect_err
tracepoint 的参数信息:
1
2
3
|
$ sudo ./bpfsnoop -t xdp_redirect_err --show-func-proto
Kernel tracepoints: (total 1)
void xdp_redirect_err(const struct net_device *dev, const struct bpf_prog *xdp, const void *tgt, int err, enum bpf_map_type map_type, u32 map_id, u32 index);
|
第 2 个参数是 const void *tgt
,为什么 verifier 报错了呢?看看 verifier 源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// kernel/bpf/btf.c
bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const struct bpf_prog *prog,
struct bpf_insn_access_aux *info)
{
// ...
/* skip modifiers */
while (btf_type_is_modifier(t)) {
info->btf_id = t->type;
t = btf_type_by_id(btf, t->type);
}
if (!btf_type_is_struct(t)) {
bpf_log(log,
"func '%s' arg%d type %s is not a struct\n",
tname, arg, btf_type_str(t));
return false;
}
// ...
}
|
笔者认为这是 verifier 的 BUG,得修。
但不能因为 verifier 有 BUG 而放弃使用 tp_btf
;bpfsnoop
里的解决方案是:
- 使用
bpf_probe_read_kernel()
helper 读取参数。
这方式可行吗?如果 tracepoint 有很多参数,比如 12 个参数,这样的方式可行吗?
好问题,直接看看内核 bpf_trace_run12()
的源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// kernel/trace/bpf_trace.c
static __always_inline
void __bpf_trace_run(struct bpf_raw_tp_link *link, u64 *args)
{
struct bpf_prog *prog = link->link.prog;
struct bpf_run_ctx *old_run_ctx;
struct bpf_trace_run_ctx run_ctx;
cant_sleep();
if (unlikely(this_cpu_inc_return(*(prog->active)) != 1)) {
bpf_prog_inc_misses_counter(prog);
goto out;
}
run_ctx.bpf_cookie = link->cookie;
old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);
rcu_read_lock();
(void) bpf_prog_run(prog, args);
rcu_read_unlock();
bpf_reset_run_ctx(old_run_ctx);
out:
this_cpu_dec(*(prog->active));
}
// ...
#define BPF_TRACE_DEFN_x(x) \
void bpf_trace_run##x(struct bpf_raw_tp_link *link, \
REPEAT(x, SARG, __DL_COM, __SEQ_0_11)) \
{ \
u64 args[x]; \
REPEAT(x, COPY, __DL_SEM, __SEQ_0_11); \
__bpf_trace_run(link, args); \
} \
EXPORT_SYMBOL_GPL(bpf_trace_run##x)
BPF_TRACE_DEFN_x(1);
BPF_TRACE_DEFN_x(2);
BPF_TRACE_DEFN_x(3);
BPF_TRACE_DEFN_x(4);
BPF_TRACE_DEFN_x(5);
BPF_TRACE_DEFN_x(6);
BPF_TRACE_DEFN_x(7);
BPF_TRACE_DEFN_x(8);
BPF_TRACE_DEFN_x(9);
BPF_TRACE_DEFN_x(10);
BPF_TRACE_DEFN_x(11);
BPF_TRACE_DEFN_x(12);
|
这是将所有参数都保存到一个 u64
数组里,然后通过 ctx
传递给 tp_btf
prog。
看源代码是这样,bpfsnoop
跑起来也没问题,所以这是可行的。
总结
tp_btf
值得推荐!
在使用的时候,先用 bpfsnoop -t <tracepoint> --show-func-proto
查看参数信息,然后根据参数信息编写 tp_btf
prog。
如果遇到 verifier 报错,可以使用 bpf_probe_read_kernel()
helper 读取参数。