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 | | BPF_STX:

*(size *) (dst_reg + off16) = src_reg

BPF_MEM | | BPF_ST:

*(size *) (dst_reg + off16) = imm32

BPF_MEM | | 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,引入了一些访问数据包的指令。现在已经废弃不再使用。





审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分