我有一个小型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 或bpf-helpers
手册页中注释如下:
long bpf_trace_printk(const char *fmt, u32 fmt_size, ...);
这意味着第一个参数必须是常量、以 null 结尾的格式字符串,而不是像您那样的整数。
叮当是做什么的?
我知道您正在将 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[] = "%dn";
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
0x000a6425
,