此处指的是 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
属性访问的转换。