Linux下C語言程式的除錯
1.編譯時新增除錯資訊
使用gcc -g -o [生成檔名] [原始檔名]來編譯生成一個帶除錯資訊的可執行檔案 例如:gcc -g -o test.debug test.c
加上-g選項以後,gcc在編譯是會做以下額外的操作:
-
建立符號表,符號表包含了程式中使用的變數名稱的列表。
-
關閉所有的優化機制,以便程式執行過程中嚴格按照原來的C程式碼進行
生成的可執行檔案可以用readelf[^1] 命令來顯示檔案的具體資訊。
2.使用除錯命令進行除錯
最常用的是gdb偵錯程式
gdb偵錯程式
gdb命令包含在GNU的gcc開發套件中,是功能強大的程式偵錯程式。GDB中的命令固然很多,但我們只需掌握其中十個左右的命令,就大致可以完成日常的基本的程式除錯工作。
語法
gdb [選項] [引數]
- -cd:設定工作目錄
- -q:安靜模式,不列印介紹資訊和版本資訊
- -d:新增檔案查詢路徑
- -x:從指定檔案中執行GDB指令
- -s:設定讀取的符號表檔案
一般可以直接使用gdb命令進入gdb命令列模式,然後用file [可執行程式] [引數列表] 的方式來開啟需要除錯的程式,因為這樣可以帶引數啟動程式,而直接gdb [檔名] 將引數帶不進去。
gdb模式下的命令
- file <檔名> 載入被除錯的可執行程式檔案。 因為一般都在被除錯程式所在目錄下執行GDB,因而文字名不需要帶路徑。 示例:(gdb) file gdb-sample
- r Run的簡寫,執行被除錯的程式。 如果此前沒有下過斷點,則執行完整個程式;如果有斷點,則程式暫停在第一個可用斷點處。 示例:(gdb) r
- c Continue的簡寫,繼續執行被除錯程式,直至下一個斷點或程式結束。 (gdb) c
- b <行號>
- b <函式名稱>
- b *<函式名稱>
- b <程式碼地址> d [編號] b: Breakpoint的簡寫,設定斷點。兩可以使用“行號”“函式名稱”“執行地址”等方式指定斷點位置。 其中在函式名稱前面加“”符號表示將斷點設定在“由編譯器生成的prolog程式碼處”。如果不瞭解彙編,可以不予理會此用法。 d: Delete breakpoint的簡寫,刪除指定編號的某個斷點,或刪除所有斷點。斷點編號從1開始遞增。 示例:(gdb) b 8 示例:(gdb) b main 示例:(gdb) b *main 示例:(gdb) b *0x804835c (gdb) d
- s, n s: 執行一行源程式程式碼,如果此行程式碼中有函式呼叫,則進入該函式; n: 執行一行源程式程式碼,此行程式碼中的函式呼叫也一併執行。 s 相當於其它偵錯程式中的“Step Into (單步跟蹤進入)”; n 相當於其它偵錯程式中的“Step Over (單步跟蹤)”。 這兩個命令必須在有原始碼除錯資訊的情況下才可以使用(GCC編譯時使用“-g”引數)。 示例:(gdb) s 示例:(gdb) n
- si, ni si命令類似於s命令,ni命令類似於n命令。所不同的是,這兩個命令(si/ni)所針對的是彙編指令,而s/n針對的是原始碼。 示例:(gdb) si 示例:(gdb) ni
- p <變數名稱> Print的簡寫,顯示指定變數(臨時變數或全域性變數)的值。 示例:(gdb) p i 示例:(gdb) p nGlobalVar display … undisplay <編號> display,設定程式中斷後欲顯示的資料及其格式。 例如,如果希望每次程式中斷後可以看到即將被執行的下一條彙編指令,可以使用命令 “display /i $pc” 其中 $pc 代表當前彙編指令,/i 表示以十六進行顯示。當需要關心彙編程式碼時,此命令相當有用。 undispaly,取消先前的display設定,編號從1開始遞增。 示例:(gdb) display /i $pc (gdb) undisplay 1
- i info的簡寫,用於顯示各類資訊,詳情請查閱“help i”。 示例:(gdb) i r
- q Quit的簡寫,退出GDB除錯環境。 示例:(gdb) q
- help [命令名稱] GDB幫助命令,提供對GDB名種命令的解釋說明。 如果指定了“命令名稱”引數,則顯示該命令的詳細說明;如果沒有指定引數,則分類顯示所有GDB命令,供使用者進一步瀏覽和查詢。 示例:(gdb) help
[^1]
readelf命令
readelf命令用來顯示一個或者多個elf格式的目標檔案的資訊,可以通過它的選項來控制顯示哪些資訊。這裡的elf-file(s)就表示那些被檢查的檔案。可以支援32位,64位的elf格式檔案,也支援包含elf檔案的文件(這裡一般指的是使用ar命令將一些elf檔案打包之後生成的例如lib*.a之類的“靜態庫”檔案)。
這個程式和objdump提供的功能類似,但是它顯示的資訊更為具體,並且它不依賴BFD庫(BFD庫是一個GNU專案,它的目標就是希望通過一種統一的介面來處理不同的目標檔案),所以即使BFD庫有什麼bug存在的話也不會影響到readelf程式。
執行readelf的時候,除了-v和-H之外,其它的選項必須有一個被指定。
ELF檔案型別
- 可重定位檔案:使用者和其他目標檔案一起建立可執行檔案或者共享目標檔案,例如lib*.a檔案。
- 可執行檔案:用於生成程序映像,載入記憶體執行,例如編譯好的可執行檔案a.out。
- 共享目標檔案:用於和其他共享目標檔案或者可重定位檔案一起生成elf目標檔案或者和執行檔案一起建立程序映像,例如lib*.so檔案。
ELF檔案作用
ELF檔案參與程式的連線(建立一個程式)和程式的執行(執行一個程式),所以可以從不同的角度來看待elf格式的檔案:
- 如果用於編譯和連結(可重定位檔案),則編譯器和連結器將把elf檔案看作是節頭表描述的節的集合,程式頭表可選。
- 如果用於載入執行(可執行檔案),則載入器則將把elf檔案看作是程式頭表描述的段的集合,一個段可能包含多個節,節頭表可選。 如果是共享檔案,則兩者都含有。
ELF檔案總體組成
elf檔案頭描述elf檔案的總體資訊。包括:系統相關,型別相關,載入相關,連結相關。
- 系統相關表示:elf檔案標識的魔術數,以及硬體和平臺等相關資訊,增加了elf檔案的移植性,使交叉編譯成為可能。
- 型別相關就是前面說的那個型別。
- 載入相關:包括程式頭表相關資訊。
- 連結相關:節頭表相關資訊。
選項
-
-a –all 顯示全部資訊,等價於 -h -l -S -s -r -d -V -A -I.
-
-h –file-header 顯示elf檔案開始的檔案頭資訊.
-
-l –program-headers –segments 顯示程式頭(段頭)資訊(如果有的話)。
-
-S –section-headers –sections 顯示節頭資訊(如果有的話)。
-
-g –section-groups 顯示節組資訊(如果有的話)。
-
-t –section-details 顯示節的詳細資訊(-S的)。
-
-s –syms –symbols 顯示符號表段中的項(如果有的話)。
-
-e –headers 顯示全部頭資訊,等價於: -h -l -S
-
-n –notes 顯示note段(核心註釋)的資訊。
-
-r –relocs 顯示可重定位段的資訊。
-
-u –unwind 顯示unwind段資訊。當前只支援IA64 ELF的unwind段資訊。
-
-d –dynamic 顯示動態段的資訊。
-
-V –version-info 顯示版本段的資訊。
-
-A –arch-specific 顯示CPU構架資訊。
-
-D –use-dynamic 使用動態段中的符號表顯示符號,而不是使用符號段。
-
-x –hex-dump= 以16進位制方式顯示指定段內內容。number指定段表中段的索引,或字串指定檔案中的段名。
-
-w[liaprmfFsoR] or –debug-dump[=line,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges] 顯示除錯段中指定的內容。
-
-I –histogram 顯示符號的時候,顯示bucket list長度的柱狀圖。
-
-v –version 顯示readelf的版本資訊。
-
-H –help 顯示readelf所支援的命令列選項。
-
-W –wide 寬行輸出。
-
@file 可以將選項集中到一個檔案中,然後使用這個@file選項載入。