一那先、關(guān)于debuginfo的疑惑
程序員應(yīng)該都知道,為了能夠使用gdb跟蹤程序赡艰,需要在編譯期使用gcc的-g選項(xiàng)售淡。而對(duì)于系統(tǒng)庫(kù)或是Linux內(nèi)核,使用gdb調(diào)試或使用systemtap探測(cè)時(shí)慷垮,還需要安裝相應(yīng)的debuginfo包揖闸。
what is systemtap ?
https://segmentfault.com/a/1190000010774974
例如glibc及它的debuginfo包為:
...
于是我不禁有如下這些疑問:
glibc-debuginfo中包含了什么信息?
glibc-debuginfo是如何創(chuàng)建出來的料身?
gdb或systemtap汤纸,是如何把glibc與glibc-debuginfo關(guān)聯(lián)起來的?
本文將通過一些例子芹血,來解答這些問題贮泞。
二楞慈、debuginfo中包含了什么信息?
讓我們來看看glibc-debuginfo中啃擦,包含有什么內(nèi)容:
[yunkai@fedora t]$ rpm -ql glibc-debuginfo-2.18-12.fc20.x86_64
/usr/lib/debug
/usr/lib/debug/.build-id
/usr/lib/debug/.build-id/00
/usr/lib/debug/.build-id/00/a32f1b9405f5fcd41a7618f3c2c895ee4aab09
/usr/lib/debug/.build-id/00/a32f1b9405f5fcd41a7618f3c2c895ee4aab09.debug
…
/usr/lib/debug/lib64/libthread_db.so.1.debug
/usr/lib/debug/lib64/libutil-2.18.so.debug
/usr/lib/debug/lib64/libc-2.18.so.debug
…
/usr/src/debug/glibc-2.18/wcsmbs/wcwidth.h
/usr/src/debug/glibc-2.18/wcsmbs/wmemchr.c
/usr/src/debug/glibc-2.18/wcsmbs/wmemcmp.c
由上可見抖部,glibc-debuginfo大致有三類文件:
存放在/usr/lib/debug/下的:.build-id/nn/nnn...nnn.debug文件,文件名是hash key议惰。
存放在/usr/lib/debug/下的其它*.debug文件慎颗,其文件名,是庫(kù)文件名+.debug后綴言询。
glibc的源代碼
當(dāng)使用gdb調(diào)試時(shí)俯萎,需要在機(jī)器碼與源代碼之間,建立起映射關(guān)系运杭。這就需要三個(gè)信息:
機(jī)器碼:可執(zhí)行文件夫啊、動(dòng)態(tài)鏈接庫(kù),例如:/lib64/libc-2.18.so
源代碼:顯然就是glibc-debuginfo中辆憔,包含的.c和.h等源文件撇眯。
映射關(guān)系:你應(yīng)該猜到了,它們就保存在*.debug文件中虱咧。
三熊榛、debuginfo是如何創(chuàng)建出來的?
當(dāng)我們使用gcc的-g選項(xiàng)編譯程序時(shí)腕巡,機(jī)器碼與源代碼的映射關(guān)系玄坦,會(huì)被默認(rèn)地與可執(zhí)行程序、動(dòng)態(tài)鏈接庫(kù)合并在一起绘沉。例如下面a.out可執(zhí)行程序煎楣,已經(jīng)包含了映射關(guān)系:
[yunkai@fedora t]$ nl main.c
`
1 #include <stdio.h>
2 int main()
3 {
4 printf("hello, world!\n");
5 return 0;
6 }
`
[yunkai@fedora t]$ gcc -g main.c
[yunkai@fedora t]$ ls -l
total 16
-rwxrwxr-x 1 yunkai yunkai 9502 Apr 9 14:55 a.out
-rw-rw-r-- 1 yunkai yunkai 76 Apr 9 14:49 main.c
把映射關(guān)系等調(diào)試信息,與可執(zhí)行文件车伞、動(dòng)態(tài)鏈接庫(kù)合并在一起择懂,會(huì)帶來一個(gè)顯著的問題:可執(zhí)行文件或庫(kù)的Size變得很大。這對(duì)于那些不關(guān)心調(diào)試信息的普通用戶另玖,是不必要的困曙。
例如,Linux的內(nèi)核日矫,如果帶上Debuginfo赂弓,會(huì)無謂的增加幾百M(fèi)的大小。如果一個(gè)Linux操作系統(tǒng)的所有庫(kù)都帶上各自的Debuginfo哪轿,那么光是一個(gè)干凈的操作系統(tǒng)盈魁,就需要浪費(fèi)掉幾G甚至十幾G的磁盤空間。如果是通過網(wǎng)絡(luò)安裝窃诉,還將浪費(fèi)所有用戶的帶寬杨耙,并顯著的拖慢安裝的進(jìn)度赤套。正是了為解決這個(gè)問題,在Linux上的各種程序和庫(kù)珊膜,在生成RPM時(shí)容握,就已經(jīng)把Debuginfo單獨(dú)的抽取出來,因此形成了獨(dú)立的debuginfo包车柠。
問題是共虑,如何讓程序生成分離的debuginfo呢己沛?我們可以通過objcopy命令的--only-keep-debug選項(xiàng)來實(shí)現(xiàn)品姓,下面的命令把調(diào)試信息從a.out中讀取出來攀细,寫到a.out.debug文件中:
[yunkai@fedora t]$ objcopy --only-keep-debug ./a.out a.out.debug
[yunkai@fedora t]$ ls -l
total 24
-rwxrwxr-x 1 yunkai yunkai 9502 Apr 9 14:55 a.out
-rwxrwxr-x 1 yunkai yunkai 6022 Apr 9 15:22 a.out.debug
-rw-rw-r-- 1 yunkai yunkai 76 Apr 9 14:49 main.c
既然已經(jīng)把調(diào)試信息,保存到了a.out.debug文件中塑陵,就可以通過objcopy的--strip-debug選項(xiàng)給a.out瘦身了(也可以使用strip --strip-debug ./a.out感憾,效果一樣):
[yunkai@fedora t]$ objcopy --strip-debug ./a.out
[yunkai@fedora t]$ ls -l
total 24
-rwxrwxr-x 1 yunkai yunkai 8388 Apr 9 15:27 a.out
-rwxrwxr-x 1 yunkai yunkai 6022 Apr 9 15:22 a.out.debug
-rw-rw-r-- 1 yunkai yunkai 76 Apr 9 14:49 main.c
當(dāng)把調(diào)試信息從a.out中清除后,使用gdb對(duì)a.out進(jìn)行調(diào)試令花,會(huì)報(bào)no debugging symbols found:
[yunkai@fedora t]$ gdb ./a.out
GNU gdb (GDB) Fedora 7.6.50.20130731-19.fc20
...
Reading symbols from /home/yunkai/t/a.out...(no debugging symbols found)...done.
(gdb)
顯然阻桅,gdb找不到調(diào)試信息了。因此兼都,我們需要在a.out中埋下一些線索嫂沉,以便gdb借助這些線索,可以正確地查找到它對(duì)應(yīng)的debug文件:a.out.debug俯抖。
在Linux下输瓜,可執(zhí)行文件或庫(kù),通常是ELF(Executable and Linkable Format)格式芬萍。這種格式,含有session headers搔啊。而調(diào)試信息的線索柬祠,正好可以通過一個(gè)約定的session header來保存,它叫.gnu_debuglink负芋÷祝可通過objcopy的--add-gnu-debuglink選項(xiàng),把調(diào)試信息的文件名(a.out.debug)保存到a.out的.gnu_debuglink這個(gè)header中旧蛾。然后gdb就可以正常調(diào)試了:
[yunkai@fedora t]$ objcopy --add-gnu-debuglink=a.out.debug ./a.out
[yunkai@fedora t]$ objdump -s -j .gnu_debuglink ./a.out
./a.out: file format elf64-x86-64
Contents of section .gnu_debuglink:
0000 612e6f75 742e6465 62756700 3fe5803b a.out.debug.?..;
[yunkai@fedora t]$ gdb a.out
...
Reading symbols from /home/yunkai/t/a.out...Reading symbols from /home/yunkai/t/a.out.debug...done.
上面的objcopy命令莽龟,其實(shí)是把a(bǔ).out.debug的文件名以及這個(gè)文件的CRC校驗(yàn)碼,寫到了.gnu_debuglink這個(gè)header的值中锨天,但是并沒有告訴a.out.debug所在的路徑(上面通過objdump命令毯盈,可以打印出.gnu_debuglink這個(gè)header的內(nèi)容)。
那么gdb是按照怎樣的規(guī)則病袄,去查找a.out.debug文件呢搂赋?在解答這個(gè)問題之前赘阀,我們先來看另一個(gè)session header,叫.note.gnu.build-id:
[yunkai@fedora t]$ readelf -t ./a.out | grep build-id
[ 3] .note.gnu.build-id
[yunkai@fedora t]$ readelf -n ./a.out
...
Notes at offset 0x00000274 with length 0x00000024:
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 888010ffb999590e7158422ea813169be34085a1
[yunkai@fedora t]$ readelf -n ./a.out.debug
...
Notes at offset 0x00000274 with length 0x00000024:
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 888010ffb999590e7158422ea813169be34085a1
這個(gè)session header是a.out原生就存在的脑奠,因此也被拷貝到了a.out.debug中基公。這個(gè)header,保存了一個(gè)Build ID宋欺,這個(gè)ID是根據(jù)a.out文件自動(dòng)計(jì)算出來的轰豆,每個(gè)執(zhí)行文件或庫(kù),都有它唯一的Build ID齿诞。
在第2節(jié)中酸休,我們注意到這種文件:.build-id/nn/nnnn...nnnn.debug,前兩個(gè)“nn”就是它的Build ID前兩位掌挚,后面的nnnn...nnnn則是Build ID的剩余部分雨席。而這個(gè)nnnn...nnnn.debug文件,只是改了個(gè)名字而已吠式。
而gdb陡厘,則是通過下面的順序查找a.out.debug文件:
<global debug directory>/.build-id/nn/nnnn...nnnn.a.out.debug
<the path of a.out>/a.out.debug
<the path of a.out>/.debug/a.out.debug
<global debug directory>/<the patch of a.out>/a.out.debug
而<global debug directory>默認(rèn)為/usr/lib/debug/√卣迹可以在gdb中糙置,通過set/show debug-file-directory命令來設(shè)置或查看這個(gè)值:
[yunkai@fedora t]$ gdb ./a.out
...
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "/usr/lib/debug".
既然a.out的Build ID為:888010ffb999590e7158422ea813169be34085a1,可以把a(bǔ).out.debug文件是目,移動(dòng)到/usr/lib/debug/.build-id/88/8010ffb999590e7158422ea813169be34085a1.debug:
[yunkai@fedora t]$ sudo cp a.out.debug \
/usr/lib/debug/.build-id/88/8010ffb999590e7158422ea813169be34085a1.debug
[yunkai@fedora t]$ gdb ./a.out
...
Reading symbols from /home/yunkai/t/a.out...Reading symbols from /usr/lib/debug/.build-id/88/8010ffb999590e7158422ea813169be34085a1.debug...done.
done.
由上可見谤饭,gdb就會(huì)優(yōu)先從/usr/lib/debug/.build-id/查找到對(duì)應(yīng)的debug信息。
四懊纳、a.out.debug里有什么內(nèi)容揉抵?
gcc目前會(huì)默認(rèn)會(huì)采用DWARF 4格式來保存調(diào)試信息∴头瑁可以通過readelf -w來查看DWARF的內(nèi)容:
[yunkai@fedora t]$ readelf -w ./a.out.debug
...
Contents of the .debug_info section:
Compilation Unit @ offset 0x0:
Length: 0x8d (32-bit)
Version: 4
Abbrev Offset: 0x0
Pointer Size: 8
<0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
<c> DW_AT_producer : (indirect string, offset: 0x6a): GNU C 4.8.2 20131212 (Red Hat 4.8.2-7) -mtune=generic -march=x86-64 -g
<10> DW_AT_language : 1 (ANSI C)
<11> DW_AT_name : (indirect string, offset: 0x2f): main.c
<15> DW_AT_comp_dir : (indirect string, offset: 0x5b): /home/yunkai/t
...
DWARF內(nèi)部通過DIE(Debugging Information Entry)冤今,形成一顆調(diào)用樹,DWARF在設(shè)計(jì)的時(shí)候茂缚,就考慮到了各種語言的支持戏罢,雖然它通常與ELF格式的文件一起工作,但它其實(shí)并不依賴ELF脚囊。
由于DWARF比較自由的設(shè)計(jì)龟糕,使它不僅支持C/C++,也支持Java/Python等等幾乎所有語言的調(diào)試信息的表達(dá)悔耘。
在DWARF里讲岁,通常包含:源代碼與機(jī)器碼的映射關(guān)系的行號(hào)表、宏信息、inline函數(shù)的信息催首、Call Frame信息等扶踊。
但對(duì)于普通用戶,通常不需要了解DWARF的太多細(xì)節(jié)郎任,如果好奇的話秧耗,推薦閱讀文獻(xiàn)5。
五舶治、在代碼中生成Marker探針
通過gcc的-g選項(xiàng)分井,所有函數(shù)名,都會(huì)自動(dòng)的生成相應(yīng)的debuginfo霉猛,供systemtap進(jìn)行探測(cè)尺锚,這種方法在英文上稱為:Debuginfo-based instrumentation,它的局限性在于惜浅,只能收集到函數(shù)調(diào)用的初始時(shí)刻瘫辩、以及函數(shù)返回的結(jié)束時(shí)刻的上下文信息。
為了解決這個(gè)問題坛悉,又提出了一種新方法:Compiled-in instrumentation伐厌,它可以讓程序員,把探針安插到指定的某行代碼中裸影,從而可以收集到那行代碼執(zhí)行時(shí)的上下文信息挣轨,這種探針被稱為Marker探針。
編寫Marker探針轩猩,需要在代碼中包含頭文件:
include <sys/sdt.h>
然后在目標(biāo)行卷扮,插入下面的Marker宏之一:
DTRACE_PROBE(provider, name)
DTRACE_PROBE4(provider, name, arg1, arg2, arg3, arg4)
寫好Marker探針并成功編譯后,可以使用下面的systemtap指令來查看Marker探針是否生效:
stap -L 'process("/path/to/a.out").mark("*")'
更具體的操作方法詳見文獻(xiàn)6均践,值得一提的是晤锹,Marker探針是非常輕量的,它幾乎對(duì)程序的性能沒有影響彤委,因?yàn)樗粫?huì)在代碼中生成nop匯編指令抖甘。它是通過把現(xiàn)場(chǎng)的上下文信息,保存在ELF文件的特定的section header(.stapsdt.base)來實(shí)現(xiàn)的葫慎,只會(huì)增加debuginfo文件的大小。
六薇宠、參考文獻(xiàn)
http://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
http://sourceware.org/binutils/docs-2.17/binutils/objcopy.html
https://blogs.oracle.com/dbx/entry/gnu_debuglink_or_debugging_system
https://blogs.oracle.com/dbx/entry/creating_separate_debug_info
http://dwarfstd.org/doc/DWARF4.pdf
https://sourceware.org/systemtap/wiki/AddingUserSpaceProbingToApps
————————————————
版權(quán)聲明:本文為CSDN博主「Chinainvent」的原創(chuàng)文章偷办,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明澄港。
原文鏈接:https://blog.csdn.net/chinainvent/article/details/24129311