圖文詳解 Gcc -g 調(diào)試信息
大家好,這里是物聯(lián)網(wǎng)心球。
作為一個(gè)Linux開發(fā)者,我們經(jīng)常會(huì)通過(guò)gcc -g命令來(lái)編譯可執(zhí)行程序,-g選項(xiàng)能夠生成調(diào)試信息,開發(fā)者根據(jù)調(diào)試信息能夠快速定位并排查程序問(wèn)題。
那么,調(diào)試信息到底是什么呢?本文我們一探究竟。
1.調(diào)試信息的重要性
gdb調(diào)試是一種非常重要的調(diào)試手段,如果gdb調(diào)試的程序沒(méi)有調(diào)試信息,那么我們將無(wú)法從gdb調(diào)試器中獲取到有價(jià)值的信息,調(diào)試過(guò)程也會(huì)異常困難。下面我們通過(guò)幾個(gè)場(chǎng)景來(lái)驗(yàn)證一下。
場(chǎng)景1:查看變量
通過(guò)print命令打印變量的值時(shí),帶調(diào)試信息輸出示例如下:
(gdb) print a
$1 = 100未帶調(diào)試信息輸出示例如下:
(gdb) print a
'a' has unknown type; cast it to its declared typegdb調(diào)試未帶調(diào)試信息的可執(zhí)行程序時(shí)無(wú)法識(shí)別變量。
場(chǎng)景2:打印數(shù)據(jù)類型
通過(guò)ptype命令打印數(shù)據(jù)類型,帶調(diào)試信息輸出示例如下:
(gdb) ptype it
type = struct item {
char num;
int data;
}未帶調(diào)試信息輸出示例如下:
(gdb) ptype it
No symbol table is loaded. Use the "file" command.gdb調(diào)試未帶調(diào)試信息的可執(zhí)行程序時(shí)無(wú)法識(shí)別數(shù)據(jù)類型。
場(chǎng)景3:指定源文件行號(hào)設(shè)置斷點(diǎn)
通過(guò)break命令在源文件指定行號(hào)設(shè)置斷點(diǎn),帶調(diào)試信息輸出示例如下:
(gdb) break main.c:12
Breakpoint 2 at 0x55555555515c: file main.c, line 14.未帶調(diào)試信息輸出示例如下:
(gdb) break main.c:12
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (main.c:12) pending.gdb調(diào)試未帶調(diào)試信息的可執(zhí)行程序時(shí),無(wú)法在指定源文件行號(hào)設(shè)置斷點(diǎn)。
場(chǎng)景4:打印堆棧
通過(guò)bt命令打印堆棧信息,帶調(diào)試信息輸出示例如下:
(gdb) bt
#0 main (argc=1, argv=0x7fffffffe468) at main.c:14未帶調(diào)試信息輸出示例如下:
(gdb) bt
#0 0x0000555555555151 in main ()gdb調(diào)試未帶調(diào)試信息的可執(zhí)行程序時(shí),打印堆棧信息無(wú)行號(hào)和其他調(diào)試信息。
通過(guò)以上幾個(gè)場(chǎng)景的對(duì)比,我們能夠直觀的感受到,帶調(diào)試信息的可執(zhí)行文件能夠提供更加豐富和關(guān)鍵的調(diào)試信息,協(xié)助開發(fā)者高效地解決問(wèn)題。
2.認(rèn)識(shí)調(diào)試信息
調(diào)試信息其實(shí)并不神秘,它是嵌入在ELF文件中的節(jié),這些節(jié)的形式為.debug_*(*代表不同的節(jié))。
我們通過(guò)圖1來(lái)深入學(xué)習(xí)調(diào)試信息。
圖1 兩種文件對(duì)比
圖1左邊為無(wú)調(diào)試信息可執(zhí)行文件,右邊為帶調(diào)試信息可執(zhí)行文件。無(wú)調(diào)試信息可執(zhí)行文件中的內(nèi)容比較少,文件占用內(nèi)存空間也比較小。通過(guò)對(duì)比,我們發(fā)現(xiàn)帶調(diào)試信息可執(zhí)行文件多了一些節(jié):.debug_aranges節(jié)、.debug_info節(jié)、.debug_abbrev節(jié)、.debug_line節(jié)、.debug_str節(jié)、.debug_line_str節(jié)。這些多出來(lái)的節(jié)就是我們今天的主角:調(diào)試信息。這些節(jié)并不是隨意生成的,需要遵循一定的規(guī)則,這個(gè)規(guī)則就是DWARF。
3.DWARF介紹
DWARF(全稱:Debugging With Attributed Record Formats)是一種廣泛使用的調(diào)試數(shù)據(jù)格式標(biāo)準(zhǔn),旨在為編譯器、調(diào)試器和其他工具提供一種標(biāo)準(zhǔn)化的機(jī)制,用于描述程序的源代碼結(jié)構(gòu)、變量、類型、函數(shù)調(diào)用棧等信息。
DWARF共經(jīng)歷了5個(gè)版本的迭代:
- DWARF1:1992年發(fā)布,為Unix System V的sdb調(diào)試器設(shè)計(jì),支持C 語(yǔ)言,現(xiàn)已很少使用。
- DWARF2:1993年發(fā)布,修正v1問(wèn)題,增加C++支持,引入數(shù)據(jù)壓縮。
- DWARF3:2005年發(fā)布,增加對(duì)多種語(yǔ)言支持,優(yōu)化數(shù)據(jù)壓縮。
- DWARF4:2010年發(fā)布,進(jìn)一步改善數(shù)據(jù)壓縮,支持編譯器優(yōu)化后代碼描述。
- DWARF5:2017年發(fā)布,支持調(diào)試信息分離,改進(jìn)宏和源文件描述。
DWARF的規(guī)則很復(fù)雜,筆者不建議大家直接研究DWARF標(biāo)準(zhǔn)。我們可以把DWARF標(biāo)準(zhǔn)當(dāng)做一個(gè)手冊(cè),當(dāng)我們學(xué)習(xí)的過(guò)程中遇到問(wèn)題再去查閱它。相較于直接學(xué)習(xí)DWARF標(biāo)準(zhǔn),更為有效的學(xué)習(xí)方法是選搞懂各種調(diào)試節(jié)(.debug_*)的原理和作用。
以DWARF5為例,常見(jiàn)的調(diào)試節(jié)見(jiàn)表1。
表1 DWARF5常見(jiàn)調(diào)試節(jié)
通過(guò)命令:readelf -w 可執(zhí)行文件,可顯示所有的調(diào)試節(jié)。由于該命令展示的內(nèi)容比較多,限于篇幅,這里不展示輸出示例。
3.1 .debug_aranges節(jié)
.debug_aranges節(jié)用于提供內(nèi)存地址與編譯單元之間的映射關(guān)系,編譯單元后續(xù)會(huì)詳細(xì)介紹。通過(guò).debug_aranges節(jié),調(diào)試器可以快速將程序計(jì)數(shù)器(PC)的值映射到.debug_info節(jié)中的編譯單元,從而獲取相關(guān)的調(diào)試信息。
通過(guò)命令:readelf -wr 可執(zhí)行文件,可查看.debug_aranges信息,輸出示例如下:
圖片
.debug_aranges節(jié)分為兩個(gè)部分:頭部信息和地址范圍表。
頭部信息相關(guān)字段如下:
- Length:表示該節(jié)的總長(zhǎng)度(不包括這個(gè)長(zhǎng)度字段本身)。
- Version:表示 DWARF 格式的版本號(hào),通常為2或4。
- Offset into .debug_info:表示.debug_info節(jié)中的偏移量,用于定位編譯單元的起始位置。
- Pointer Size:表示目標(biāo)系統(tǒng)中指針的大小,以字節(jié)為單位。
- Segment Size:表示目標(biāo)系統(tǒng)中段選擇符的大小,以字節(jié)為單位,通常為 0。
地址范圍表由一系列的地址范圍對(duì)組成,每個(gè)地址范圍對(duì)包含兩個(gè)字段: - Address:表示地址范圍的起始地址。
- Length:表示地址范圍的長(zhǎng)度。
gcc編譯可執(zhí)行程序時(shí),會(huì)將源文件(如.c/.cpp文件)編譯成編譯單元并將編譯單元覆蓋的地址范圍記錄在.debug_aranges節(jié)。
gdb需要確定某個(gè)指令地址(如:0x7fff1234)對(duì)應(yīng)的源代碼位置時(shí): 首先,通過(guò)0x7fff1234地址查詢.debug_aranges節(jié)地址范圍表,找到對(duì)應(yīng)的編譯單元在.debug_info中的偏移;然后,通過(guò)偏移定位到.debug_info中的編譯單元;最后,從編譯單元開始解析DIE樹,獲取到源碼相關(guān)的信息。
3.2 .debug_info節(jié)
.debug_info節(jié)由調(diào)試信息條目(Debugging Information Entry,DIE)構(gòu)成。DIE是.debug_info節(jié)中的一個(gè)基本單元,它通過(guò)標(biāo)簽(Tag)和屬性(Attributes)來(lái)描述程序中的一個(gè)語(yǔ)義實(shí)體(如:函數(shù)名、參數(shù)、局部變量、代碼行號(hào)范圍)。每個(gè)DIE可以有多個(gè)子條目,形成一個(gè)樹狀結(jié)構(gòu),用于描述更復(fù)雜的語(yǔ)義關(guān)系。
通過(guò)命令:readelf -wi 可執(zhí)行文件,可查看.debug_info信息,輸出示例如下:
圖片
每個(gè) DIE 包含以下幾個(gè)部分:
(1)Tag(標(biāo)簽)
Tag 是一個(gè)單字節(jié)的值,用于標(biāo)識(shí)調(diào)試信息條目(DIE)的類型。常見(jiàn)的Tag如下:
- DW_TAG_compile_unit:表示一個(gè)編譯單元,通常是源文件。
- DW_TAG_subprogram:表示一個(gè)函數(shù)或方法。
- DW_TAG_variable:表示一個(gè)變量,可以是全局變量或局部變量。
- DW_TAG_formal_parameter:表示函數(shù)的參數(shù)。
- DW_TAG_typedef:表示一個(gè)類型定義。
- DW_TAG_structure_type:表示一個(gè)結(jié)構(gòu)體類型。
- DW_TAG_union_type:表示一個(gè)聯(lián)合體類型。
- DW_TAG_enumeration_type:表示一個(gè)枚舉類型。
- DW_TAG_array_type:表示一個(gè)數(shù)組類型。
- DW_TAG_base_type:表示一個(gè)基本類型,如int 、float等。
(2)Attributes(屬性)
Attributes 是一個(gè)屬性列表,每個(gè)屬性由一個(gè)屬性名稱和一個(gè)屬性值組成。常見(jiàn)的Attributes如下:
- DW_AT_name:表示實(shí)體的名稱,如變量名、函數(shù)名、文件名等。
- DW_AT_type:表示實(shí)體的類型,通常是一個(gè)偏移量,指向.debug_info節(jié)中的另一個(gè) DIE。
- DW_AT_location:表示變量的存儲(chǔ)位置,可以是寄存器或內(nèi)存地址。
- DW_AT_low_pc和DW_AT_high_pc:表示函數(shù)的代碼范圍,分別表示函數(shù)的起始地址和結(jié)束地址。
- DW_AT_decl_file、DW_AT_decl_line 和DW_AT_decl_column:表示實(shí)體在源文件中的聲明位置。
(3)Children(子條目)
Children 是一個(gè) DIE 的子條目列表,每個(gè)子條目也是一個(gè) DIE。
3.3 .debug_abbrev節(jié)
.debug_abbrev節(jié)用于定義.debug_info節(jié)中調(diào)試信息條目(DIE)的結(jié)構(gòu)和屬性的縮寫表。通過(guò)使用縮寫表,.debug_info節(jié)可以更高效地存儲(chǔ)和解析調(diào)試信息,減少重復(fù)數(shù)據(jù)的存儲(chǔ)。
通過(guò)命令:readelf -wa 可執(zhí)行文件,可查看.debug_abbrev節(jié)信息,輸出示例如下:
圖片
.debug_abbrev節(jié)中的DIE和.debug_info節(jié)中的DIE互為抽象和實(shí)現(xiàn)的關(guān)系。一個(gè)比較形象的比喻,.debug_abbrev節(jié)中的DIE如同C++中的類,.debug_info節(jié)中的DIE如同C++中的對(duì)象。類是數(shù)據(jù)類型,對(duì)象為類的具體實(shí)現(xiàn)。
3.4 .debug_line節(jié)
.debug_line節(jié)建立了機(jī)器指令地址與源代碼行號(hào)之間的映射關(guān)系,是實(shí)現(xiàn)源碼級(jí)調(diào)試的核心基礎(chǔ)。
.debug_line節(jié)的核心功能是:
- 提供源代碼行號(hào)與機(jī)器指令地址的精確映射。
- 使調(diào)試器能夠?qū)⒊绦蛴?jì)數(shù)器(PC)值轉(zhuǎn)換為源代碼位置。
- 支持設(shè)置斷點(diǎn)、單步執(zhí)行和堆棧跟蹤等基本調(diào)試功能。
通過(guò)命令:readelf -wl 可執(zhí)行文件,可查看.debug_line節(jié)信息,輸出示例如下:
圖片
注意,.debug_line節(jié)的規(guī)則比較復(fù)雜,我們可以跳過(guò)規(guī)則學(xué)習(xí),先了解.debug_line的作用。.debug_line節(jié)最終將會(huì)呈現(xiàn)一個(gè)指令地址和源文件行號(hào)的映射表,見(jiàn)表2。
表2 .debug_line指令地址和行號(hào)映射表
3.5 .debug_str節(jié)
.debug_str節(jié)是一個(gè)字符串池,它存儲(chǔ)了調(diào)試過(guò)程中所需的所有字符串?dāng)?shù)據(jù),包括:函數(shù)名、變量名、類型名、編譯器信息、其他文本描述。
通過(guò)命令:readelf -ws 可執(zhí)行文件,可查看.debug_str節(jié)信息,輸出示例如下:
圖片
從輸出示例可以看到.debug_str包含一些我們比較常見(jiàn)的字符串,如果我們想使用這些字符串,只需要從調(diào)試文件指定的位置讀取這些字符串即可。
3.6 .debug_line_str節(jié)
.debug_line_str節(jié)同樣是一個(gè)字符串池。這些字符串通常包括文件名、目錄路徑等,它們被.debug_line節(jié)中的行號(hào)程序引用。
輸出示例如下:
圖片
.debug_line_str和.debug_str的使用方法類似。
4.調(diào)試信息分離
調(diào)試信息分離是指將調(diào)試信息從可執(zhí)行文件中分離出來(lái),存儲(chǔ)在單獨(dú)的文件中。將調(diào)試信息存儲(chǔ)在單獨(dú)的文件有幾個(gè)優(yōu)點(diǎn):
- 減小可執(zhí)行文件大小:通過(guò)將調(diào)試信息分離到單獨(dú)的文件中,可執(zhí)行文件的大小可以顯著減小。
- 提高安全性:分離的調(diào)試信息文件可以存儲(chǔ)在安全的環(huán)境中,從而防止調(diào)試信息泄露。
- 提高性能:較小的可執(zhí)行文件可以更快地加載和運(yùn)行,從而提高應(yīng)用程序的性能。
4.1 實(shí)現(xiàn)原理
為了讓大家更好的理解調(diào)試信息分離的過(guò)程,我們通過(guò)一張圖描述該過(guò)程,如圖2所示。

圖2 調(diào)試信息分離
調(diào)試信息分離具體步驟如下:
- 步驟1:編譯帶調(diào)試信息的可執(zhí)行程序。
gcc -g編譯帶調(diào)試信息的可執(zhí)行文件,以test程序?yàn)槔钊缦拢?/span>
gcc -g -o test test.c- 步驟2:生成單獨(dú)的調(diào)試信息文件。
使用objcopy命令的--only-keep-debug選項(xiàng),將調(diào)試信息提取到一個(gè)單獨(dú)的文件中,命令如下:
objcopy --only-keep-debug test test.debugtest.debug文件為單獨(dú)調(diào)試文件。
- 步驟3:去除可執(zhí)行文件中的調(diào)試信息。
使用objcopy命令的--strip-debug選項(xiàng),移除可執(zhí)行文件調(diào)試信息,
objcopy --strip-debug test執(zhí)行完該命令,可執(zhí)行文件中的.debug_*節(jié)將全部被刪除。
- 步驟4:添加調(diào)試信息文件鏈接。
使用objcopy命令的--add-gnu-debuglink選項(xiàng),將調(diào)試信息文件的路徑添加到可執(zhí)行文件中。gdb調(diào)試可執(zhí)行文件時(shí),就能夠自動(dòng)查找到調(diào)試信息文件。
objcopy --add-gnu-debuglink=test.debug test執(zhí)行完該命令,可執(zhí)行文件中將添加一個(gè).gnu_debuglink節(jié),其中包含調(diào)試信息文件的路徑。通過(guò)以下命令可以查看.gnu_debuglink節(jié)中的內(nèi)容:
readelf -p .gnu_debuglink test輸出示例如下:
圖片
4.2 gdb調(diào)試單獨(dú)調(diào)試文件
當(dāng)我們按照上述步驟生成了單獨(dú)的調(diào)試文件后,我們既能夠獲得一個(gè)輕量級(jí)的可執(zhí)行程序,又能夠有一個(gè)完整的調(diào)試文件。當(dāng)程序在實(shí)際環(huán)境運(yùn)行出現(xiàn)問(wèn)題時(shí),我們能夠通過(guò)單獨(dú)的調(diào)試文件排查問(wèn)題。
gdb調(diào)試單獨(dú)調(diào)試文件分為:手動(dòng)添加調(diào)試文件和自動(dòng)添加調(diào)試文件。
手動(dòng)添加通過(guò)gdb add-symbol-file命令完成,輸出示例如下:
(gdb) add-symbol-file test.debug
add symbol table from file "test.debug"
(y or n) y
Reading symbols from test.debug...自動(dòng)添加的方式需要在可執(zhí)行程序中添加調(diào)試信息文件鏈接(參考調(diào)試信息分離步驟4)。注意,可執(zhí)行文件和調(diào)試文件需要在同一個(gè)目錄。輸出示例如下:
圖片
























