ebpf: 如何在 eBPF 汇编程序中使用 BPF_FUNC_trace_printk
ebpf: how to use BPF_FUNC_trace_printk in eBPF assembly program
我有一个小型 socket filter
类型的 eBPF 程序,我正在尝试打印从 __sk_buff
上下文中读取的协议值:
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_6, offsetof(struct __sk_buff, protocol)),
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -4),
BPF_MOV64_IMM(BPF_REG_2, 4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_trace_printk),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
...
我创建了一个原始 socket
并将其绑定到 lo
接口,然后 setsockopt(fd, SOL_SOCKET, SO_ATTACH_BPF, ...)
。它编译和加载没有问题,但是每当我 ping 127.0.0.1
我从来没有在 trace_pipe
.
中看到痕迹
因此,为了确保它 BPF_FUNC_trace_printk
确实可以工作,我对其进行了更改,以便它在堆栈上打印一个静态字符串,并且它确实在每个到达环回的数据包上打印。
我做错了什么?
阅读友好的手册:)
我认为您没有正确调用 bpf_trace_printk()
助手(顺便说一句,BPF_FUNC_trace_prink
只是一个整数)。它的签名,注释在内核 UAPI 头文件 bpf.h 或 the bpf-helpers
man page 中,如下所示:
long bpf_trace_printk(const char *fmt, u32 fmt_size, ...);
这意味着第一个参数必须是常量、以 null 结尾的格式字符串,而不是像您那样的整数。
clang 有什么作用?
我知道你正在将你的 eBPF 程序附加到套接字并且不能从 C 编译整个程序。但是,为什么不编译 那个特定部分 作为一个通用的网络 eBPF 程序来查看字节码应该是什么样的?让我们写C代码:
#include <linux/bpf.h>
static long (*bpf_trace_printk)(const char *fmt, __u32 fmt_size, ...) = (void *) BPF_FUNC_trace_printk;
int printk_proto(struct __sk_buff *skb) {
char fmt[] = "%d\n";
bpf_trace_printk(fmt, sizeof(fmt), skb->protocol);
return 0;
}
编译成目标文件。作为记录,这不会加载,除非我们在加载时同时提供有效的许可证字符串(因为 bpf_trace_prink()
需要 GPL 兼容程序)和兼容程序类型。但在我们的例子中这并不重要,我们只想看看生成的指令。
$ clang -O2 -g -emit-llvm -c prink_protocol.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o prink_protocol.o
转储字节码:
$ llvm-objdump -d prink_protocol.o
prink_protocol.o: file format elf64-bpf
Disassembly of section .text:
0000000000000000 <printk_proto>:
0: b4 02 00 00 25 64 0a 00 w2 = 680997
1: 63 2a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r2
2: 61 13 10 00 00 00 00 00 r3 = *(u32 *)(r1 + 16)
3: bf a1 00 00 00 00 00 00 r1 = r10
4: 07 01 00 00 fc ff ff ff r1 += -4
5: b4 02 00 00 04 00 00 00 w2 = 4
6: 85 00 00 00 06 00 00 00 call 6
7: b4 00 00 00 00 00 00 00 w0 = 0
8: 95 00 00 00 00 00 00 00 exit
我们可以看到,在前两条指令中,程序将格式字符串(小端)写入堆栈:680997
is 0x000a6425
, [=20=]\nd%
。 r2
仍然包含格式字符串的长度。协议值存储在 r3
中,这是调用 bpf_trace_prink()
.
的第三个参数
我有一个小型 socket filter
类型的 eBPF 程序,我正在尝试打印从 __sk_buff
上下文中读取的协议值:
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_6, offsetof(struct __sk_buff, protocol)),
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -4),
BPF_MOV64_IMM(BPF_REG_2, 4),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_trace_printk),
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
...
我创建了一个原始 socket
并将其绑定到 lo
接口,然后 setsockopt(fd, SOL_SOCKET, SO_ATTACH_BPF, ...)
。它编译和加载没有问题,但是每当我 ping 127.0.0.1
我从来没有在 trace_pipe
.
因此,为了确保它 BPF_FUNC_trace_printk
确实可以工作,我对其进行了更改,以便它在堆栈上打印一个静态字符串,并且它确实在每个到达环回的数据包上打印。
我做错了什么?
阅读友好的手册:)
我认为您没有正确调用 bpf_trace_printk()
助手(顺便说一句,BPF_FUNC_trace_prink
只是一个整数)。它的签名,注释在内核 UAPI 头文件 bpf.h 或 the bpf-helpers
man page 中,如下所示:
long bpf_trace_printk(const char *fmt, u32 fmt_size, ...);
这意味着第一个参数必须是常量、以 null 结尾的格式字符串,而不是像您那样的整数。
clang 有什么作用?
我知道你正在将你的 eBPF 程序附加到套接字并且不能从 C 编译整个程序。但是,为什么不编译 那个特定部分 作为一个通用的网络 eBPF 程序来查看字节码应该是什么样的?让我们写C代码:
#include <linux/bpf.h>
static long (*bpf_trace_printk)(const char *fmt, __u32 fmt_size, ...) = (void *) BPF_FUNC_trace_printk;
int printk_proto(struct __sk_buff *skb) {
char fmt[] = "%d\n";
bpf_trace_printk(fmt, sizeof(fmt), skb->protocol);
return 0;
}
编译成目标文件。作为记录,这不会加载,除非我们在加载时同时提供有效的许可证字符串(因为 bpf_trace_prink()
需要 GPL 兼容程序)和兼容程序类型。但在我们的例子中这并不重要,我们只想看看生成的指令。
$ clang -O2 -g -emit-llvm -c prink_protocol.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o prink_protocol.o
转储字节码:
$ llvm-objdump -d prink_protocol.o
prink_protocol.o: file format elf64-bpf
Disassembly of section .text:
0000000000000000 <printk_proto>:
0: b4 02 00 00 25 64 0a 00 w2 = 680997
1: 63 2a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r2
2: 61 13 10 00 00 00 00 00 r3 = *(u32 *)(r1 + 16)
3: bf a1 00 00 00 00 00 00 r1 = r10
4: 07 01 00 00 fc ff ff ff r1 += -4
5: b4 02 00 00 04 00 00 00 w2 = 4
6: 85 00 00 00 06 00 00 00 call 6
7: b4 00 00 00 00 00 00 00 w0 = 0
8: 95 00 00 00 00 00 00 00 exit
我们可以看到,在前两条指令中,程序将格式字符串(小端)写入堆栈:680997
is 0x000a6425
, [=20=]\nd%
。 r2
仍然包含格式字符串的长度。协议值存储在 r3
中,这是调用 bpf_trace_prink()
.