0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何使用gobpf和uprobe來為Go程序構(gòu)建函數(shù)參數(shù)跟蹤程序

Linux閱碼場(chǎng) ? 來源:Linux內(nèi)核之旅 ? 作者:Zain Asgar, 陳恒奇 ? 2021-04-03 16:15 ? 次閱讀

這是本系列文章的第一篇,講述了我們?nèi)绾卧谏a(chǎn)環(huán)境中使用 eBPF 調(diào)試應(yīng)用程序而無需重新編譯/重新部署。這篇文章介紹了如何使用 gobpf 和 uprobe 來為 Go 程序構(gòu)建函數(shù)參數(shù)跟蹤程序。這項(xiàng)技術(shù)也可以擴(kuò)展應(yīng)用于其他編譯型語言,例如 C++,Rust 等。本系列的后續(xù)文章將討論如何使用 eBPF 來跟蹤 HTTP/gRPC/SSL 等。

簡(jiǎn)介

在調(diào)試時(shí),我們通常對(duì)了解程序的狀態(tài)感興趣。這使我們能夠檢查程序正在做什么,并確定缺陷在代碼中的位置。觀察狀態(tài)的一種簡(jiǎn)單方法是使用調(diào)試器來捕獲函數(shù)的參數(shù)。對(duì)于 Go 程序來說,我們經(jīng)常使用 Delve 或者 GDB。

在開發(fā)環(huán)境中,Delve 和 GDB 工作得很好,但是在生產(chǎn)環(huán)境中并不經(jīng)常使用它們。那些使調(diào)試器強(qiáng)大的特性也讓它們不適合在生產(chǎn)環(huán)境中使用。調(diào)試器會(huì)導(dǎo)致程序中斷,甚至允許修改狀態(tài),這可能會(huì)導(dǎo)致軟件產(chǎn)生意外故障。

為了更好地捕獲函數(shù)參數(shù),我們將探索使用 eBPF(在 Linux 4.x+ 中可用)以及高級(jí)的 Go 程序庫 gobpf。

eBPF 是什么?

擴(kuò)展的 BPF(eBPF) 是 Linux 4.x+ 里的一項(xiàng)內(nèi)核技術(shù)。你可以把它想像成一個(gè)運(yùn)行在 Linux 內(nèi)核中的輕量級(jí)的沙箱虛擬機(jī),可以提供對(duì)內(nèi)核內(nèi)存的經(jīng)過驗(yàn)證的訪問。

如下概述所示,eBPF 允許內(nèi)核運(yùn)行 BPF 字節(jié)碼。盡管使用的前端語言可能會(huì)有所不同,但它通常是 C 的受限子集。一般情況下,使用 Clang 將 C 代碼編譯為 BPF 字節(jié)碼,然后驗(yàn)證這些字節(jié)碼,確??梢园踩\(yùn)行。這些嚴(yán)格的驗(yàn)證確保了機(jī)器碼不會(huì)有意或無意地破壞 Linux 內(nèi)核,并且 BPF 探針每次被觸發(fā)時(shí),都只會(huì)執(zhí)行有限的指令。這些保證使 eBPF 可以用于性能關(guān)鍵的工作負(fù)載,例如數(shù)據(jù)包過濾,網(wǎng)絡(luò)監(jiān)控等。

從功能上講,eBPF 允許你在某些事件(例如定時(shí)器,網(wǎng)絡(luò)事件或函數(shù)調(diào)用)觸發(fā)時(shí)運(yùn)行受限的 C 代碼。當(dāng)在函數(shù)調(diào)用上觸發(fā)時(shí),我們稱這些函數(shù)為探針,它們既可以用于內(nèi)核里的函數(shù)調(diào)用(kprobe) 也可以用于用戶態(tài)程序中的函數(shù)調(diào)用(uprobe)。本文重點(diǎn)介紹使用 uprobe 來動(dòng)態(tài)跟蹤函數(shù)參數(shù)。

Uprobe

uprobe 可以通過插入觸發(fā)軟中斷的調(diào)試陷阱指令(x86 上的 int3)來攔截用戶態(tài)程序。這也是調(diào)試器的工作方式。uprobe 的流程與任何其他 BPF 程序基本相同,如下圖所示。經(jīng)過編譯和驗(yàn)證的 BPF 程序?qū)⒆鳛?uprobe 的一部分執(zhí)行,并且可以將結(jié)果寫入緩沖區(qū)。

fe942248-8cdd-11eb-8b86-12bb97331649.jpg

讓我們看看 uprobe 是如何工作的。要部署 uprobe 并捕獲函數(shù)參數(shù),我們將使用這個(gè)簡(jiǎn)單的示例程序。這個(gè) Go 程序的相關(guān)部分如下所示。

main() 是一個(gè)簡(jiǎn)單的 HTTP 服務(wù)器,在路徑 /e 上公開單個(gè) GET 端點(diǎn),該端點(diǎn)使用迭代逼近來計(jì)算歐拉數(shù)(e)。computeE接受單個(gè)查詢參數(shù)(iterations),該參數(shù)指定計(jì)算近似值要運(yùn)行的迭代次數(shù)。迭代次數(shù)越多,近似值越準(zhǔn)確,但會(huì)消耗指令周期。理解函數(shù)背后的數(shù)學(xué)并不是必需的。我們只是想跟蹤對(duì) computeE 的任何調(diào)用的參數(shù)。

// computeE computes the approximation of e by running a fixed number of iterations.

func computeE(iterations int64) float64 {

res := 2.0

fact := 1.0

for i := int64(2); i 《 iterations; i++ {

fact *= float64(i)

res += 1 / fact

}

return res

}

func main() {

http.HandleFunc(“/e”, func(w http.ResponseWriter, r *http.Request) {

// Parse iters argument from get request, use default if not available.

// 。.. removed for brevity 。..

w.Write([]byte(fmt.Sprintf(“e = %0.4f

”, computeE(iters))))

})

// Start server.。.

}

要了解 uprobe 的工作原理,讓我們看一下二進(jìn)制文件中如何跟蹤符號(hào)。由于 uprobe 通過插入調(diào)試陷阱指令來工作,因此我們需要獲取函數(shù)所在的地址。Linux 上的 Go 二進(jìn)制文件使用 ELF 存儲(chǔ)調(diào)試信息。除非刪除了調(diào)試數(shù)據(jù),否則即使在優(yōu)化過的二進(jìn)制文件中也可以找到這些信息。我們可以使用 objdump 命令檢查二進(jìn)制文件中的符號(hào):

[0] % objdump --syms app|grep computeE

00000000006609a0 g F .text 000000000000004b main.computeE

從這個(gè)輸出中,我們知道函數(shù) computeE 位于地址 0x6609a0。要看到它前后的指令,我們可以使用 objdump 來反匯編二進(jìn)制文件(通過添加 -d 選項(xiàng)實(shí)現(xiàn))。反匯編后的代碼如下:

[0] % objdump -d app | less

00000000006609a0 《main.computeE》:

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

由此可見,當(dāng) computeE 被調(diào)用時(shí)會(huì)發(fā)生什么。第一條指令是 mov 0x8(%rsp), %rax。它把 rsp 寄存器偏移 0x8 的內(nèi)容移動(dòng)到 rax 寄存器。這實(shí)際上就是上面的輸入?yún)?shù) iterations。Go 的參數(shù)在棧上傳遞。

有了這些信息,我們現(xiàn)在就可以繼續(xù)深入,編寫代碼來跟蹤 computeE 的參數(shù)了。

構(gòu)建跟蹤程序

要捕獲事件,我們需要注冊(cè)一個(gè) uprobe 函數(shù),還需要一個(gè)可以讀取輸出的用戶空間函數(shù)。如下圖所示。我們將編寫一個(gè)稱為跟蹤程序的二進(jìn)制文件,它負(fù)責(zé)注冊(cè) BPF 代碼并讀取 BPF 代碼的結(jié)果。如圖所示,uprobe 簡(jiǎn)單地寫入 perf buffer,這是用于 perf 事件的 Linux 內(nèi)核數(shù)據(jù)結(jié)構(gòu)。

fec975f6-8cdd-11eb-8b86-12bb97331649.png

現(xiàn)在,我們已了解了涉及到的各個(gè)部分,下面讓我們?cè)敿?xì)研究添加 uprobe 時(shí)發(fā)生的情況。下圖顯示了 Linux 內(nèi)核如何使用uprobe 修改二進(jìn)制文件。軟中斷指令(int3)作為第一條指令被插入 main.computeE 中。這將導(dǎo)致軟中斷,從而允許 Linux 內(nèi)核執(zhí)行我們的 BPF 函數(shù)。然后我們將參數(shù)寫入 perf buffer,該緩沖區(qū)由跟蹤程序異步讀取。

ff0bbdbc-8cdd-11eb-8b86-12bb97331649.png

BPF 函數(shù)相對(duì)簡(jiǎn)單,C代碼如下所示。我們注冊(cè)這個(gè)函數(shù),每次調(diào)用 main.computeE 時(shí)都將調(diào)用它。一旦調(diào)用,我們只需讀取函數(shù)參數(shù)并寫入 perf buffer。設(shè)置緩沖區(qū)需要很多樣板代碼,可以在完整的示例中找到。

#include 《uapi/linux/ptrace.h》

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é)果。

ff4b47e8-8cdd-11eb-8b86-12bb97331649.gif

另一個(gè)很棒的事情是,我們可以使用 GDB 來查看對(duì)二進(jìn)制文件所做的修改。在運(yùn)行我們的跟蹤程序之前,我們輸出地址 0x6609a0 的指令。

(gdb) display /4i 0x6609a0

10: x/4i 0x6609a0

0x6609a0 《main.computeE》: mov 0x8(%rsp),%rax

0x6609a5 《main.computeE+5》: mov $0x2,%ecx

0x6609aa 《main.computeE+10》: movsd 0xfa616(%rip),%xmm0

0x6609b2 《main.computeE+18》: movsd 0xfa636(%rip),%xmm1

而這是在我們運(yùn)行跟蹤程序之后。我們可以清楚地看到,第一個(gè)指令現(xiàn)在變成 int3 了。

(gdb) display /4i 0x6609a0

7: x/4i 0x6609a0

0x6609a0 《main.computeE》: int3

0x6609a1 《main.computeE+1》: mov 0x8(%rsp),%eax

0x6609a5 《main.computeE+5》: mov $0x2,%ecx

0x6609aa 《main.computeE+10》: movsd 0xfa616(%rip),%xmm0

盡管我們?yōu)樵撎囟ㄊ纠龑?duì)跟蹤程序進(jìn)行了硬編碼,但是這個(gè)過程是可以通用化的。Go 的許多方面(例如嵌套指針,接口,通道等)讓這個(gè)過程變得有挑戰(zhàn)性,但是解決這些問題可以使用現(xiàn)有系統(tǒng)中不存在的另一種檢測(cè)模式。另外,因?yàn)檫@一過程工作在二進(jìn)制層面,它也可以用于其他語言(C++,Rust 等)編譯的二進(jìn)制文件。我們只需考慮它們各自 ABI 的差異。

下一步是什么?

使用 uprobe 進(jìn)行 BPF 跟蹤有其自身的優(yōu)缺點(diǎn)。當(dāng)我們需要觀察二進(jìn)制程序的狀態(tài)時(shí),BPF 很有用,甚至在連接調(diào)試器會(huì)產(chǎn)生問題或者壞處的環(huán)境(例如生產(chǎn)環(huán)境二進(jìn)制程序)。最大的缺點(diǎn)是,即使是最簡(jiǎn)單的程序狀態(tài)的觀測(cè)性,也需要編寫代碼來實(shí)現(xiàn)。編寫和維護(hù) BPF 代碼很復(fù)雜。沒有大量高級(jí)工具,不太可能把它當(dāng)作一般的調(diào)試手段。
編輯:lyn

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    316

    瀏覽量

    21695
  • 函數(shù)參數(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    6

    瀏覽量

    6002
  • BPF
    BPF
    +關(guān)注

    關(guān)注

    0

    文章

    25

    瀏覽量

    4027

原文標(biāo)題:在生產(chǎn)環(huán)境中使用 eBPF 調(diào)試 GO 程序

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    TMETRIC:簡(jiǎn)單步驟將工作區(qū)連接到時(shí)間跟蹤應(yīng)用程序

    將計(jì)時(shí)器按鈕添加到組合門票 完成這三個(gè)簡(jiǎn)單步驟以在 Assembla 中啟用時(shí)間跟蹤。設(shè)置時(shí)間不超過 3 分鐘。 注冊(cè) TMetric 具有高級(jí)計(jì)費(fèi)和報(bào)告功能的時(shí)間跟蹤應(yīng)用程序 安裝瀏覽器擴(kuò)展
    的頭像 發(fā)表于 01-07 09:23 ?96次閱讀
    TMETRIC:簡(jiǎn)單步驟將工作區(qū)連接到時(shí)間<b class='flag-5'>跟蹤</b>應(yīng)用<b class='flag-5'>程序</b>

    嵌入式學(xué)習(xí)-飛凌嵌入式ElfBoard ELF 1板卡-spi編程示例之spi編寫程序

    傳輸數(shù)據(jù)。它使用`ioctl`系統(tǒng)調(diào)用和`SPI_IOC_MESSAGE`命令執(zhí)行SPI數(shù)據(jù)傳輸。第四部分:`main`函數(shù)程序的入口點(diǎn)。它將`send_num`變量設(shè)置38,然
    發(fā)表于 11-07 09:36

    飛凌嵌入式ElfBoard ELF 1板卡-spi編程示例之spi編寫程序

    傳輸數(shù)據(jù)。它使用`ioctl`系統(tǒng)調(diào)用和`SPI_IOC_MESSAGE`命令執(zhí)行SPI數(shù)據(jù)傳輸。第四部分:`main`函數(shù)程序的入口點(diǎn)。它將`send_num`變量設(shè)置38,然
    發(fā)表于 11-06 09:15

    面試???1:函數(shù)指針與指針函數(shù)、數(shù)組指針與指針數(shù)組

    函數(shù)是指返回值指針的函數(shù)。語法:返回值類型*函數(shù)名(參數(shù)列表)。示例代碼:#include#includeint*getNumber(in
    的頭像 發(fā)表于 08-10 08:11 ?971次閱讀
    面試???1:<b class='flag-5'>函數(shù)</b>指針與指針<b class='flag-5'>函數(shù)</b>、數(shù)組指針與指針數(shù)組

    如何看懂檢測(cè)設(shè)備程序邏輯

    的地方,通常是主函數(shù)(main function)或啟動(dòng)函數(shù)(startup function)。在程序入口處,通常會(huì)進(jìn)行一些初始化操作,如初始化全局變量、配置硬件設(shè)備等。 程序主體
    的頭像 發(fā)表于 07-17 16:50 ?482次閱讀

    python函數(shù)的萬能參數(shù)

    我們通過一個(gè)簡(jiǎn)單的事例展示一下函數(shù)的萬能參數(shù),我們先寫一個(gè)最簡(jiǎn)單的函數(shù)。
    的頭像 發(fā)表于 07-17 14:56 ?367次閱讀
    python<b class='flag-5'>函數(shù)</b>的萬能<b class='flag-5'>參數(shù)</b>

    STM32F103VE ADC采樣正弦波,程序阻塞如何處理?

    函數(shù)的實(shí)現(xiàn)是匯編實(shí)現(xiàn)的,無法跟蹤。請(qǐng)問應(yīng)該如何處理? 如果有類似的例子,也請(qǐng)給我參考一下。 請(qǐng)指教,謝謝! 程序代碼: u32 adcin[1024]={0}; while(1) { for(i=0
    發(fā)表于 05-15 07:23

    使用Redis和Spring?Ai構(gòu)建rag應(yīng)用程序

    隨著AI技術(shù)的不斷進(jìn)步,開發(fā)者面臨著如何有效利用現(xiàn)有工具和技術(shù)加速開發(fā)過程的挑戰(zhàn)。Redis與SpringAI的結(jié)合為Java開發(fā)者提供了一個(gè)強(qiáng)大的平臺(tái),以便快速構(gòu)建并部署響應(yīng)式AI應(yīng)用。探索這一
    的頭像 發(fā)表于 04-29 08:04 ?1086次閱讀
    使用Redis和Spring?Ai<b class='flag-5'>構(gòu)建</b>rag應(yīng)用<b class='flag-5'>程序</b>

    Go語言中的函數(shù)、方法與接口詳解

    Go 沒有類,不過可以為結(jié)構(gòu)體類型定義方法。方法就是一類帶特殊的接收者參數(shù)函數(shù)。方法接收者在它自己的參數(shù)列表內(nèi),位于 func 關(guān)鍵字和方法名之間。(非結(jié)構(gòu)體類型也可以定義方法)
    的頭像 發(fā)表于 04-23 16:21 ?892次閱讀

    使用Docker部署Go Web應(yīng)用程序步驟

    大多數(shù)情況下Go應(yīng)用程序被編譯成單個(gè)二進(jìn)制文件,web應(yīng)用程序則會(huì)包括模版和配置文件。而當(dāng)一個(gè)項(xiàng)目中有很多文件的時(shí)候,由于很多文件沒有同步就會(huì)導(dǎo)致錯(cuò)誤的發(fā)生并且產(chǎn)生很多的問題。
    發(fā)表于 04-20 09:33 ?537次閱讀
    使用Docker部署<b class='flag-5'>Go</b> Web應(yīng)用<b class='flag-5'>程序</b>步驟

    學(xué)習(xí)筆記|如何用Go程序采集溫濕度傳感器數(shù)據(jù)

    在共創(chuàng)社內(nèi)部的交流中,先前有一位成員展示了如何借助C語言實(shí)現(xiàn)對(duì)AHT20溫濕度傳感器數(shù)據(jù)的讀取。這一實(shí)例觸發(fā)了另一位共創(chuàng)官的靈感,他決定采納Go語言重新構(gòu)建這一數(shù)據(jù)采集流程。接下來,我們將詳細(xì)解析
    的頭像 發(fā)表于 03-21 11:46 ?768次閱讀
    學(xué)習(xí)筆記|如何用<b class='flag-5'>Go</b><b class='flag-5'>程序</b>采集溫濕度傳感器數(shù)據(jù)

    什么是pipeline?Go構(gòu)建流數(shù)據(jù)pipeline的技術(shù)

    本文介紹了在 Go構(gòu)建流數(shù)據(jù)pipeline的技術(shù)。 處理此類pipeline中的故障很棘手,因?yàn)閜ipeline中的每個(gè)階段可能會(huì)阻止嘗試向下游發(fā)送值,并且下游階段可能不再關(guān)心傳入的數(shù)據(jù)。
    的頭像 發(fā)表于 03-11 10:16 ?652次閱讀

    如何使用exit()、_exit()和_Exit()終止程序運(yùn)行呢?

    在Linux系統(tǒng)下,你可以使用 exit()、_exit() 和 _Exit() 終止程序運(yùn)行,特別是在出現(xiàn)錯(cuò)誤或執(zhí)行失敗的情況下。
    的頭像 發(fā)表于 02-22 12:20 ?979次閱讀

    如何使用linux下gdb調(diào)試python程序

    如何使用linux下gdb調(diào)試python程序? 在Linux下,可以使用GDB(GNU調(diào)試器)調(diào)試Python程序。GDB是一個(gè)強(qiáng)大的調(diào)試工具,可以幫助開發(fā)者診斷和修復(fù)
    的頭像 發(fā)表于 01-31 10:41 ?2703次閱讀

    GD32 MCU是如何進(jìn)入中斷函數(shù)

    用過GD32 MCU的小伙伴們都知道,程序是順序執(zhí)行的,但當(dāng)有中斷的時(shí)候程序會(huì)跳轉(zhuǎn)到中斷函數(shù),執(zhí)行完中斷函數(shù)
    的頭像 發(fā)表于 01-30 09:45 ?1146次閱讀
    GD32 MCU是如何進(jìn)入中斷<b class='flag-5'>函數(shù)</b>的