<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          eBPF 概述:第 2 部分:機(jī)器和字節(jié)碼

          共 4397字,需瀏覽 9分鐘

           ·

          2021-11-27 10:22

          1. 前言

          我們?cè)诘?1 篇文章中介紹了 eBPF 虛擬機(jī),包括其有意的設(shè)計(jì)限制以及如何從用戶空間進(jìn)程中進(jìn)行交互。如果你還沒有讀過這篇文章,建議你在繼續(xù)之前讀一下,因?yàn)闆]有適當(dāng)?shù)慕榻B,直接開始接觸機(jī)器和字節(jié)碼的細(xì)節(jié)是比較困難的。如果有疑問,請(qǐng)看第 1 部分開頭的流程圖。

          本系列的第 2 部分對(duì)第 1 部分中研究的 eBPF 虛擬機(jī)和程序進(jìn)行了更深入的探討。掌握這些低層次的知識(shí)并不是強(qiáng)制性的,但可以為本系列的其他部分打下非常有用的基礎(chǔ),我們將在這些機(jī)制的基礎(chǔ)上研究更高層次的工具。

          2. 虛擬機(jī)

          eBPF 是一個(gè) RISC 寄存器機(jī),共有?11 個(gè) 64 位寄存器,一個(gè)程序計(jì)數(shù)器和 512 字節(jié)的固定大小的棧。9 個(gè)寄存器是通用讀寫的,1 個(gè)是只讀棧指針,程序計(jì)數(shù)器是隱式的,也就是說,我們只能跳轉(zhuǎn)到它的某個(gè)偏移量。VM 寄存器總是 64 位寬(即使在 32 位 ARM 處理器內(nèi)核中運(yùn)行?。?,如果最重要的 32 位被清零,則支持 32 位子寄存器尋址 - 這在第 4 部分交叉編譯和在嵌入式設(shè)備上運(yùn)行 eBPF 程序時(shí)非常有用。

          這些寄存器是:

          r0:存儲(chǔ)返回值,包括函數(shù)調(diào)用和當(dāng)前程序退出代碼
          r1-r5:作為函數(shù)調(diào)用參數(shù)使用,在程序啟動(dòng)時(shí),r1 包含 "上下文" 參數(shù)指針
          r6-r9:這些在內(nèi)核函數(shù)調(diào)用之間被保留下來
          r10:每個(gè) eBPF 程序 512 字節(jié)棧的只讀指針

          在加載時(shí)提供的 eBPF?程序類型決定了哪些內(nèi)核函數(shù)的子集可以被調(diào)用,以及在程序啟動(dòng)時(shí)通過 r1 提供的"上下文"參數(shù)。存儲(chǔ)在 r0 中的程序退出值的含義也由程序類型決定。

          每個(gè)函數(shù)調(diào)用在寄存器 r1-r5 中最多可以有 5 個(gè)參數(shù);這適用于 ebpf 到 ebpf 的調(diào)用和內(nèi)核函數(shù)調(diào)用。寄存器 r1-r5 只能存儲(chǔ)數(shù)字或指向棧的指針(作為函數(shù)的參數(shù)),不能直接指向任意的內(nèi)存。所有的內(nèi)存訪問必須在 eBPF 程序中使用之前首先將數(shù)據(jù)加載到 eBPF 棧。這一限制有助于 eBPF 驗(yàn)證器,它簡(jiǎn)化了內(nèi)存模型,使其更容易進(jìn)行內(nèi)核檢查。

          BPF 可訪問的內(nèi)核 “輔助”(helper) 函數(shù)是由內(nèi)核通過類似于定義 syscalls 的 API 定義的(不能通過模塊擴(kuò)展),定義使用?BPF_CALL_*?宏。bpf.h?試圖為所有 BPF 可訪問的內(nèi)核輔助函數(shù)提供參考。例如,bpf_trace_printk?的定義使用了 BPF_CALL_5 和 5 對(duì)類型 / 參數(shù)名稱。定義參數(shù)數(shù)據(jù)類型是非常重要的,因?yàn)樵诿看?eBPF 程序加載時(shí),eBPF 驗(yàn)證器會(huì)確保寄存器的數(shù)據(jù)類型與被調(diào)用者的參數(shù)類型相符。

          eBPF 指令也是固定大小的 64 位編碼,目前大約有 100 條指令,被分組為?8 類。該虛擬機(jī)支持從通用內(nèi)存(map、棧、如數(shù)據(jù)包緩沖區(qū)等的 “上下文”,)進(jìn)行 1-8 字節(jié)的加載/存儲(chǔ),前/后(非)條件跳轉(zhuǎn)、算術(shù)/邏輯操作和函數(shù)調(diào)用。操作碼格式格式深入研究的文檔,請(qǐng)參考 Cilium 項(xiàng)目指令集文檔。IOVisor 項(xiàng)目也維護(hù)了一個(gè)有用的指令規(guī)格。

          在本系列第 1 部分研究的例子中,我們使用了部分有用的內(nèi)核宏,使用以下結(jié)構(gòu)創(chuàng)建了一個(gè) eBPF 字節(jié)碼指令數(shù)組(所有指令都是這樣編碼的):

          struct?bpf_insn?{
          ?__u8?code;??/*?opcode?*/
          ?__u8?dst_reg:4;?/*?dest?register?*/
          ?__u8?src_reg:4;?/*?source?register?*/
          ?__s16?off;??/*?signed?offset?*/
          ?__s32?imm;??/*?signed?immediate?constant?*/
          };

          /*
          msb????????????????????????????????????????????????????????lsb
          +------------------------+----------------+----+----+--------+
          |immediate???????????????|offset??????????|src?|dst?|opcode??|
          +------------------------+----------------+----+----+--------+
          */

          讓我們看看?BPF_JMP_IMM?指令,它編碼了一個(gè)針對(duì)立即值的條件跳轉(zhuǎn)。下面的宏注釋對(duì)指令的邏輯應(yīng)該是不言自明的。操作碼編碼了指令類別 BPF_JMP,操作(通過 BPF_OP 位域以確保正確)和一個(gè)標(biāo)志 BPF_K,表示它是對(duì)直接/常量值的操作。

          #define?BPF_OP(code)????((code)?&?0xf0)
          #define?BPF_K??0x00

          /*?Conditional?jumps?against?immediates,?if?(dst_reg?'op'?imm32)?goto?pc?+?off16?*/

          #define?BPF_JMP_IMM(OP,?DST,?IMM,?OFF)????\
          ?((struct?bpf_insn)?{?????\
          ??.code??=?BPF_JMP?|?BPF_OP(OP)?|?BPF_K,??\
          ??.dst_reg?=?DST,?????\
          ??.src_reg?=?0,?????\
          ??.off???=?OFF,?????\
          ??.imm???=?IMM?})

          如果我們?nèi)ビ?jì)算該指令的值,或者拆解一個(gè)包含 BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2) 的 eBPF 字節(jié)碼,我們會(huì)發(fā)現(xiàn)它是 0x020015。這個(gè)特定的字節(jié)碼非常頻繁地被用來測(cè)試存儲(chǔ)在 r0 中的函數(shù)調(diào)用的返回值;如果 r0 == 0,它就會(huì)跳過接下來的 2 條指令。

          3. 重新認(rèn)識(shí)字節(jié)碼

          現(xiàn)在我們已經(jīng)有了必要的知識(shí)來完全理解本系列第 1 部分中 eBPF 例子中使用的字節(jié)碼,現(xiàn)在我們將一步一步地進(jìn)行詳解。記住,sock_example.c?是一個(gè)簡(jiǎn)單的用戶空間程序,使用 eBPF 來統(tǒng)計(jì)回環(huán)接口上收到多少個(gè) TCP、UDP 和 ICMP 協(xié)議包。

          在更高層次上,代碼所做的是從接收到的數(shù)據(jù)包中讀取協(xié)議號(hào),然后把它推到 eBPF 棧中,作為 map_lookup_elem 調(diào)用的索引,從而得到各自協(xié)議的數(shù)據(jù)包計(jì)數(shù)。map_lookup_elem 函數(shù)在 r0 接收一個(gè)索引(或鍵)指針,在 r1 接收一個(gè) map 文件描述符。如果查找調(diào)用成功,r0 將包含一個(gè)指向存儲(chǔ)在協(xié)議索引的 map 值的指針。然后我們?cè)邮降卦黾?map 值并退出。

          BPF_MOV64_REG(BPF_REG_6,?BPF_REG_1),

          當(dāng)一個(gè) eBPF 程序啟動(dòng)時(shí),r1 中的地址指向 context 上下文(當(dāng)前情況下為數(shù)據(jù)包緩沖區(qū))。r1 將在函數(shù)調(diào)用時(shí)用于參數(shù),所以我們也將其存儲(chǔ)在 r6 中作為備份。

          BPF_LD_ABS(BPF_B,?ETH_HLEN?+?offsetof(struct?iphdr,?protocol)?/*?R0?=?ip->proto?*/),

          這條指令從 context 上下文緩沖區(qū)的偏移量向 r0 加載一個(gè)字節(jié)(BPF_B),當(dāng)前情況下是網(wǎng)絡(luò)數(shù)據(jù)包緩沖區(qū),所以我們從一個(gè)?iphdr 結(jié)構(gòu)?中提供協(xié)議字節(jié)的偏移量,以加載到 r0。

          BPF_STX_MEM(BPF_W,?BPF_REG_10,?BPF_REG_0,?-4),?/*?*(u32?*)(fp?-?4)?=?r0?*/

          將包含先前讀取的協(xié)議的字(BPF_W)加載到棧上(由 r10 指出,從偏移量 -4 字節(jié)開始)。

          BPF_MOV64_REG(BPF_REG_2,?BPF_REG_10),
          BPF_ALU64_IMM(BPF_ADD,?BPF_REG_2,?-4),?/*?r2?=?fp?-?4?*/

          將棧地址指針移至 r2 并減去 4,所以現(xiàn)在 r2 指向協(xié)議值,作為下一個(gè) map 鍵查找的參數(shù)。

          BPF_LD_MAP_FD(BPF_REG_1,?map_fd),

          將本地進(jìn)程中的文件描述符引用包含協(xié)議包計(jì)數(shù)的 map 加載到 r1。

          BPF_RAW_INSN(BPF_JMP?|?BPF_CALL,?0,?0,?0,?BPF_FUNC_map_lookup_elem),

          執(zhí)行 map 查找調(diào)用,將棧中由 r2 指向的協(xié)議值作為 key。結(jié)果存儲(chǔ)在 r0 中:一個(gè)指向由 key 索引的值的指針地址。

          BPF_JMP_IMM(BPF_JEQ,?BPF_REG_0,?0,?2),

          還記得?0x020015?嗎?這和第一節(jié)的字節(jié)碼是一樣的。如果 map 查找沒有成功,r0 == 0,所以我們跳過下面兩條指令。

          BPF_MOV64_IMM(BPF_REG_1,?1),?/*?r1?=?1?*/
          BPF_RAW_INSN(BPF_STX?|?BPF_XADD?|?BPF_DW,?BPF_REG_0,?BPF_REG_1,?0,?0),?/*?xadd?r0?+=?r1?*/

          遞增 r0 所指向的地址的 map 值。

          BPF_MOV64_IMM(BPF_REG_0,?0),?/*?r0?=?0?*/
          BPF_EXIT_INSN(),

          將 eBPF 的 retcode 設(shè)置為 0 并退出。

          盡管這個(gè) sock_example 邏輯是非常簡(jiǎn)單(它只是在一個(gè)映射中增加一些數(shù)字),但在原始字節(jié)碼中實(shí)現(xiàn)或理解它也是很難做到的。更加復(fù)雜的任務(wù)在像這樣的匯編程序中完成會(huì)變得非常困難。展望未來,我們將準(zhǔn)備使用更高級(jí)別的語言和工具來實(shí)現(xiàn)更強(qiáng)大的 eBPF 用例,而不費(fèi)吹灰之力。

          4. 總結(jié)

          在這一部分中,我們仔細(xì)觀察了 eBPF 虛擬機(jī)的寄存器和指令集,了解了 eBPF 可訪問的內(nèi)核函數(shù)是如何從字節(jié)碼中調(diào)用的,以及它們是如何被核心內(nèi)核通過類似 syscall 的特殊目的 API 定義的。我們也完全理解了第 1 部分例子中使用的字節(jié)碼。還有一些未探索的領(lǐng)域,如創(chuàng)建多個(gè) eBPF 程序函數(shù)或鏈?zhǔn)?eBPF 程序以繞過 Linux 發(fā)行版的 4096 條指令限制。也許我們會(huì)在以后的文章中探討這些。

          現(xiàn)在,主要的問題是編寫原始字節(jié)碼是很困難的,這非常像編寫匯編代碼,而且編寫效果不高。在第 3 部分中,我們將開始研究使用高級(jí)語言編譯成 eBPF 字節(jié)碼,到此為止我們已經(jīng)了解了虛擬機(jī)工作的底層基礎(chǔ)知識(shí)。


          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人日本黄色 | 日本A片免费观看 | 麻豆91福利在线观看 | 久久综合国产视频 | 丰满人妻一区二区三区色按摩 |