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

          LLVM eBPF 匯編編程教程

          共 6018字,需瀏覽 13分鐘

           ·

          2021-12-10 00:39

          1 引言

          1.1 主流開發(fā)方式:從 C 代碼直接生成 eBPF 字節(jié)碼

          eBPF 相比于 cBPF(經(jīng)典 BPF)的優(yōu)勢之一是:Clang/LLVM 為它提供了一個(gè)編譯后端, 能從 C 源碼直接生成 eBPF 字節(jié)碼(bytecode)。(寫作本文時(shí),GCC 也提供了一個(gè)類似 的后端,但各方面都沒有 Clang/LLVM 完善,因此后者仍然是生成 eBPF 字節(jié)碼 的最佳參考工具)。

          將 C 代碼編譯成 eBPF 目標(biāo)文件非常有用,因?yàn)?直接用字節(jié)碼編寫高級程序是非常耗時(shí)的。此外,截至本文寫作時(shí), 還無法直接編寫字節(jié)碼程序來使用 CO-RE 等復(fù)雜特性。

          因此,Clang 和 LLVM 仍然是 eBPF 工作流不可或缺的部分。

          1.2 特殊場景需求:eBPF 匯編編程更合適

          但是,C 方式不適用于某些特殊的場景,例如:

          1. 只是想測試特定的 eBPF 指令流
          2. 對程序的某個(gè)特定部分進(jìn)行深度調(diào)優(yōu)

          在這些情況下,就需要直接編寫或修改 eBFP 匯編程序。

          1.3 幾種 eBPF 匯編編程方式

          1. 直接編寫 eBPF 字節(jié)碼程序。也就是編寫可直接加載運(yùn)行的 二進(jìn)制 eBPF 程序,

            • 這肯定是可行的,但過程非常冗長無聊,對開發(fā)者極其不友好。
            • 此外,為保證與 tc 等工具的兼容,還要將寫好的程序轉(zhuǎn)換成目標(biāo)文件(object file),因此工作量又多了一些。
          2. 直接用 eBPF 匯編語言編寫,然后用專門的匯編器 (例如 ebpf_asm)將其匯編(assemble)成字節(jié)碼。

            • 相比字節(jié)碼(二進(jìn)制),匯編語言(文本)至少可讀性還是好很多的。
          3. 用 LLVM 將 C 編譯成 eBPF 匯編,然后手動修改生成的匯編程序, 最后再將其匯編(assemble)成字節(jié)碼放到對象文件。

          4. 在 C 中插入內(nèi)聯(lián)匯編,然后統(tǒng)一用 clang/llvm 編譯。

          以上幾種方式 Clang/LLVM 都支持!先用可讀性比較好的方式寫, 然后再將其匯編(assembling)成另字節(jié)碼程序。此外,甚至能 dump 對象文件中包含的程序。

          本文將會展示第三種和第四種方式,第二種可以認(rèn)為是第三種的更加徹底版,開發(fā)的流程 、步驟等已經(jīng)包括在第三種了。

          2 Clang/LLVM 編譯 eBPF 基礎(chǔ)

          在開始匯編編程之前,先來熟悉一下 clang/llvm 將 C 程序編譯成 eBPF 程序的過程。

          2.1 將 C 程序編譯成 BPF 目標(biāo)文件

          下面是個(gè) eBPF 程序:沒做任何事情,直接返回零,

          //?bpf.c
          int?func()?{
          ????return?0;
          }

          如下命令可以將其編譯成對象文件(目標(biāo)文件):

          #?注意?target?類型指定為?`bpf`
          $?clang?-target?bpf?-Wall?-O2?-c?bpf.c?-o?bpf.o

          某些復(fù)雜的程序可能需要用下面的命令來編譯:

          $?clang?-O2?-emit-llvm?-c?bpf.c?-o?-?|?\
          ?llc?-march=bpf?-mcpu=probe?-filetype=obj?-o?bpf.o

          以上命令會將 C 源碼編譯成字節(jié)碼,然后生成一個(gè) ELF 格式的目標(biāo)文件。

          1.2 查看 ELF 文件中的 eBPF 字節(jié)碼

          默認(rèn)情況下,代碼位于 ELF 的 .text 區(qū)域(section):

          $?readelf?-x?.text?bpf.o
          Hex?dump?of?section?'.text':
          ??0x00000000?b7000000?00000000?95000000?00000000?................

          這就是編譯生成的字節(jié)碼!

          以上字節(jié)碼包含了兩條 eBPF 指令:

          b7 0 0 0000 00000000    # r0 = 0
          95 0 0 0000 00000000 # exit and return r0

          如果對 eBPF 匯編語法不熟悉,可參考:

          1. 簡潔文檔: (https://github.com/iovisor/bpf-docs/blob/master/eBPF.md)
          2. 詳細(xì)文檔: (https://www.kernel.org/doc/Documentation/networking/filter.txt)

          有了以上基礎(chǔ),接下來看如何開發(fā) eBPF 匯編程序。

          3 方式一:C 生成 eBPF 匯編 + 手工修改匯編

          本節(jié)需要 Clang/LLVM 6.0+ 版本(clang -v)。

          譯文基于 10.0,結(jié)果與原文略有差異。

          C 源碼:

          //?bpf.c
          int?func()?{
          ?return?0;
          }

          3.1 將 C 編譯成 eBPF 匯編(clang)

          其實(shí)前面已經(jīng)看到了,與將普通 C 程序編譯成匯編類似,只是這里指定 target 類型是 bpf (bpf target 與默認(rèn) target 的不同,見 Cilium 文檔 BPF 和 XDP 參考指南):

          Cilium:BPF 和 XDP 參考指南:

          http://docs.cilium.io/en/latest/bpf/#llvm

          $?clang?-target?bpf?-S?-o?bpf.s?bpf.c

          查看生成的匯編代碼:

          $?cat?bpf.s
          ????.text
          ????.file???"bpf.c"
          ????.globl??func?????????????????#?--?Begin?function?func
          ????.p2align????????3
          ????.type???func,@function
          func:???????????????????????????#?@func
          #?%bb.0:
          ????r0?=?0
          ????exit
          .Lfunc_end0:
          ????.size???func,?.Lfunc_end0-func
          ???????????????????????????????#?--?End?function
          ????.addrsig

          接下來就可以修改這段匯編代碼了。

          3.2 手工修改匯編程序

          因?yàn)閰R編程序是文本文件,因此編輯起來很容易。作為練手,我們在程序最后加上一行匯編指令 r0 = 3:

          $?cat?bpf.s
          ????.text
          ????.file???"bpf.c"
          ????.globl??func????????????????????#?--?Begin?function?func
          ????.p2align????????3
          ????.type???func,@function
          func:???????????????????????????????#?@func
          #?%bb.0:
          ????r0?=?0
          ????exit
          ????r0?=?3??????????????????????????#?--?這行是我們手動加的
          .Lfunc_end0:
          ????.size???func,?.Lfunc_end0-func
          ????????????????????????????????????#?--?End?function
          ????.addrsig

          這行放在了 exit 之后,因此實(shí)際上沒任何作用。

          3.3 將匯編程序 assemble 成 ELF 對象文件(llvm-mc)

          接下來將 bpf.s 匯編(assemble)成包含字節(jié)碼的 ELF 對象文件。這 里需要用到 LLVM 自帶的與機(jī)器碼(machine code,mc)打交道的工具 llvm-mc:

          $?llvm-mc?-triple?bpf?-filetype=obj?-o?bpf.o?bpf.s

          bpf.o 就是生成的 ELF 文件!

          3.4 查看對象文件中的 eBPF 字節(jié)碼(readelf)

          查看 bpf.o 中的字節(jié)碼:

          $?readelf?-x?.text?bpf.o

          Hex?dump?of?section?'.text':
          ??0x00000000?b7000000?00000000?95000000?00000000?................
          ??0x00000010?b7000000?03000000???????????????????........

          看到和之前相比,

          • 第一行(包含前兩條指令)一樣,
          • 第二行是新多出來的(對應(yīng)的正是我們新加的一行匯編指令),作用:將常量 3 load 到寄存器 r0 中。

          至此,我們已經(jīng)成功地修改了指令流。接下來就可以用 bpftool 之 類的工具將這個(gè)程序加載到內(nèi)核,任務(wù)完成!

          3.5 以更加人類可讀的方式查看 eBPF 字節(jié)碼(llvm-objdump -d)

          LLVM 還能以人類可讀的方式 dump eBPF 對象文件中的指令,這里就要用到 llvm-objdump:

          #?-d???????????:?alias?for?--disassemble
          #?--disassemble:?display?assembler?mnemonics?for?the?machine?instructions
          $?llvm-objdump?-d?bpf.o
          bpf.o:??file?format?ELF64-BPF

          Disassembly?of?section?.text:

          0000000000000000?func:
          ???????0:???????b7?00?00?00?00?00?00?00?r0?=?0
          ???????1:???????95?00?00?00?00?00?00?00?exit
          ???????2:???????b7?00?00?00?03?00?00?00?r0?=?3

          最后一列顯示了對應(yīng)的 LLVM 使用的匯編指令(也是前面我們手工編輯時(shí)使用的 eBPF 指令)。

          3.6 編譯時(shí)嵌入調(diào)試符號或 C 源碼(clang -g + llvm-objdump -S)

          除了字節(jié)碼和匯編指令,LLVM 還能將調(diào)試信息(debug symbols)嵌入到對象文件, 更具體說就是能在字節(jié)碼旁邊同時(shí)顯示對應(yīng)的 C 源碼,對調(diào)試非常有用,也是 觀察 C 指令如何映射到 eBPF 指令的好機(jī)會。

          在 clang 編譯時(shí)加上 -g 參數(shù):

          #?-g:?generate?debug?information.
          $?clang?-target?bpf?-g?-S?-o?bpf.s?bpf.c
          $?llvm-mc?-triple?bpf?-filetype=obj?-o?bpf.o?bpf.s
          #?-S??????:?alias?for?--source
          #?--source:?display?source?inlined?with?disassembly.?Implies?disassemble?object
          $?llvm-objdump?-S?bpf.o
          Disassembly?of?section?.text:

          0000000000000000?func:
          ;?int?func()?{
          ???????0:???????b7?00?00?00?00?00?00?00?r0?=?0
          ;?????return?0;
          ???????1:???????95?00?00?00?00?00?00?00?exit

          注意這里用的是 -S(顯示源碼),不是 -d(反匯編)。

          4 方式二:內(nèi)聯(lián)匯編(inline assembly)

          接下來看另一種生成和編譯 eBPF 匯編的方式:直接在 C 程序中嵌入 eBPF 匯編。

          4.1 C 內(nèi)聯(lián)匯編示例

          下面是個(gè)非常簡單的例子,受 Cilium 文檔 BPF 和 XDP 參考指南的啟發(fā):

          //?inline_asm.c
          int?func()?{
          ????unsigned?long?long?foobar?=?2,?r3?=?3,?*foobar_addr?=?&foobar;

          ????asm?volatile("lock?*(u64?*)(%0+0)?+=?%1"?:?//?等價(jià)于:foobar += r3
          ?????????"=r"(foobar_addr)?:
          ?????????"r"(r3),?"0"(foobar_addr))
          ;

          ????return?foobar;
          }

          關(guān)鍵字 asm 用于插入?yún)R編代碼。

          4.2 編譯及查看生成的字節(jié)碼

          $?clang?-target?bpf?-Wall?-O2?-c?inline_asm.c?-o?inline_asm.o

          反匯編:

          $?llvm-objdump?-d?inline_asm.o
          Disassembly?of?section?.text:

          0000000000000000?func:
          ???????0:???????b7?01?00?00?02?00?00?00?r1?=?2
          ???????1:???????7b?1a?f8?ff?00?00?00?00?*(u64?*)(r10?-?8)?=?r1
          ???????2:???????b7?01?00?00?03?00?00?00?r1?=?3
          ???????3:???????bf?a2?00?00?00?00?00?00?r2?=?r10
          ???????4:???????07?02?00?00?f8?ff?ff?ff?r2?+=?-8
          ???????5:???????db?12?00?00?00?00?00?00?lock?*(u64?*)(r2?+?0)?+=?r1
          ???????6:???????79?a0?f8?ff?00?00?00?00?r0?=?*(u64?*)(r10?-?8)
          ???????7:???????95?00?00?00?00?00?00?00?exit

          對應(yīng)到最后一列的匯編,大家應(yīng)該大致能看懂。

          4.3 小結(jié)

          這種方式的好處是:源碼仍然是 C,因此無需像前一種方式那樣必須手動執(zhí)行編譯( compile)和匯編(assemble)兩個(gè)分開的過程。

          5 結(jié)束語

          本文通過兩個(gè)極簡的例子展示了兩種 eBPF 匯編編程方式:

          1. 手動生成并修改一段特定的指令流
          2. 在 C 中插入內(nèi)聯(lián)匯編

          這兩種方式我認(rèn)為都是有用的,比如在 Netronome,我們經(jīng)常用前一種方式做單元測試, 檢查 nfp 驅(qū)動中的 eBPF hw offload 特性。

          LLVM 支持編寫任意的 eBPF 匯編程序(但提醒一下:編譯能通過是一回事,能不能通過校驗(yàn)器是另一回事)。有興趣自己試試吧!


          原文:?https://arthurchiao.art/blog/ebpf-assembly-with-llvm-zh/

          瀏覽 286
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  亚洲午夜无码久久久 | 亚洲综合免费观看 | 国产精品乱码69一区二区三区 | 亚洲AV无码久久精品蜜桃动态图 | 夜夜艹天天艹 |