eBPF指令集规范v1.0原创
金庆辉,系统工程师,对系统性能分析领域有强烈的兴趣。
版权说明:本文档翻译自官方文档,额外加入了自己的一些理解。
目的:编译成功的eBPF程序,加载时偶尔会过不了内核BPF verifier,冒出一堆汇编语句。理解eBPF指令集,可以帮助我们调试这类问题。
1. eBPF指令集规范v1.0
本文档是eBPF指令集规范,版本 1.0
1.1 寄存器和使用规范
eBPF有10个通用寄存器和一个只读的栈帧寄存器,他们都是64-bit宽度。
eBPF的寄存器使用规范为:
- 
R0: 保存函数返回值和eBPF程序退出值。
 - 
R1 - R5: 用于函数调用参数。
 - 
R6 - R9: callee函数负责进入时保存这几个寄存器到栈中,函数退出前再恢复寄存器原有值。(callee saved registers that function calls will preserve)
 - 
R10: 只读的栈帧寄存器,用于访问栈。
 
R0 - R5是临时寄存器,eBPF程序如果希望在函数调用后寄存器值不变,需要自己保存和恢复寄存器。(R0 - R5 are scratch registers and eBPF programs needs to spill/fill them if necessary across calls.)
译者注:
- 
Scratch register / temporary register:顾名思义,用于保存临时值或者中间值。
 - 
Caller 和 Callee: A函数中调用B函数,那么 A是Caller,B是Callee。
 - 
Caller saved registers 和 Callee saved registers
 
| Caller saved registers(AKA volatile registers, or call-clobbered) | Callee saved registers(AKA non-volatile registers, or call-preserved) | 
|---|---|
| Caller函数负责保存和恢复寄存器(也可以不保存和恢复) | Callee函数负责保存和恢复寄存器,这样寄存器的值在子函数调用后不会改变 | 
更多资料
1.2 指令编码
eBPF有两种编码模式:
- 
基础编码,64bit固定长度编码。
 - 
宽指令编码,在基础编码后增加了一个64bit的立即数(imm64)。这样指令为128bit。
 
基础编码格式,每一列约定为field:
| 32 bits (MSB,最高bit位) | 16 bits | 4 bits | 4 bits | 8 bits (LSB,最低比特位) | 
|---|---|---|---|---|
| imm32(立即数) | off16(偏移) | src_reg(源寄存器) | dst_reg(目的寄存器) | opcode(操作码) | 
注意:绝大多数指令不会使用所有的field,不使用的field被置0。
宽指令编码目前只有64-bit立即数指令使用。
1.2.1 指令类型(class)
基础编码中的field的opcode,一共8bit,其中最低位3bit用来保存指令类型:
| class | value | description | reference | 
|---|---|---|---|
| BPF_LD | 0x00 | 只能用于宽指令,从imm64中加载数据到寄存器 | 
Load 和 store指令 | 
| BPF_LDX | 0x01 | 从内存中加载数据到dst_reg | 
Load 和 store指令 | 
| BPF_ST | 0x02 | 把imm32数据保存到内存中 | 
Load 和 store指令 | 
| BPF_STX | 0x03 | 把src_reg寄存器数据保存到内存 | 
Load 和 store指令 | 
| BPF_ALU | 0x04 | 32bit算术运算 | 算术和跳转指令 | 
| BPF_JMP | 0x05 | 64bit跳转操作 | 算术和跳转指令 | 
| BPF_JMP32 | 0x06 | 32bit跳转操作 | 算术和跳转指令 | 
| BPF_ALU64 | 0x07 | 64bit算术运算 | 算术和跳转指令 | 
1.3 算术和跳转指令
(BPF_ALU, BPF_ALU64, BPF_JMP和BPF_JMP32)称为算术和跳转指令。8bit的opcode被分为3个部分:
| 4 bits (MSB,最高bit位) | 1 bit | 3 bits (LSB,最低bit位) | 
|---|---|---|
| operation code | source | 指令类型(BPF_ALU, BPF_ALU64, BPF_JMP或BPF_JMP32) | 
第4个bit(source)含义:
| source | value | description | 
|---|---|---|
| BPF_K | 0x00 | 使用32-bitimm32作为源操作数 | 
| BPF_X | 0x08 | 使用源寄存器(src_reg)作为源操作数 | 
4个bit的operation code用来存储具体指令操作码。
1.3.1 算术指令(BPF_ALU, BPF_ALU64)
BPF_ALU操作数为32bit,BPF_ALU64操作数为64bit。4个bit的operation code编码了如下操作:
| operation code | value | description | 
|---|---|---|
| BPF_ADD | 0x00 | dst += src | 
| BPF_SUB | 0x10 | dst -= src | 
| BPF_MUL | 0x20 | dst *= src | 
| BPF_DIV | 0x30 | dst /= src | 
| BPF_OR | 0x40 | dst |= src | 
| BPF_AND | 0x50 | dst &= src | 
| BPF_LSH | 0x60 | dst <<= src | 
| BPF_RSH | 0x70 | dst >>= src | 
| BPF_NEG | 0x80 | dst = ~src | 
| BPF_MOD | 0x90 | dst %= src | 
| BPF_XOR | 0xa0 | dst ^= src | 
| BPF_MOV | 0xb0 | dst = src | 
| BPF_ARSH | 0xc0 | 算术右移操作。对于负数,右移会在左边最高位补上右移次数个1,对于正数则补0 | 
| BPF_END | 0xd0 | 字节的swap操作 | 
译者注:
- 
上表中dst一定是指目的寄存器,不支持内存地址。
 - 
上表中src可能是源寄存器,也可能是
imm32,根据source位(BPF_K或者BPF_X)来区分。 - 
eBPF寄存器都是64bit,根据操作数类型,可以使用全部64bit,也可以只使用其中32bit。
 
一些例子:
BPF_ADD | BPF_X | BPF_ALU :
dst_reg = (u32) dst_reg + (u32) src_reg;
BPF_XOR | BPF_K | BPF_ALU64 :
dst_reg = dst_reg + src_reg
BPF_XOR | BPF_K | BPF_ALU :
dst_reg = (u32) dst_reg ^ (u32) imm32
BPF_XOR | BPF_K | BPF_ALU64 :
dst_reg = dst_reg ^ imm32
1.3.1.1 字节swap指令
字节swap指令,属于BPF_ALU分类,操作码为BPF_END。
字节swap指令操作数只有dst_reg,不操作src_reg和imm32。
opcode中的source位含义现在更改为:
| source | value | description | 
|---|---|---|
| BPF_TO_LE | 0x00 | 主机字节序到小端字节序 | 
| BPF_TO_BE | 0x08 | 主字节序序到大端字节序 | 
基础编码格式中的imm32,此时编码了swap操作的位宽。支持:16,32和64bit。
例子:
BPF_ALU | BPF_TO_LE | BPF_END,并且imm32 = 16:
dst_reg = htole16(dst_reg)
BPF_ALU | BPF_TO_LE | BPF_END,并且imm32 = 64:
dst_reg = htole64(dst_reg)
1.3.2 跳转指令(BPF_JMP32, BPF_JMP)
操作数为寄存器,BPF_JMP32使用32bit,BPF_JMP使用64bit,其它行为都一样。operation code含义如下:
| operation code | value | description | notes | 
|---|---|---|---|
| BPF_JA | 0x00 | PC += off | 仅用在BPF_JMP中 | 
| BPF_JEQ | 0x10 | PC += off if dst == src | |
| BPF_JGT | 0x20 | PC += off if dst > src | unsigned | 
| BPF_JGE | 0x30 | PC += off if dst >= src | unsigned | 
| BPF_JSET | 0x40 | PC += off if dst & src | |
| BPF_JNE | 0x50 | PC += off if dst != src | |
| BPF_JSGT | 0x60 | PC += off if dst > src | signed | 
| BPF_JSGE | 0x70 | PC += off if dst >= src | signed | 
| BPF_CALL | 0x80 | 函数调用 | |
| BPF_EXIT | 0x90 | 函数或者程序返回 | 仅用在BPF_JMP分类中 | 
| BPF_JLT | 0xa0 | PC += off if dst < src | unsigned | 
| BPF_JLE | 0xb0 | PC += off if dst <= src | unsigned | 
| BPF_JSLT | 0xc0 | PC += off if dst < src | signed | 
| BPF_JSLE | 0xd0 | PC += off if dst <= src | signed | 
eBPF程序在调用BPF_EXIT前,需要把返回值保存在R0寄存器中。
译者注: 上表中的术语:
- 
PC:程序计数器。
 - 
off: 基础编码格式中的
off16。 - 
src,dst: 都是指的寄存器的值。
 
1.4 Load 和 Store指令
BPF_LD, BPF_LDX, BPF_ST和BPF_STX指令类型中,8bit的opcode含义为:
| 3 bits (MSB) | 2 bit | 3 bits (LSB) | 
|---|---|---|
mode | 
size | 
指令类型(BPF_LD, BPF_LDX, BPF_ST或BPF_STX) | 
mode含义是:
| mode | value | description | reference | 
|---|---|---|---|
| BPF_IMM | 0x00 | 64bit立即数指令 | 64bit立即数指令 | 
| BPF_ABS | 0x20 | 经典BPF数据包访问(直接) | 
经典BPF数据包访问指令 | 
| BPF_IND | 0x40 | 经典BPF数据包访问(间接) | 
经典BPF数据包访问指令 | 
| BPF_MEM | 0x60 | 标准load和store操作 | 标准load和store指令 | 
| BPF_ATOMIC | 0xc0 | 原子操作 | 原子操作 | 
size含义:
| size | value | description | 
|---|---|---|
| BPF_W | 0x00 | 字长(4字节) | 
| BPF_H | 0x08 | 半字长(2字节) | 
| BPF_B | 0x010 | 字节(1字节) | 
| BPF_DW | 0x18 | 双字长(8字节) | 
1.4.1 标准load和store指令
BPF_MEM代表了标准load和store指令,这些指令用于寄存器和内存之间传递数据。
BPF_MEM | <size> | BPF_STX:
*(size *) (dst_reg + off16) = src_reg
BPF_MEM | <size> | BPF_ST:
*(size *) (dst_reg + off16) = imm32
BPF_MEM | <size> | BPF_LDX:
dst_reg = *(size *) (src_reg + off16)
size 可选值:BPF_B, BPF_H, BPF_W, or BPF_DW
1.4.2 原子操作
原子操作,指的的是对内存的操作,不会被其他eBPF程序中途扰乱。
eBPF所有的原子操作由BPF_ATOMIC指定,例如:
- 
BPF_ATOMIC | BPF_W | BPF_STX:32-bit原子操作。 - 
BPF_ATOMIC | BPF_DW | BPF_STX:64-bit原子操作。 - 
8-bit和16-bit原子操作不支持。
 
基本编码格式的imm32用来编码真正的原子操作, 以下是简单原子指令:
| imm32 | value | description | 
|---|---|---|
| BPF_ADD | 0x00 | 原子加 | 
| BPF_OR | 0x40 | 原子或 | 
| BPF_AND | 0x50 | 原子与 | 
| BPF_XOR | 0xa0 | 原子异或 | 
BPF_ATOMIC | BPF_W | BPF_STX, imm32 = BPF_ADD,含义:
*(u32 *)(dst_reg + off16) += src_reg
BPF_ATOMIC | BPF_DW | BPF_STX, imm32 = BPF_ADD, 含义:
*(u64 *)(dst_reg + off16) += src_reg
除了以上比较简单的原子操作,还有2个复杂原子指令:
| imm32 | value | description | 
|---|---|---|
| BPF_XCHG | 0xe0|BPF_FETCH | 原子交换,交换src_reg和(dst_reg + off16)指向内存的值 | 
| BPF_CMPXCHG | 0xf0|BPF_FETCH | 原子CAS.if (*(uXX*)(dst_reg + off16) == R0) { *(uXX*)(dst_reg + off16) = (src_reg) }; 无论是否交换成功,R0都会保存(dst_reg + off16)指向内存的被修改前的原始值。如果是32bit操作数,那么用会0补齐高位后再保存到R0。 | 
BPF_FETCH:
| imm32 | value | description | 
|---|---|---|
| BPF_FETCH | 0x01 | 代表需要返回旧值 | 
- 
对于
简单原子指令是可选的,如果设置了,src_reg将保存(dst_reg + off16)指向的内存中被修改前的原始值。 - 
对于
复杂原子指令是必选的。 
1.4.3 64bit立即数指令
mode为BPF_IMM的指令,用于eBPF宽指令,有额外的一个imm64值。
目前只有一条指令:BPF_LD | BPF_DW | BPF_IMM,含义为:dst_reg = imm64
1.4.4 经典BPF数据包访问指令
eBPF之前为了兼容经典BPF,引入了一些访问数据包的指令。现在已经废弃不再使用。
