【好文推荐】
大家好,我是你们的彦祖,今天这篇文主要介绍 eBPF 的指令系统,对于想深入理解 eBPF 的同学千万不要错过,会对你有很大的帮助。
eBPF 指令系统
参考资料:
https://www.kernel.org/doc/html/latest/networking/filter.html#ebpf-opcode-encoding
BPF 是一个通用的 RISC 指令集,最初是为了用 C 的子集编写程序而设计的,这些程序可以通过编译器后端(例如 LLVM)编译成 BPF 指令,以便内核稍后可以通过将内核 JIT 编译器转换为原生操作码,以实现内核内部的最佳执行性能。
寄存器
eBPF 由 11 个 64 位寄存器、一个程序计数器和一个 512 字节的大 BPF 堆栈空间组成。寄存器被命名为r0- r10。操作模式默认为 64 位。64位的寄存器也可作32 位子寄存器使用,它们只能通过特殊的 ALU(算术逻辑单元)操作访问,使用低32位,高32位使用零填充。
寄存器的使用约定如下:
其他:在加载和存储指令中,寄存器 R6 是一个隐式输入,必须包含指向 sk_buff 的指针。寄存器 R0 是一个隐式输出,它包含从数据包中获取的数据。
指令格式
代码实现如下:
struct bpf_insn {
__u8 code; /* opcode */
__u8 dst_reg:4; /* dest register */
__u8 src_reg:4; /* source register */
__s16 off; /* signed offset */
__s32 imm; /* signed immediate constant */
};
指令类型
其中的op字段,如下:
+----------------+--------+--------------------+
| 5 bits | 3 bits |
| xxxxxx | instruction class |
+----------------+--------+--------------------+
(MSB) (LSB)
op字段的低3位,决定指令类型。指令类型包含:加载与存储指令、运算指令、跳转指令。
顺便提下:ebpf中一个字是四个字节大小,32 bits
- BPF_LD 和 BPF_LDX: 两个类都用于加载操作。BPF_LD用于加载双字。后者是从 cBPF 继承而来的,主要是为了保持 cBPF 到 BPF 的转换效率,因为它们优化了 JIT 代码。
- BPF_ST 和 BPF_STX: 两个类都用于存储操作,用于将数据从寄存器到存储器中。
- BPF_ALU 和 BPF_ALU64: 分别是32位和64位下的ALU操作。
- BPF_JMP 和 BPF_JMP32:跳转指令。JMP32的跳转范围是32位大小(一个 word)
运算和跳转指令
当 BPF_CLASS(code) == BPF_ALU 或 BPF_JMP 时,op字段可分为三部分,如下所示:
+----------------+--------+--------------------+
| 4 bits | 1 bit | 3 bits |
| operation code | source | instruction class |
+----------------+--------+--------------------+
(MSB) (LSB)
其中的第四位,可以为0或者1,在linux中,使用如下宏定义:
BPF_K 0x00
BPF_X 0x08
// #define BPF_CLASS(code) ((code) & 0x07