此处指的是 trace kprobe bpf 程序,而不是 trace kprobe 事件。
trace kprobe 程序的 demo
demo 效果如下:
1
2
3
4
5
6
7
8
9
|
# ./fentry_fexit-kprobe
2023/07/22 06:45:15 Attached fentry(tcp_connect)
2023/07/22 06:45:15 Attached fexit(tcp_connect)
2023/07/22 06:45:15 Attached kprobe(tcp_connect)
2023/07/22 06:45:15 Attached kprobe(inet_csk_complete_hashdance)
2023/07/22 06:45:15 Listening events...
2023/07/22 06:45:16 new tcp connection: 192.168.1.106:40296 -> 172.217.194.102:443 (fentry)
2023/07/22 06:45:16 new tcp connection: 192.168.1.106:40296 -> 172.217.194.102:443 (kprobe)
2023/07/22 06:45:16 new tcp connection: 192.168.1.106:40296 -> 172.217.194.102:443 (fexit: 0)
|
其中使用的 trace 手段是 fentry 和 fexit。
demo 中使用的 fentry/fexit bpf 代码如下:
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
|
#include "lib_kprobe.h"
SEC("fentry/tcp_connect")
int BPF_PROG(fentry_tcp_connect, struct pt_regs *regs)
{
bpf_printk("tcpconn, fentry_tcp_connect\n");
struct sock *sk;
sk = (typeof(sk))PT_REGS_PARM1(regs);
__handle_new_connection(ctx, sk, PROBE_TYPE_FENTRY, 0);
return 0;
}
SEC("fexit/tcp_connect")
int BPF_PROG(fexit_tcp_connect, struct pt_regs *regs, int retval)
{
bpf_printk("tcpconn, fexit_tcp_connect\n");
struct sock *sk;
sk = (typeof(sk))PT_REGS_PARM1(regs);
__handle_new_connection(ctx, sk, PROBE_TYPE_FEXIT, retval);
return 0;
}
|
demo 中使用的 kprobe bpf 代码如下:
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
|
#include "lib_kprobe.h"
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(key_size, 4);
__uint(value_size, 4);
__uint(max_entries, 1);
} progs SEC(".maps");
SEC("kprobe/hanle_new_connection")
int handle_new_connection(struct pt_regs *ctx)
{
bpf_printk("tcpconn, handle_new_connection\n");
struct sock *sk;
sk = (typeof(sk))PT_REGS_PARM1(ctx);
__handle_new_connection(ctx, sk, PROBE_TYPE_DEFAULT, 0);
return 0;
}
SEC("kprobe/tcp_connect")
int k_tcp_connect(struct pt_regs *ctx)
{
bpf_tail_call_static(ctx, &progs, 0);
return 0;
}
SEC("kprobe/inet_csk_complete_hashdance")
int k_icsk_complete_hashdance(struct pt_regs *ctx)
{
bpf_tail_call_static(ctx, &progs, 0);
return 0;
}
|
对其中的 k_tcp_connect kprobe 进行 fenrty/fexit。
用户态的 Go 代码需要做的事情是:
1
2
3
4
5
6
7
8
9
10
11
12
|
spec, err := loadFentryFexit()
if err != nil {
log.Printf("Failed to load bpf obj: %v", err)
return
}
kprobeFentry := spec.Programs["fentry_tcp_connect"]
kprobeFentry.AttachTarget = obj.tcpconnPrograms.K_tcpConnect
kprobeFentry.AttachTo = "k_tcp_connect"
kprobeFexit := spec.Programs["fexit_tcp_connect"]
kprobeFexit.AttachTarget = obj.tcpconnPrograms.K_tcpConnect
kprobeFexit.AttachTo = "k_tcp_connect"
|
- 第一步,创建
kprobe 程序。
- 第二步,给
fentry 和 fexit 程序指定 AttachTarget 和 AttachTo。
- 其中,
AttachTarget 是 kprobe 程序,AttachTo 是 kprobe 程序中的函数名。
- 即,将
fentry 和 fexit 程序 attach 到 kprobe 程序的 k_tcp_connect 函数上。
P.S. demo 源代码:GitHub Asphaltt/learn-by-example/ebpf/fentry_fexit-kprobe
fentry/fexit 的函数参数
仔细对比上面 fentry/fexit 的函数定义和 kprobe 程序的函数定义:
1
2
3
4
5
6
7
8
|
SEC("fentry/kprobe")
int BPF_PROG(fentry_kprobe, struct pt_regs *regs);
SEC("fexit/kprobe")
int BPF_PROG(fexit_kprobe, struct pt_regs *regs, int retval);
SEC("kprobe/tcp_connect")
int k_tcp_connect(struct pt_regs *ctx);
|
因为 kprobe 程序只有一个参数:struct pt_regs *ctx,所以 fentry/fexit 的函数参数就有一个对应的参数:struct pt_regs *regs;但参数名不能叫 ctx。
fentry/fexit 的函数参数里不能再使用 ctx
这是因为 BPF_PROG() 宏里已默认提供了 ctx 参数,所以不能再使用 ctx 参数名了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#define BPF_PROG(name, args...) \
name(unsigned long long *ctx); \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args); \
typeof(name(0)) name(unsigned long long *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_ctx_cast(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args)
|
小结
不像 XDP 和 tc-bpf,kprobe 程序的参数就是运行的时候内核提供的实际参数,不需要在 verifier 阶段做 ctx 属性访问的转换。