在實(shí)際的軟件開發(fā)過(guò)程中,內(nèi)存問(wèn)題常常是耗費(fèi)大量時(shí)間進(jìn)行分析的挑戰(zhàn)之一。為了更有效地定位和解決與內(nèi)存相關(guān)的難題,一系列輔助工具應(yīng)運(yùn)而生,其中備受贊譽(yù)的Valgrind工具便是其中之一。事實(shí)上,筆者本人曾利用Valgrind工具成功地發(fā)現(xiàn)并解決了一個(gè)隱藏在軟件中的bug,這充分體現(xiàn)了工具在開發(fā)過(guò)程中的重要性。
然而,同樣強(qiáng)大的bpftrace工具同樣具備簡(jiǎn)潔而直觀的特點(diǎn),能夠協(xié)助我們高效地追蹤內(nèi)存泄漏問(wèn)題。在這方面,bpftrace提供了一種更加精細(xì)的、實(shí)時(shí)的分析方式,幫助開發(fā)人員準(zhǔn)確地定位代碼中可能存在的內(nèi)存泄漏情況。
構(gòu)建樣例
我們編寫一個(gè)程序--mem_check.c,代碼中包含正確的申請(qǐng)內(nèi)存和釋放內(nèi)存的邏輯,同時(shí)包含存在內(nèi)存泄露的代碼代碼。。
#include
int main(){
char *p1 = NULL;
char *p2 = NULL;
for(int i = 0; i < 5; i++)
{
p1 = malloc(16);
}
for(int i = 0; i < 5; i++)
{
p2 = malloc(32);
free(p2);
}
getchar();
return 0;
}
上面的代碼非常簡(jiǎn)單,我們申請(qǐng)了5次16個(gè)字節(jié)的內(nèi)存,沒(méi)有釋放,存在內(nèi)存泄露。申請(qǐng)5次32個(gè)字節(jié)的內(nèi)存,有釋放,沒(méi)存在內(nèi)存泄露。那么我們?nèi)绾瓮ㄟ^(guò)bpftrace定位呢?
我們通過(guò)bpftrace對(duì)mem_check.c進(jìn)行動(dòng)態(tài)的統(tǒng)計(jì)內(nèi)存的申請(qǐng)和釋放,定位內(nèi)存泄露的問(wèn)題。我們需要對(duì)關(guān)鍵的兩個(gè)接口進(jìn)行probe--malloc和free,這兩個(gè)接口的實(shí)現(xiàn)在libc中。
編譯mem_check.c文件,生成可執(zhí)行文件:
探測(cè)mem_ckeck可執(zhí)行文件
bpftrace可以對(duì)內(nèi)核態(tài)進(jìn)行探測(cè)也可以對(duì)用戶態(tài)進(jìn)行探測(cè),其中探針如下:
- 內(nèi)核態(tài)探針:kprobe/kretprobe
- 用戶態(tài)探針:uprobe/uretprobe
mem_check.c是一個(gè)應(yīng)用程序,顯然我們需要使用用戶態(tài)探針:uprobe/uretprobe
通過(guò)uprobe探測(cè)mem_check.c中的malloc函數(shù),我們單行指令驗(yàn)證,參數(shù)格式是 uprobe:可執(zhí)行文件:函數(shù)名:
理論是沒(méi)有沒(méi)有問(wèn)題,但實(shí)際發(fā)生錯(cuò)誤:No probes to attach。原因:可執(zhí)行文件mem_check中找不到符號(hào):malloc,我們可以通過(guò)nm命令確定一下:
我們發(fā)現(xiàn)malloc是一個(gè)鏈接自GLIBC_2.2.5的符號(hào),并不是mem_ckeck自身的符號(hào),所以我們探測(cè)的符號(hào)修改libc庫(kù)中malloc符號(hào),系統(tǒng)中可能存在多個(gè)c庫(kù),我們需要找到mem_ckeck程序使用的C庫(kù),通過(guò)ldd命令查看:
mem_check可執(zhí)行文件使用的C庫(kù)為:/lib/x86_64-linux-gnu/libc.so.6,我們將可以執(zhí)行文件替換為/lib/x86_64-linux-gnu/libc.so.6。再次執(zhí)行,會(huì)出現(xiàn)大量?jī)?nèi)容,顯然是其他進(jìn)程調(diào)用了malloc引起的,而我們的mem_ckeck還沒(méi)有運(yùn)行,顯然還沒(méi)有探測(cè)我們的可執(zhí)行程序。
我們需要進(jìn)行過(guò)濾,增加filter只保留我們關(guān)心的應(yīng)用程序的調(diào)用探測(cè)。bpftrace提供了系統(tǒng)變量comm表示可執(zhí)行文件名 (進(jìn)程名),只需要在上述指令中增加 filter,只處理comm=="mem_check"的malloc調(diào)用事件。左邊終端執(zhí)行探測(cè),右邊終端執(zhí)行可執(zhí)行文件。每調(diào)用一次malloc函數(shù),就能探測(cè)到一次:
使用bpftrace腳本進(jìn)一步探測(cè)
將上面的單行命令變?yōu)閎pftrace腳本--bpf_test.bt
printf("start proben");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc calln");
}
END {
printf("end proben");
}
探測(cè)mem_check中malloc的內(nèi)存空間大小。
malloc的原型:
bpftrace的uprobe和kprobe可以通過(guò)內(nèi)置變量arg0、arg1 ··· ··· 訪問(wèn)函數(shù)參數(shù),對(duì)bpf_test.bt修改就可以打印malloc申請(qǐng)內(nèi)存的大小:
printf("start proben");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}
END {
printf("end proben");
}
如下圖可以看到mem_check中申請(qǐng)內(nèi)存的情況,最后一個(gè)malloc size 1024是mem_check自動(dòng)創(chuàng)建輸出緩沖區(qū)申請(qǐng)的內(nèi)存,不用理會(huì)。
探測(cè)mem_check中malloc的返回值
malloc的返回值是地址,需借助uretprobe進(jìn)行探測(cè),函數(shù)返回值可通過(guò)內(nèi)置變量retval訪問(wèn)。uretprobe的filter與malloc參數(shù)探測(cè)時(shí)類似,腳本修改為:
printf("start proben");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
}
END {
printf("end proben");
}
運(yùn)行結(jié)果:
探測(cè)mem_check中free
我們已經(jīng)探測(cè)到mem_check的malloc的內(nèi)存大小,內(nèi)存的地址,我們通過(guò)探測(cè)free,然后匹配malloc和free的情況就可以查找內(nèi)存的泄漏點(diǎn)。腳本修改為:
printf("start proben");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free /comm == "mem_check"/{
printf("free addr = %pn", arg0);
}
END {
printf("end proben");
}
運(yùn)行結(jié)果:
探測(cè)內(nèi)存泄露
上面我們已經(jīng)探測(cè)到了mem_check中的malloc,free情況。我們可以通過(guò)malloc和free的地址集合差,就可以得到內(nèi)存泄露的地址位置。
bpftrace底層使用的是eBPF的map作為存儲(chǔ)結(jié)構(gòu),可以簡(jiǎn)單的看作K-V存儲(chǔ),我們可以利用map來(lái)統(tǒng)計(jì)地址集合差,步驟如下:
- 定義一個(gè)map變量@mem:保存malloc返回的內(nèi)存地址。
- 當(dāng)探測(cè)到free調(diào)用時(shí),將@mem對(duì)應(yīng)地址刪除。
- 最后@mem剩下的就是內(nèi)存泄露的地址。
內(nèi)存泄露檢測(cè)腳本如下:
printf("start proben");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
@size = arg0;
}
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
@mem[retval] = @size;
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free /comm == "mem_check"/{
printf("free addr = %pn", arg0);
delete(@mem[arg0]);
}
END {
printf("end proben");
}
運(yùn)行結(jié)果:
如上圖,紅色框中就是沒(méi)有釋放的內(nèi)存和內(nèi)存大小。
總結(jié)
通過(guò)編寫一些簡(jiǎn)單的bpftrace腳本,我們就可以監(jiān)視應(yīng)用程序的內(nèi)存分配和釋放事件,捕獲內(nèi)存泄漏的跡象。這種直接的實(shí)時(shí)監(jiān)控方式,使得開發(fā)者能夠在問(wèn)題出現(xiàn)時(shí)即刻獲得反饋,從而更加迅速地解決潛在的內(nèi)存問(wèn)題,提升軟件的穩(wěn)定性和性能。
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
3025瀏覽量
74054 -
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68616 -
Valgrind
+關(guān)注
關(guān)注
0文章
9瀏覽量
6811
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論