目的:編譯成功的eBPF程序,加載時偶爾會過不了內(nèi)核BPF verifier,冒出一堆匯編語句。理解eBPF指令集,可以幫助我們調(diào)試這類問題。
1. eBPF指令集規(guī)范v1.0
本文檔是eBPF指令集規(guī)范,版本 1.0
1.1 寄存器和使用規(guī)范
eBPF有10個通用寄存器和一個只讀的棧幀寄存器,他們都是64-bit寬度。
eBPF的寄存器使用規(guī)范為:
R0: 保存函數(shù)返回值和eBPF程序退出值。
R1 - R5: 用于函數(shù)調(diào)用參數(shù)。
R6 - R9: callee函數(shù)負責進入時保存這幾個寄存器到棧中,函數(shù)退出前再恢復寄存器原有值。(callee saved registers that function calls will preserve)
R10: 只讀的棧幀寄存器,用于訪問棧。
R0 - R5是臨時寄存器,eBPF程序如果希望在函數(shù)調(diào)用后寄存器值不變,需要自己保存和恢復寄存器。(R0 - R5 are scratch registers and eBPF programs needs to spill/fill them if necessary across calls.)
譯者注:
Scratch register / temporary register:顧名思義,用于保存臨時值或者中間值。
Caller 和 Callee: A函數(shù)中調(diào)用B函數(shù),那么 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函數(shù)負責保存和恢復寄存器(也可以不保存和恢復) | Callee函數(shù)負責保存和恢復寄存器,這樣寄存器的值在子函數(shù)調(diào)用后不會改變 |
更多資料
1.2 指令編碼
eBPF有兩種編碼模式:
基礎(chǔ)編碼,64bit固定長度編碼。
寬指令編碼,在基礎(chǔ)編碼后增加了一個64bit的立即數(shù)(imm64)。這樣指令為128bit。
基礎(chǔ)編碼格式,每一列約定為field:
32 bits (MSB,最高bit位) | 16 bits | 4 bits | 4 bits | 8 bits (LSB,最低比特位) |
---|---|---|---|---|
imm32(立即數(shù)) | off16(偏移) | src_reg(源寄存器) | dst_reg(目的寄存器) | opcode(操作碼) |
注意:絕大多數(shù)指令不會使用所有的field,不使用的field被置0。
寬指令編碼目前只有64-bit立即數(shù)指令使用。
1.2.1 指令類型(class)
基礎(chǔ)編碼中的field的opcode,一共8bit,其中最低位3bit用來保存指令類型:
class | value | description | reference |
---|---|---|---|
BPF_LD | 0x00 | 只能用于寬指令,從imm64中加載數(shù)據(jù)到寄存器 | Load 和 store指令 |
BPF_LDX | 0x01 | 從內(nèi)存中加載數(shù)據(jù)到dst_reg | Load 和 store指令 |
BPF_ST | 0x02 | 把imm32數(shù)據(jù)保存到內(nèi)存中 | Load 和 store指令 |
BPF_STX | 0x03 | 把src_reg寄存器數(shù)據(jù)保存到內(nèi)存 | Load 和 store指令 |
BPF_ALU | 0x04 | 32bit算術(shù)運算 | 算術(shù)和跳轉(zhuǎn)指令 |
BPF_JMP | 0x05 | 64bit跳轉(zhuǎn)操作 | 算術(shù)和跳轉(zhuǎn)指令 |
BPF_JMP32 | 0x06 | 32bit跳轉(zhuǎn)操作 | 算術(shù)和跳轉(zhuǎn)指令 |
BPF_ALU64 | 0x07 | 64bit算術(shù)運算 | 算術(shù)和跳轉(zhuǎn)指令 |
1.3 算術(shù)和跳轉(zhuǎn)指令
(BPF_ALU, BPF_ALU64, BPF_JMP和BPF_JMP32)稱為算術(shù)和跳轉(zhuǎn)指令。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作為源操作數(shù) |
BPF_X | 0x08 | 使用源寄存器(src_reg)作為源操作數(shù) |
4個bit的operation code用來存儲具體指令操作碼。
1.3.1 算術(shù)指令(BPF_ALU, BPF_ALU64)
BPF_ALU操作數(shù)為32bit,BPF_ALU64操作數(shù)為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 | 算術(shù)右移操作。對于負數(shù),右移會在左邊最高位補上右移次數(shù)個1,對于正數(shù)則補0 |
BPF_END | 0xd0 | 字節(jié)的swap操作 |
譯者注:
上表中dst一定是指目的寄存器,不支持內(nèi)存地址。
上表中src可能是源寄存器,也可能是imm32,根據(jù)source位(BPF_K或者BPF_X)來區(qū)分。
eBPF寄存器都是64bit,根據(jù)操作數(shù)類型,可以使用全部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 字節(jié)swap指令
字節(jié)swap指令,屬于BPF_ALU分類,操作碼為BPF_END。
字節(jié)swap指令操作數(shù)只有dst_reg,不操作src_reg和imm32。
opcode中的source位含義現(xiàn)在更改為:
source | value | description |
---|---|---|
BPF_TO_LE | 0x00 | 主機字節(jié)序到小端字節(jié)序 |
BPF_TO_BE | 0x08 | 主字節(jié)序序到大端字節(jié)序 |
基礎(chǔ)編碼格式中的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 跳轉(zhuǎn)指令(BPF_JMP32, BPF_JMP)
操作數(shù)為寄存器,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 | 函數(shù)調(diào)用 | |
BPF_EXIT | 0x90 | 函數(shù)或者程序返回 | 僅用在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程序在調(diào)用BPF_EXIT前,需要把返回值保存在R0寄存器中。
譯者注: 上表中的術(shù)語:
PC:程序計數(shù)器。
off: 基礎(chǔ)編碼格式中的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立即數(shù)指令 | 64bit立即數(shù)指令 |
BPF_ABS | 0x20 | 經(jīng)典BPF數(shù)據(jù)包訪問(直接) | 經(jīng)典BPF數(shù)據(jù)包訪問指令 |
BPF_IND | 0x40 | 經(jīng)典BPF數(shù)據(jù)包訪問(間接) | 經(jīng)典BPF數(shù)據(jù)包訪問指令 |
BPF_MEM | 0x60 | 標準load和store操作 | 標準load和store指令 |
BPF_ATOMIC | 0xc0 | 原子操作 | 原子操作 |
size含義:
size | value | description |
---|---|---|
BPF_W | 0x00 | 字長(4字節(jié)) |
BPF_H | 0x08 | 半字長(2字節(jié)) |
BPF_B | 0x010 | 字節(jié)(1字節(jié)) |
BPF_DW | 0x18 | 雙字長(8字節(jié)) |
1.4.1 標準load和store指令
BPF_MEM代表了標準load和store指令,這些指令用于寄存器和內(nèi)存之間傳遞數(shù)據(jù)。
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 原子操作
原子操作,指的的是對內(nèi)存的操作,不會被其他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)指向內(nèi)存的值 |
BPF_CMPXCHG | 0xf0|BPF_FETCH | 原子CAS.if (*(uXX*)(dst_reg + off16) == R0) { *(uXX*)(dst_reg + off16) = (src_reg) };無論是否交換成功,R0都會保存(dst_reg + off16)指向內(nèi)存的被修改前的原始值。如果是32bit操作數(shù),那么用會0補齊高位后再保存到R0。 |
BPF_FETCH:
imm32 | value | description |
---|---|---|
BPF_FETCH | 0x01 | 代表需要返回舊值 |
對于簡單原子指令是可選的,如果設(shè)置了,src_reg將保存(dst_reg + off16)指向的內(nèi)存中被修改前的原始值。
對于復雜原子指令是必選的。
1.4.3 64bit立即數(shù)指令
mode為BPF_IMM的指令,用于eBPF寬指令,有額外的一個imm64值。
目前只有一條指令:
BPF_LD | BPF_DW | BPF_IMM,含義為:
dst_reg = imm64
1.4.4 經(jīng)典BPF數(shù)據(jù)包訪問指令
eBPF之前為了兼容經(jīng)典BPF,引入了一些訪問數(shù)據(jù)包的指令?,F(xiàn)在已經(jīng)廢棄不再使用。
審核編輯:劉清
-
寄存器
+關(guān)注
關(guān)注
31文章
5343瀏覽量
120447 -
JMP
+關(guān)注
關(guān)注
1文章
17瀏覽量
12609 -
ALU
+關(guān)注
關(guān)注
0文章
33瀏覽量
13105 -
BPF
+關(guān)注
關(guān)注
0文章
25瀏覽量
4007
原文標題:eBPF指令集規(guī)范v1.0
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論