- 深入理解eBPF與可觀測(cè)性
- 毛文安 鄭昱笙 程書(shū)意 廖肇燕
- 2357字
- 2025-06-09 17:11:31
2.1.2 eBPF指令集
cBPF使用32位的經(jīng)典BPF虛擬機(jī),包含了限定的指令集,而eBPF則使用64位的eBPF虛擬機(jī),擁有更多的寄存器和指令,支持更豐富的操作和功能。BPF(默認(rèn)指eBPF非cBPF)程序指令都是64位,使用了11個(gè)64位寄存器和一個(gè)程序計(jì)數(shù)器,以及一個(gè)大小為512字節(jié)的BPF棧。
1.寄存器和調(diào)用規(guī)約
eBPF有10個(gè)通用寄存器和一個(gè)只讀的fp(frame pointer,幀指針)寄存器,它們都是64位。eBPF調(diào)用規(guī)約如下。
1)R0:保存函數(shù)調(diào)用的返回值,以及eBPF程序退出值。
2)R1~R5:函數(shù)調(diào)用入?yún)ⅰ?/p>
3)R6~R9:調(diào)用者保存寄存器。
4)R10:只讀的,棧幀寄存器。
2.指令編碼
eBPF有兩類(lèi)指令編碼:基礎(chǔ)指令編碼和寬指令編碼。
1)基礎(chǔ)指令編碼:?jiǎn)螚l指令長(zhǎng)度為64位。指令構(gòu)成如表2-3所示。
表2-3 eBPF基礎(chǔ)指令編碼

說(shuō)明:
?操作碼:指令的具體操作,如BPF_ADD、BPF_LD等。
?目的寄存器:R0~R9中的一個(gè)。
?源寄存器:R0~R10中的一個(gè)。
?偏移:16位,主要用于進(jìn)行指針類(lèi)型的數(shù)學(xué)運(yùn)算,可記為off16。
?立即數(shù):32位有符號(hào)的立即數(shù),可記為imm32。
Linux內(nèi)核使用struct bpf_insn結(jié)構(gòu)體表示eBPF的指令格式,struct bpf_insn結(jié)構(gòu)體的具體定義如下:

每條指令可能只用了一部分,并非全部。接下來(lái)將著重介紹一下操作碼的格式,如表2-4所示。
表2-4 操作碼的格式

操作碼的每個(gè)字段說(shuō)明如下:
?編碼:細(xì)分的操作碼,比如運(yùn)算指令BPF_ALU下面有相加(BPF_ADD)、相減(BPF_SUB)等細(xì)分指令。
?標(biāo)識(shí)位:包含BPF_K和BPF_X。BPF_K表示使用32位的立即數(shù)作為源操作數(shù);BPF_X表示使用源寄存器作為源操作數(shù)。
?指令類(lèi)型:包含三大類(lèi)指令,加載與存儲(chǔ)指令、運(yùn)算指令、跳轉(zhuǎn)指令。如表2-5所示。
表2-5 cBPF和eBPF的指令類(lèi)型與值

說(shuō)明:
eBPF把BPF_RET和BPF_MISC指令去掉了,換成了BPF_JMP32和BPF_ALU64,以提供更大范圍的跳轉(zhuǎn)和64位場(chǎng)景下的運(yùn)算操作。
?BPF_LDX和BPF_LD:兩個(gè)都用于加載操作,從而將數(shù)據(jù)從存儲(chǔ)器加載到寄存器中。BPF_LDX表示從內(nèi)存中加載數(shù)據(jù)到dst_reg;BPF_LD表示從imm64中加載數(shù)據(jù)到寄存器。
?BPF_ST和BPF_STX:兩個(gè)都用于存儲(chǔ)操作,從而將數(shù)據(jù)從寄存器存儲(chǔ)到存儲(chǔ)器中。BPF_ST表示把src_reg寄存器數(shù)據(jù)保存到內(nèi)存中;BPF_STX表示把imm32數(shù)據(jù)保存到內(nèi)存中。
?BPF_ALU和BPF_ALU64:分別是32位和64位下的ALU運(yùn)算操作。
?BPF_JMP和BPF_JMP32:跳轉(zhuǎn)指令。JMP32的跳轉(zhuǎn)范圍是0~32位(一個(gè)字)。
2)寬指令編碼:由基礎(chǔ)指令+64位立即數(shù)組成,寬指令是在基礎(chǔ)指令后增加了一個(gè)64位的立即數(shù)(imm64),即指令長(zhǎng)度為128位。寬指令編碼如表2-6所示。
64位立即數(shù)(imm64)的構(gòu)成方式:(imm32<<32)|imm32。其中,imm32為基礎(chǔ)指令中的立即數(shù)。
表2-6 寬指令編碼

3.加載指令
加載指令共分為4種類(lèi)型,分別是加載內(nèi)存數(shù)據(jù)指令、加載64位立即數(shù)指令、網(wǎng)絡(luò)報(bào)文訪問(wèn)指令和間接訪問(wèn)指令。
?加載內(nèi)存數(shù)據(jù)指令:一般形式是dst_reg=*(uint*)(src_reg+off16),對(duì)應(yīng)的宏定義如下所示:

?加載64位立即數(shù)指令:只能用于寬指令,從imm64中加載數(shù)據(jù)到寄存器,一般形式是dst_reg=imm64,對(duì)應(yīng)的宏定義如下所示:

?網(wǎng)絡(luò)報(bào)文訪問(wèn)指令:一般形式是R0=*(uint*)(skb->data+imm32),對(duì)應(yīng)的宏定義如下所示:


?間接訪問(wèn)指令:一般形式是R0=*(uint*)(skb->data+src_reg+imm32),對(duì)應(yīng)的宏定義如下所示:

4.存儲(chǔ)指令
存儲(chǔ)指令共分為3種類(lèi)型:寄存器數(shù)據(jù)寫(xiě)回內(nèi)存指令、32位立即數(shù)寫(xiě)回內(nèi)存指令和原子操作指令。
?寄存器數(shù)據(jù)寫(xiě)回內(nèi)存指令:一般形式是*(uint*)(dst_reg+off16)=src_reg,對(duì)應(yīng)的宏定義如下所示:

?32位立即數(shù)寫(xiě)回內(nèi)存指令:一般形式是*(uint *)(dst_reg+off16)=imm32,對(duì)應(yīng)的宏定義如下所示:

?原子操作指令:原子操作通常在需要同步訪問(wèn)或修改共享數(shù)據(jù)的并發(fā)編程中使用。在eBPF中,原子操作指令可以用來(lái)安全地更新eBPF程序共享的map值或其他數(shù)據(jù)結(jié)構(gòu),而無(wú)須擔(dān)心多個(gè)CPU核心或線程之間的競(jìng)爭(zhēng)條件。原子操作指令對(duì)編寫(xiě)多線程安全的eBPF程序至關(guān)重要,尤其是在網(wǎng)絡(luò)數(shù)據(jù)包處理或性能監(jiān)控等需要進(jìn)行高并發(fā)處理的場(chǎng)景中。指令形式類(lèi)似于寄存器數(shù)據(jù)寫(xiě)回內(nèi)存指令,對(duì)應(yīng)的宏定義如下所示:

5.邏輯運(yùn)算指令
邏輯運(yùn)算指令共分為6種類(lèi)型:寄存器運(yùn)算指令、立即數(shù)運(yùn)算指令、大小端轉(zhuǎn)換指令、寄存器mov指令、立即數(shù)mov指令和擴(kuò)展mov指令。
1)寄存器運(yùn)算指令:一般形式是dst_reg+=src_reg,對(duì)應(yīng)的宏定義如下所示:

2)立即數(shù)運(yùn)算指令:一般形式是dst_reg+=imm32,對(duì)應(yīng)的宏定義如下所示:


3)大小端轉(zhuǎn)換指令:進(jìn)行大小端轉(zhuǎn)換,比如將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換成主機(jī)字節(jié)序。

4)寄存器mov指令:一般形式是dst_reg=src_reg,對(duì)應(yīng)的宏定義如下所示:

5)立即數(shù)mov指令:一般形式是dst_reg=imm32,對(duì)應(yīng)的宏定義如下所示:

6)擴(kuò)展mov指令:特殊形式的mov32指令,該指令專(zhuān)門(mén)用于對(duì)目標(biāo)寄存器進(jìn)行顯式的零擴(kuò)展操作。零擴(kuò)展是指將一個(gè)較小的帶符號(hào)或者無(wú)符號(hào)數(shù)值擴(kuò)展為一個(gè)較大的無(wú)符號(hào)數(shù)值,并用0填充新增的位。在編寫(xiě)eBPF程序時(shí),有時(shí)只用到寄存器的部分位(比如只用到了低32位),對(duì)于某些操作,我們需要確保寄存器的高位處于清零狀態(tài),以避免不可預(yù)知的錯(cuò)誤發(fā)生。BPF_ZEXT_REG可確保我們?cè)?4位寄存器中操作的是一個(gè)無(wú)符號(hào)的32位數(shù),而不被高位“污染”。對(duì)應(yīng)的宏定義如下所示:

6.跳轉(zhuǎn)指令
跳轉(zhuǎn)指令可分為4種類(lèi)型:條件跳轉(zhuǎn)指令、無(wú)條件跳轉(zhuǎn)指令、函數(shù)調(diào)用指令,以及程序退出指令。
1)條件跳轉(zhuǎn)指令。依據(jù)比對(duì)的操作數(shù)類(lèi)型,條件跳轉(zhuǎn)指令分為兩類(lèi):一類(lèi)是基于寄存器值的條件跳轉(zhuǎn),即BPF_JMP_REG;另一類(lèi)是基于立即數(shù)的條件跳轉(zhuǎn),即BPF_JMP_IMM。BPF_JMP_REG指令的一般形式為if(dst_reg 'op' src_reg)goto pc+off16,其中dst_reg和src_reg是寄存器,op是比較運(yùn)算符,off16為跳轉(zhuǎn)的偏移量。BPF_JMP_IMM指令的形式為if(dst_reg 'op' imm32)goto pc+off16,這里imm32表示一個(gè)立即數(shù)。此外,還有專(zhuān)為處理32位操作數(shù)設(shè)計(jì)的條件跳轉(zhuǎn)指令,即BPF_JMP32_REG和BPF_JMP32_IMM。相關(guān)的宏定義如下所示:


2)無(wú)條件跳轉(zhuǎn)指令。一般的形式是goto pc+off16。此類(lèi)處理一般對(duì)應(yīng)于C語(yǔ)言中的goto語(yǔ)言,或者編譯器隱含生成的跳轉(zhuǎn)語(yǔ)句。相關(guān)的宏定義如下所示:

3)函數(shù)調(diào)用指令。該指令可分為兩大類(lèi):第一類(lèi)是自定義函數(shù)調(diào)用,在傳統(tǒng)的eBPF程序中,所有子函數(shù)都應(yīng)該使用__always_inline屬性聲明,這將指示編譯器對(duì)函數(shù)進(jìn)行內(nèi)聯(lián)處理,而不是生成普通的函數(shù)調(diào)用代碼。第二類(lèi)是輔助函數(shù)調(diào)用,這涉及調(diào)用內(nèi)核提供的輔助函數(shù),它們?yōu)閑BPF程序執(zhí)行特定的操作或訪問(wèn)內(nèi)核數(shù)據(jù)提供了接口。其對(duì)應(yīng)的宏是BPF_EMIT_CALL,其定義如下所示:


4)程序退出指令。該指令一般對(duì)應(yīng)的C語(yǔ)言是return語(yǔ)句。相關(guān)的宏定義如下所示:

- Kali Linux滲透測(cè)試全流程詳解
- Extending Puppet
- 開(kāi)源安全運(yùn)維平臺(tái)OSSIM疑難解析:入門(mén)篇
- Windows Vista融會(huì)貫通
- Linux系統(tǒng)安全基礎(chǔ):二進(jìn)制代碼安全性分析基礎(chǔ)與實(shí)踐
- Learning Bootstrap
- Linux內(nèi)核觀測(cè)技術(shù)BPF
- 嵌入式系統(tǒng)及其應(yīng)用(第三版)
- 新編電腦辦公(Windows 10+ Office 2013版)從入門(mén)到精通
- Vim 8文本處理實(shí)戰(zhàn)
- AWS SysOps Cookbook
- Windows 10從新手到高手
- 每天5分鐘玩轉(zhuǎn)Docker容器技術(shù)
- Windows PE權(quán)威指南
- 用“芯”探核:基于龍芯的Linux內(nèi)核探索解析