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

          揭秘 BPF map 前生今世

          共 7739字,需瀏覽 16分鐘

           ·

          2022-01-14 22:23

          1. 前言

          眾所周知,map 可用于內(nèi)核 BPF 程序和用戶應(yīng)用程序之間實(shí)現(xiàn)雙向的數(shù)據(jù)交換, 為 BPF 技術(shù)中的重要基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。

          在 BPF 程序中可以通過(guò)聲明 struct bpf_map_def 結(jié)構(gòu)完成創(chuàng)建,這其實(shí)帶給我們一種錯(cuò)覺(jué),感覺(jué)這和普通的 C 語(yǔ)言變量沒(méi)有區(qū)別,然而事實(shí)真的是這樣的嗎?事情遠(yuǎn)沒(méi)有這么簡(jiǎn)單,讀完本文以后相信你會(huì)有更大的驚喜。

          struct?bpf_map_def?SEC("maps")?my_map?=?{
          ?.type?=?BPF_MAP_TYPE_ARRAY,
          ??//?...
          };

          我們知道最終 BPF 程序是需要在內(nèi)核中執(zhí)行,但是 map 數(shù)據(jù)結(jié)構(gòu)是用于用戶空間和內(nèi)核 BPF 程序雙向的數(shù)據(jù)結(jié)構(gòu),那么問(wèn)題來(lái)了:

          • 通過(guò) struct bpf_map_def 定義的變量究竟是如何創(chuàng)建的,是在用戶空間創(chuàng)建還是內(nèi)核中直接創(chuàng)建的?

          • 如何實(shí)現(xiàn)創(chuàng)建后的 map 的結(jié)構(gòu),在用戶空間與內(nèi)核中 BPF 程序關(guān)聯(lián)?你可能注意到在用戶空間中對(duì)于 map 的訪問(wèn)是通過(guò) map 文件句柄 fd ?完成(類(lèi)型為 int),但是在 BPF 程序中是通過(guò) ?struct bpf_map * 結(jié)構(gòu)完成的。

          畢竟數(shù)據(jù)交換跨越了用戶空間和內(nèi)核空間,本文將從深入淺出為各位看官揭開(kāi) map 整個(gè)生命管理的 "大瓜"。

          2. 簡(jiǎn)單的使用樣例

          本樣例來(lái)自于 samples/bpf/sockex1_user.c[1]sockex1_kern.c[2],略有修改和刪除。

          sockex1_user.c[3] 用戶空間程序主要內(nèi)容如下(為方便展示,部分內(nèi)容有刪除和修改):

          int?main(int?argc,?char?**argv)
          {
          ?struct?bpf_object?*obj;
          ?int?map_fd,?prog_fd;
          ?//?...

          ??//?加載?BPF?程序至?bpf_object?對(duì)象中,
          ?bpf_prog_load("sockex_kern.o",?BPF_PROG_TYPE_SOCKET_FILTER,?&obj,?&prog_fd))
          ?
          ??//?獲取?my_map?對(duì)應(yīng)的?map_fd?句柄
          ?map_fd?=?bpf_object__find_map_fd_by_name(obj,?"my_map");?//?==?本次關(guān)注?==

          ??//?通過(guò)?setsockopt?將?BPF?字節(jié)碼加載到內(nèi)核中
          ??sock?=?open_raw_sock("lo");
          ?setsockopt(sock,?SOL_SOCKET,?SO_ATTACH_BPF,?&prog_fd,?sizeof(prog_fd));

          ?popen("ping?-4?-c5?localhost",?"r");?//?產(chǎn)生報(bào)文

          ??//?從?my_map?中讀取?5?次?IPPROTO_TCP?的統(tǒng)計(jì)
          ?for?(i?=?0;?i?5;?i++)?{?
          ??long?long?tcp_cnt;
          ??int?key?=?IPPROTO_TCP;

          ??assert(bpf_map_lookup_elem(map_fd,?&key,?&tcp_cnt)?==?0);?//?==?本次關(guān)注?==
          ??//?...
          ??sleep(1);
          ?}

          ?return?0;
          }

          sockex1_user.c 文件中的 bpf_map_lookup_elem 調(diào)用的函數(shù)原型如下,定義在文件 tools/lib/bpf/bpf.c[4] 中:

          int?bpf_map_lookup_elem(int?fd,?const?void?*key,?void?*value)

          函數(shù)底層通過(guò) sys_bpf(cmd=BPF_MAP_LOOKUP_ELEM,...) 實(shí)現(xiàn),為我們方便 map 操作的用戶空間封裝函數(shù), bpf 系統(tǒng)調(diào)用可參考 man 2 bpf[5]。

          其中 sockex1_kern.c[6] 主要內(nèi)容如下:

          //?map?定義?
          struct?bpf_map_def?SEC("maps")?my_map?=?{
          ?.type?=?BPF_MAP_TYPE_ARRAY,
          ?.key_size?=?sizeof(u32),
          ?.value_size?=?sizeof(long),
          ?.max_entries?=?256,
          };

          //?BPF?程序,獲取到報(bào)文協(xié)議類(lèi)型并進(jìn)行計(jì)數(shù)更新
          SEC("socket1")
          int?bpf_prog1(struct?__sk_buff?*skb)
          {
          ?int?index?=?load_byte(skb,?ETH_HLEN?+?offsetof(struct?iphdr,?protocol));
          ?long?*value;

          ?value?=?bpf_map_lookup_elem(&my_map,?&index);??//?查找索引并更新?map?對(duì)應(yīng)的值,==?本次關(guān)注?==
          ?if?(value)
          ??__sync_fetch_and_add(value,?skb->len);

          ?return?0;
          }
          char?_license[]?SEC("license")?=?"GPL";

          sockex1_kern.c 文件中的 ?bpf_map_lookup_elem ?函數(shù)為內(nèi)核中提供的 BPF 輔助函數(shù),原型聲明如下,詳情可參考 man 7 bpf-helper[7]

          void?*bpf_map_lookup_elem(struct?bpf_map?*map,?const?void?*key)

          用戶空間與內(nèi)核 BPF 輔助函數(shù)參數(shù)對(duì)比

          通過(guò)分析 sockex1_user.c 和 sockex1_kern.c 函數(shù)中的 bpf_map_lookup_elem 使用姿勢(shì),這里我們做個(gè)簡(jiǎn)單對(duì)比:

          //?用戶空間?map?查詢函數(shù)
          int?bpf_map_lookup_elem(int?fd,?const?void?*key,?void?*value)

          //?內(nèi)核中?BPF?輔助函數(shù)?map?查詢函數(shù)
          void?*bpf_map_lookup_elem(struct?bpf_map?*map,?const?void?*key)

          那么如何將 int fdstruct bpf_map *map 共同關(guān)聯(lián)一個(gè)對(duì)象呢?這需要我們通過(guò)分析 BPF 字節(jié)碼來(lái)進(jìn)行解密。

          3. 深入指令分析

          首先我們將 sockex1_kern.c 文件使用 ?llvm/clang 將之編譯成 ELF 的 BPF 字節(jié)碼。對(duì)于生成的 sockex1_kern.o 文件可以用 llvm-objdump 來(lái)查看相對(duì)應(yīng)的文件格式,這里我們僅關(guān)注 map 相關(guān)的部分。

          3.1 查看 BPF 指令

          $?clang?-O2?-target?bpf?-c?sockex1_kern.c??-o?sockex1_kern.o
          $?llvm-objdump?-S?sockex1_kern.o

          0000000000000000?:
          ????//?...
          ????;??value?=?bpf_map_lookup_elem(&my_map,?&index);?#?備注:編譯的機(jī)器啟用了 BTF
          ???????7:?18?01?00?00?00?00?00?00?00?00?00?00?00?00?00?00?r1?=?0?ll
          ???????9:?85?00?00?00?01?00?00?00?call?1
          ???????//?...

          上述結(jié)果展示了 BPF 程序中 socket1 部分的函數(shù) bpf_prog1 的 BPF 指令,但是其中對(duì)于涉及到的變量 my_map 的引用都未有解決。上述的反匯編部分打印了 map_lookup_elem() 函數(shù)調(diào)用涉及的指令:

          • 根據(jù) BPF 程序調(diào)用的約定,寄存器 r1 為函數(shù)調(diào)用的第 1 個(gè)參數(shù),這里即 bpf_map_lookup_elem(&my_map, &index) 調(diào)用中的 my_map 。
          ???????7:?18?01?00?00?00?00?00?00?00?00?00?00?00?00?00?00?r1?=?0?ll??#?64?位直接數(shù)賦值?,?r1?=?0?
          ???????9:?85?00?00?00?01?00?00?00?call?1?????????????????????????????#?調(diào)用?bpf_map_lookup_elem,編號(hào)為?1

          上述 "7:" 行代表了為一條 16 個(gè)字節(jié)的 BPF 指令,表示加載一個(gè) 64 位立即數(shù)。

          這里無(wú)需擔(dān)心相關(guān)的 BPF 指令集,后續(xù)我們會(huì)詳細(xì)展開(kāi)解釋。1 個(gè) BPF 指令由 8 個(gè)字節(jié)組成,格式定義如下:

          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?*/
          };

          通過(guò)上述結(jié)構(gòu)對(duì)應(yīng)拆解一下 ”7:“ 行(其中包含了 2 條 BPF 指令,為 BPF 指令中的特殊指令,運(yùn)行時(shí)會(huì)被解析成 1 條指令執(zhí)行) ,第 1 條 BPF 指令詳細(xì)的信息如下:(這里忽略了 off 字段)

          • opcode 為 0x18,即 BPF_LD | BPF_IMM | BPF_DW。該 opcode 表示要將一個(gè) 64 位的立即數(shù)加載到目標(biāo)寄存器。
          • dst_reg 是 1(4 個(gè) bit 位),代表寄存器 r1。
          • src_reg 是 0(4 個(gè) bit 位),表示立即數(shù)在指令內(nèi)。
          • imm 為 0,因?yàn)?my_map 的值在生成 BPF 字節(jié)碼的時(shí)候還未進(jìn)行創(chuàng)建。

          第 2 條指令主要負(fù)責(zé)保存 imm 的高 32 位。

          3.2 加載器創(chuàng)建 map 對(duì)象

          當(dāng)加載器(loader)在加載 ELF 對(duì)象 sockex1_kern.o 時(shí),其首先會(huì)從 ELF 格式的 maps 區(qū)域獲取到定義的 map 對(duì)象 my_map 及相關(guān)的屬性, 然后通過(guò)調(diào)用 bpf() 系統(tǒng)調(diào)用來(lái)創(chuàng)建 my_map 對(duì)象,如果創(chuàng)建成功,那么 bpf() 系統(tǒng)調(diào)用返回一個(gè)文件描述符 (map fd)。

          同時(shí),加載器也會(huì)對(duì)于基于 map 元信息(比如名稱(chēng) my_map)與通過(guò) bpf() 系統(tǒng)調(diào)用創(chuàng)建 map 后返回的 map fd 建立起對(duì)應(yīng)關(guān)系,此后用戶空間空間程序就可以使用 my_map 作為關(guān)鍵字獲取到其對(duì)應(yīng)的 fd,具體代碼如下:

          map_fd?=?bpf_object__find_map_fd_by_name(obj,?"my_map");

          用戶空間獲取到了 map 對(duì)象的 fd,后續(xù)可用于 map_lookup_elem(map_fd, ...) 函數(shù)進(jìn)行 map 的查詢等操作。

          3.3 第一次變身:map fd 替換

          以上完成了 my_map 對(duì)象的創(chuàng)建,但是在 BPF 字節(jié)碼程序加載到內(nèi)核前,還需要將 map fd 在 BPF 指令集中完成第一次變身,如函數(shù) lib/bpf.c: bpf_apply_relo_map() 的代碼片段所示:

          ????????prog->insns[insn_off].src_reg?=?BPF_PSEUDO_MAP_FD;?//?值在內(nèi)核中定義為?1
          ????????prog->insns[insn_off].imm?=?ctx->map_fds[map_idx];?// ctx->map_fds[map_idx]?即為保存的 map fd 值。

          這里假設(shè)獲取到的 map 文件描述符為 6,那么在加載的 BPF 程序完成 bpf_apply_relo_map 的替換后上述的指令對(duì)比如下:

          ELF 文件中的字節(jié)碼:

          ???????7:?18?01?00?00?00?00?00?00?00?00?00?00?00?00?00?00?r1?=?0?ll??#?64?位直接數(shù)賦值?,?r1?=?0?
          ???????9:?85?00?00?00?01?00?00?00?call?1?????????????????????????????#?調(diào)用?bpf_map_lookup_elem,編號(hào)為?1

          替換 map fd 后的字節(jié)碼:

          ???????7:?18?11?00?00?06?00?00?00?00?00?00?00?00?00?00?00?r1?=?0?ll??#?64?位直接數(shù)賦值?,?r1?=?6?
          ???????9:?85?00?00?00?01?00?00?00?call?1?????????????????????????????#?調(diào)用?bpf_map_lookup_elem,編號(hào)為?1?????????????????????

          3.4 第二次變身:map fd 替換成 map 結(jié)構(gòu)指針

          當(dāng)上述經(jīng)過(guò)第一次變身的 BPF 字節(jié)碼加載到內(nèi)核后,還需要進(jìn)行一次變身,才能真正在內(nèi)核中工作,這次 BPF 驗(yàn)證器(verifier)扛過(guò)大旗。

          驗(yàn)證器將加載器注入到指令中的 map fd 替換成內(nèi)核中的 map 對(duì)象指針。調(diào)用堆棧的情況如下:

          ????sys_bpf()
          ????-->?bpf_prog_load()
          ????????-->?bpf_check()
          ????????????-->?replace_map_fd_with_map_ptr()
          ???????????-->?do_check()
          ????????????????-->?check_ld_imm()
          ????????????????==>?check_func_arg()
          ????????????-->?convert_pseudo_ld_imm64()

          函數(shù) replace_map_fd_with_map_ptr() 通過(guò)以下代碼完成第二次大變身,實(shí)現(xiàn)了內(nèi)核中 BPF 字節(jié)碼的 imm 搖身一變成為 map ptr 地址。

          ????????f?=?fdget(insn[0].imm);??//?從第?1?條指令中的?imm?字段獲取到加載器設(shè)置的?map?fd
          ????????map?=?__bpf_map_get(f);??//?基于?map?fd?獲取到?map?對(duì)象指針
          ????????addr?=?(unsigned?long)map;??
          ????????insn[0].imm?=?(u32)addr;???//?將 map ?對(duì)象指針低 32 位放入第一條指令中的 imm 字段
          ????????insn[1].imm?=?addr?>>?32;??//?將?map??對(duì)象指針高?32?位放入第二條指令中的?imm?字段

          于此同時(shí),函數(shù) convert_pseudo_ld_imm64() 還需要清理加載器設(shè)置的 ?src_reg = BPF_PSEUDO_MAP_FD 操作( prog->insns[insn_off].src_reg = BPF_PSEUDO_MAP_FD;), 用于表明完成了整個(gè)指令的重寫(xiě)工作:

          ????????if?(insn->code?==?(BPF_LD?|?BPF_IMM?|?BPF_DW))
          ????????????????insn->src_reg?=?0;

          如果這里的 my_map 在內(nèi)核中 64 位地址為 0xffff8881384aa200,那么驗(yàn)證器完成第二次變身后的 BPF 字節(jié)碼對(duì)比如下。

          替換 map fd 后的字節(jié)碼:

          ???????7:?18?11?00?00?06?00?00?00?00?00?00?00?00?00?00?00?r1?=?0?ll??#?64?位直接數(shù)賦值?,?r1?=?6?
          ???????9:?85?00?00?00?01?00?00?00?call?1?????????????????????????????#?調(diào)用?bpf_map_lookup_elem,編號(hào)為?1?

          替換為 map 對(duì)象指針后的字節(jié)碼如下:

          ????? 7:18 01 00?00?00 a2 4a 38 00?00?00?00 81 88 ff ff ??????????#?64?位直接數(shù)賦值?,?r1?=?0xffff8881384aa200?
          ????? 9:85 00?00?00 30 86 01 00??????????????????#?調(diào)用?bpf_map_lookup_elem,編號(hào)為?1

          在完成了上述兩次變身后,當(dāng)在內(nèi)核中調(diào)用 map_lookup_elem() 時(shí),第一個(gè)參數(shù) my_map 的值為 0xffff8881384aa200

          從而實(shí)現(xiàn)了從最早的 ELF 中的 0 ,替換成了 map_fd (6),直到最后的 map 對(duì)象 struct bpf_map * (0xffff8881384aa200)。

          提示,內(nèi)核中 bpf_map_lookup_elem 輔助函數(shù)的原型定義為:

          static?void?*(*bpf_map_lookup_elem)(struct?bpf_map?*map,?void?*key)

          4. 整個(gè)流程總結(jié)

          通過(guò)上述 map 訪問(wèn)指令的 2 次大變身,我們可以清晰了解 map 創(chuàng)建、map fd 指令重寫(xiě)和 map ptr 對(duì)象的重寫(xiě),也能夠徹底明白用戶空間 map fd 與內(nèi)核中 map 對(duì)象指針的關(guān)聯(lián)關(guān)系。

          俗話說(shuō)一圖勝千言,這里我們用一張圖進(jìn)行整個(gè)流程的總結(jié):

          原始圖片來(lái)自于這里 [8],略有修改。

          參考

          參考資料

          [1]

          samples/bpf/sockex1_user.c: https://elixir.bootlin.com/linux/v5.13/source/samples/bpf/sockex1_user.c

          [2]

          sockex1_kern.c: https://elixir.bootlin.com/linux/v5.13/source/samples/bpf/sockex1_kern.c

          [3]

          sockex1_user.c: https://elixir.bootlin.com/linux/v5.13/source/samples/bpf/sockex1_user.c

          [4]

          tools/lib/bpf/bpf.c: https://elixir.bootlin.com/linux/v5.13/source/tools/lib/bpf/bpf.c

          [5]

          man 2 bpf: https://man7.org/linux/man-pages/man2/bpf.2.html

          [6]

          sockex1_kern.c: https://elixir.bootlin.com/linux/v5.13/source/samples/bpf/sockex1_kern.c

          [7]

          man 7 bpf-helper: https://man7.org/linux/man-pages/man7/bpf-helpers.7.html

          [8]

          這里 : https://github.com/qmonnet/echo-bpftool/blob/main/slides/loading.svg

          [9]

          Linux bpf map internals: https://mechpen.github.io/posts/2019-08-03-bpf-map/index.html

          [10]

          eCHO episode 11: Exploring bpftool with Quentin Monnet: https://www.youtube.com/watch?v=1EOLh3zzWP4&t=1527s

          [11]

          ebpf: BPF_FUNC_map_lookup_elem calling convention: https://stackoverflow.com/questions/67440821/ebpf-bpf-func-map-lookup-elem-calling-convention

          瀏覽 37
          點(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>
                  老欧性老太色HD大全 | 69精品| 婷婷丁香五月激情网 | 青娱乐户外黄色片 | 狠狠操天天鲁 |