目的:編譯成功的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 *) (dst_reg + off16) = src_reg
BPF_MEM |
*(size *) (dst_reg + off16) = imm32
BPF_MEM |
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,引入了一些訪問數據包的指令。現在已經廢棄不再使用。
審核編輯:劉清
-
寄存器
+關注
關注
31文章
5363瀏覽量
121195 -
JMP
+關注
關注
1文章
17瀏覽量
12620 -
ALU
+關注
關注
0文章
33瀏覽量
13135 -
BPF
+關注
關注
0文章
25瀏覽量
4055
原文標題:eBPF指令集規范v1.0
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論