1 Linux 实现注意事项

本文档提供了 eBPF 指令集在 Linux 内核中实现的更多具体细节。

1.1 字节交换指令

BPF_FROM_LEBPF_FROM_BE 分别作为 BPF_TO_LEBPF_TO_BE 的别名存在。

1.2 跳转指令

BPF_CALL | BPF_X | BPF_JMP (0x8d),其中辅助函数整数将从指定寄存器中读取,目前不受验证器支持。任何包含此指令的程序都将无法加载,直到添加此类支持为止。

1.3 映射

Linux 只支持对包含单个元素的数组映射执行 'map_val(map)' 操作。

Linux 使用 fd_array 来存储与 BPF 程序关联的映射。因此,map_by_idx(imm) 使用该数组中该索引处的 fd。

1.4 变量

以下 64 位即时指令指定应加载一个变量地址,该地址对应于 ‘imm’ 字段中存储的某个整数

操作码结构

操作码

伪代码

imm 类型

dst 类型

BPF_IMM | BPF_DW | BPF_LD

0x18

0x3

dst = var_addr(imm)

变量 ID

数据指针

在 Linux 上,此整数是一个 BTF ID。

1.5 传统 BPF 数据包访问指令

ISA 标准文档中所述,Linux 具有特殊的 eBPF 指令,用于访问数据包数据,这些指令是从经典 BPF 继承而来的,旨在保留在 eBPF 解释器中运行的传统套接字过滤器的性能。

这些指令有两种形式:BPF_ABS | <size> | BPF_LDBPF_IND | <size> | BPF_LD

这些指令用于访问数据包数据,并且只能在程序上下文是指向网络数据包的指针时使用。BPF_ABS 访问由立即数据指定的绝对偏移处的数据包数据,而 BPF_IND 访问数据包数据时,其偏移量除了立即数据外还包括一个寄存器的值。

这些指令有七个隐式操作数

  • 寄存器 R6 是一个隐式输入,它必须包含指向 struct sk_buff 的指针。

  • 寄存器 R0 是一个隐式输出,其中包含从数据包中获取的数据。

  • 寄存器 R1-R5 是被指令覆盖的暂存寄存器。

这些指令也带有一个隐式的程序退出条件。如果 eBPF 程序试图访问超出数据包边界的数据,程序执行将被中止。

BPF_ABS | BPF_W | BPF_LD (0x20) 意味着

R0 = ntohl(*(u32 *) ((struct sk_buff *) R6->data + imm))

其中 ntohl() 将 32 位值从网络字节序转换为主机字节序。

BPF_IND | BPF_W | BPF_LD (0x40) 意味着

R0 = ntohl(*(u32 *) ((struct sk_buff *) R6->data + src + imm))