1. 程式人生 > >Linux下C語言程式的除錯

Linux下C語言程式的除錯

1.編譯時新增除錯資訊

使用gcc -g -o [生成檔名] [原始檔名]來編譯生成一個帶除錯資訊的可執行檔案 例如:gcc -g -o test.debug test.c

加上-g選項以後,gcc在編譯是會做以下額外的操作:

  1. 建立符號表,符號表包含了程式中使用的變數名稱的列表。

  2. 關閉所有的優化機制,以便程式執行過程中嚴格按照原來的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選項載入。