<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 在生產(chǎn)環(huán)境調(diào)試 Go 應(yīng)用

          共 2689字,需瀏覽 6分鐘

           ·

          2020-10-24 06:10

          本文是描述我們?nèi)绾卧谏a(chǎn)中使用 eBPF 調(diào)試應(yīng)用程序的系列文章中的第一篇,無(wú)需重新編譯/重新部署,這篇文章介紹了如何使用 gobpf[1] 和uprobes 為 Go 應(yīng)用程序建立一個(gè)函數(shù)參數(shù)跟蹤器,這種技術(shù)也可以擴(kuò)展到其他編譯語(yǔ)言,如 C++、Rust 等。

          介紹

          通常在調(diào)試應(yīng)用的時(shí)候,我們對(duì)捕獲程序的狀態(tài)比較感興趣,這可以讓我們檢查應(yīng)用程序正在做什么,并確定我們代碼中的錯(cuò)誤所在,觀察狀態(tài)的一個(gè)簡(jiǎn)單方法是使用調(diào)試器來(lái)捕獲函數(shù)參數(shù),對(duì)于 Go 應(yīng)用程序,我們經(jīng)常使用的是 Delve 或 gdb。

          Delve 和 gdb 在開(kāi)發(fā)環(huán)境中調(diào)試效果很好,但在生產(chǎn)中并不經(jīng)常使用,調(diào)試器會(huì)對(duì)程序造成很大的干擾,甚至允許狀態(tài)變化,這可能就會(huì)導(dǎo)致生產(chǎn)環(huán)境的應(yīng)用出現(xiàn)一些意外的故障。

          為了更干凈地捕獲函數(shù)參數(shù),我們將探索使用 Linux 4.x+ 中可用的增強(qiáng)型 BPF(eBPF[2])和更高級(jí)別的 Go 庫(kù) gobpf 的使用。

          什么是 eBPF?

          Extended BPF(eBPF)是 Linux 4.x+ 中的一種內(nèi)核技術(shù),你可以把它看作是一個(gè)輕量級(jí)的沙盒虛擬機(jī),它運(yùn)行在 Linux 內(nèi)核內(nèi)部,可以提供對(duì)內(nèi)核內(nèi)存的驗(yàn)證訪(fǎng)問(wèn)。

          如下所示,eBPF 允許內(nèi)核運(yùn)行 BPF 字節(jié)碼,雖然使用的前端語(yǔ)言可以不同,但通常都是 C 語(yǔ)言的限制子集,通常先用 Clang 將 C 代碼編譯成 BPF 字節(jié)碼,然后對(duì)字節(jié)碼進(jìn)行驗(yàn)證以確保其安全執(zhí)行。這些嚴(yán)格的驗(yàn)證保證了機(jī)器代碼不會(huì)故意或意外地危害 Linux 內(nèi)核,并且保證了 BPF 探針每次被觸發(fā)時(shí)都能在一定數(shù)量的指令中執(zhí)行,這些保證使得 eBPF 能夠用于性能關(guān)鍵型的工作負(fù)載,如數(shù)據(jù)包過(guò)濾、網(wǎng)絡(luò)監(jiān)控等。

          在功能上,eBPF 允許你在一些事件(如定時(shí)器、網(wǎng)絡(luò)事件或函數(shù)調(diào)用)發(fā)生時(shí)運(yùn)行受限的 C 代碼,當(dāng)觸發(fā)一個(gè)函數(shù)調(diào)用時(shí),我們把這些函數(shù)稱(chēng)為 probe,它們可以用來(lái)運(yùn)行在內(nèi)核內(nèi)的函數(shù)調(diào)用上(kprobes),也可以運(yùn)行在用戶(hù)空間程序的函數(shù)調(diào)用上(uprobes)。接下來(lái)我們主要介紹如何使用 uprobes 來(lái)動(dòng)態(tài)跟蹤函數(shù)參數(shù)。

          Uprobes

          Uprobes 允許你通過(guò)插入一個(gè)調(diào)試陷阱指令(x86 上的 int3)來(lái)攔截用戶(hù)空間程序,觸發(fā)軟中斷,這也是調(diào)試器的工作方式。一個(gè) uprobe 的流程基本上與任何其他 BPF 程序相同。編譯和驗(yàn)證過(guò)的 BPF 程序作為 uprobe 的一部分被執(zhí)行,結(jié)果可以寫(xiě)入緩沖區(qū)。

          用于跟蹤的 BPF(來(lái)自Brendan Gregg)

          讓我們看看 uprobes 是如何實(shí)際運(yùn)行的,為了部署 uprobes 和捕獲函數(shù)參數(shù),我們將使用一個(gè)簡(jiǎn)單的演示程序。

          //?computeE?computes?the?approximation?of?e?by?running?a?fixed?number?of?iterations.
          2func?computeE(iterations?int64)?float64?{
          3??res?:=?2.0
          4??fact?:=?1.0
          5
          6??for?i?:=?int64(2);?i?7????fact?*=?float64(i)
          8????res?+=?1?/?fact
          9??}
          10??return?res
          11}
          12
          13func?main()?{
          14??http.HandleFunc("/e",?func(w?http.ResponseWriter,?r?*http.Request)?{
          15????//?Parse?iters?argument?from?get?request,?use?default?if?not?available.
          16????//?...?removed?for?brevity?...
          17????w.Write([]byte(fmt.Sprintf("e?=?%0.4f\n",?computeE(iters))))
          18??})
          19??//?Start?server...
          20}

          main() 函數(shù)中啟動(dòng)了一個(gè)簡(jiǎn)單的 HTTP 服務(wù)器,它在 /e 上暴露了一個(gè)單一的 GET 端點(diǎn),該端點(diǎn)使用迭代近似計(jì)算歐拉數(shù)(e),computeE 接收一個(gè)單一的查詢(xún)參數(shù)(iters),它指定了近似運(yùn)行的迭代次數(shù)。迭代次數(shù)越多,近似越精確,但代價(jià)是計(jì)算周期,我們不需要了解函數(shù)背后的數(shù)學(xué)知識(shí)點(diǎn),這里我們主要是了解如何跟蹤 computeE 的調(diào)用參數(shù)。

          為了了解 uprobes 是如何工作的,我們來(lái)看看二進(jìn)制文件內(nèi)部是如何跟蹤符號(hào)的。由于 uprobes 是通過(guò)插入調(diào)試陷阱指令來(lái)工作的,所以我們需要得到函數(shù)所在的地址,Linux 上的 Go 二進(jìn)制文件使用 ELF 來(lái)存儲(chǔ)調(diào)試信息,即使在優(yōu)化的二進(jìn)制文件中,這些信息也是可用的,除非調(diào)試數(shù)據(jù)被剝離了,我們可以使用命令 objdump 來(lái)檢查二進(jìn)制中的符號(hào)。

          [0]?%?objdump?--syms?app|grep?computeE
          00000000006609a0?g?????F?.text????000000000000004b??????????????main.computeE

          從輸出可以看出來(lái)函數(shù) computeE 位于地址 0x6609a0,要查看它周?chē)闹噶睿覀兛梢砸?objdump 將其拆解為二進(jìn)制(通過(guò)添加 -d 完成),拆解后的代碼是這樣的。

          [0]?%?objdump?-d?app?|?less
          00000000006609a0?:
          ??6609a0:???????48?8b?44?24?08??????????mov????0x8(%rsp),%rax
          ??6609a5:???????b9?02?00?00?00??????????mov????$0x2,%ecx
          ??6609aa:???????f2?0f?10?05?16?a6?0f????movsd??0xfa616(%rip),%xmm0
          ??6609b1:???????00
          ??6609b2:???????f2?0f?10?0d?36?a6?0f????movsd??0xfa636(%rip),%xmm1

          從中我們可以看到調(diào)用 computeE 時(shí)的情況,第一條指令是 mov 0x8(%rsp),%rax,這將內(nèi)容偏移 0x8 從 rsp 寄存器移到 rax 寄存器,這其實(shí)就是上面的輸入?yún)?shù)迭代,Go 的參數(shù)是在棧上傳遞的。

          有了這些信息,我們現(xiàn)在就可以編寫(xiě)代碼來(lái)跟蹤 compute 的參數(shù)了。

          構(gòu)建 Tracer

          為了捕捉事件,我們需要注冊(cè)一個(gè) uprobe 函數(shù),并有一個(gè)可以讀取輸出的用戶(hù)空間函數(shù)。我們將編寫(xiě)一個(gè)名為 tracer 的二進(jìn)制,負(fù)責(zé)注冊(cè) bPF 代碼和讀取 bPF 代碼的結(jié)果,如圖所示,uprobe 將簡(jiǎn)單地寫(xiě)入一個(gè) perf-buffer,一個(gè)用于 perf 事件的 linux 內(nèi)核數(shù)據(jù)結(jié)構(gòu)。

          Tracer 二進(jìn)制監(jiān)聽(tīng)從 App 產(chǎn)生的 perf 事件

          接下來(lái)讓我們來(lái)看看當(dāng)我們添加一個(gè) uprobe 時(shí)發(fā)生的細(xì)節(jié),下圖顯示了 Linux 內(nèi)核是如何用 uprobe 修改二進(jìn)制的。軟中斷指令(int3)作為main.computeE 的第一條指令被插入,這將引起一個(gè)軟中斷,允許 Linux 內(nèi)核執(zhí)行我們的 BPF 函數(shù),然后我們將參數(shù)寫(xiě)入 perf-buffer,由 tracer 異步讀取。

          這里的 BPF 函數(shù)比較簡(jiǎn)單,C 代碼如下所示。我們注冊(cè)這個(gè)函數(shù),這樣每次調(diào)用 main.computeE 時(shí)都會(huì)調(diào)用它,一旦它被調(diào)用,我們只需讀取函數(shù)參數(shù)并將其寫(xiě)入 perf buffer。大量的模板需要來(lái)設(shè)置緩沖區(qū)等,這可以在 完整的示例[3] 中找到。

          #include?

          BPF_PERF_OUTPUT(trace);

          inline?int?computeECalled(struct?pt_regs?*ctx)?{
          ??//?The?input?argument?is?stored?in?ax.
          ??long?val?=?ctx->ax;
          ??trace.perf_submit(ctx,?&val,?sizeof(val));
          ??return?0;
          }

          現(xiàn)在,我們有了一個(gè)功能完備的 main.computeE 函數(shù)的端到端參數(shù)跟蹤器,其結(jié)果如下所示。

          我們實(shí)際上可以使用 GDB來(lái) 查看對(duì)二進(jìn)制的修改,在這里,我們?cè)谶\(yùn)行跟蹤二進(jìn)制之前,將 0x6609a0 地址的指令轉(zhuǎn)儲(chǔ)起來(lái)。

          (gdb)?display?/4i?0x6609a0
          10:?x/4i?0x6609a0
          ???0x6609a0?:????mov????0x8(%rsp),%rax
          ???0x6609a5?:??mov????$0x2,%ecx
          ???0x6609aa?:?movsd??0xfa616(%rip),%xmm0
          ???0x6609b2?:?movsd??0xfa636(%rip),%xmm1

          這是我們運(yùn)行跟蹤二進(jìn)制后的情況,我們可以清楚地看到,現(xiàn)在第一條指令是 int3。

          (gdb)?display?/4i?0x6609a0
          7:?x/4i?0x6609a0
          ???0x6609a0?:????int3
          ???0x6609a1?:??mov????0x8(%rsp),%eax
          ???0x6609a5?:??mov????$0x2,%ecx
          ???0x6609aa?:?movsd??0xfa616(%rip),%xmm0

          雖然我們?yōu)檫@個(gè)特殊的例子硬編碼了追蹤器,但我們可以想辦法把這個(gè)過(guò)程通用化。Go 的許多特性,如嵌套指針、接口、通道等,使得這個(gè)過(guò)程具有挑戰(zhàn)性,但是解決這些問(wèn)題可實(shí)現(xiàn)現(xiàn)有系統(tǒng)中不存在的另一種檢測(cè)模式。另外,由于這個(gè)過(guò)程是在二進(jìn)制層面工作的,所以它可以與其他語(yǔ)言(C++、Rust 等)的原生編譯二進(jìn)制一起使用,我們只需要考慮到它們各自 ABI 的不同點(diǎn)即可。

          總結(jié)

          使用 uprobes 的 BPF 跟蹤有它自己的優(yōu)點(diǎn)和缺點(diǎn),當(dāng)我們需要對(duì)二進(jìn)制狀態(tài)進(jìn)行觀察時(shí),使用 BPF 是有好處的,即使是在附加調(diào)試器會(huì)有問(wèn)題或有害的環(huán)境中運(yùn)行時(shí)也是如此(例如生產(chǎn)二進(jìn)制文件)。最大的缺點(diǎn)是,即使是很小的應(yīng)用程序狀態(tài)的跟蹤也需要我們?nèi)ゾ帉?xiě)代碼,因?yàn)?BPF 的代碼的編寫(xiě)和維護(hù)還是相對(duì)較復(fù)雜的。

          參考文檔

          • https://github.com/iovisor/gobpf
          • https://github.com/iovisor/bcc
          • https://www.youtube.com/watch?v=SlcBq3xDc7I

          原文鏈接:https://blog.pixielabs.ai/blog/ebpf-function-tracing/post/

          參考資料

          [1]

          gobpf: https://github.com/iovisor/gobpf

          [2]

          eBPF: https://ebpf.io/

          [3]

          完整的示例: https://github.com/pixie-labs/pixie/blob/main/demos/simple-gotracing/trace_example/trace.go



          訓(xùn)練營(yíng)推薦





          ?點(diǎn)擊屏末?|??|?即刻學(xué)習(xí)

          瀏覽 72
          點(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>
                  自拍偷拍字幕第9页 | 成人看片suvav | 777奇米狠狠色 | 91大鸡吧| 99re3|