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().

的第三个参数