無論是多麼優秀的程序員,都難以保證自己在編寫代碼時不會出現任何錯誤,因此調試是軟件開發過程中的一個必不可少的組成部分。當程序完成編譯之後,它很可能無法正常運行,或者會徹底崩潰,或者不能實現預期的功能。此時如何通過調試找到問題的症結所在,就變成了擺在開發人員面前最嚴峻的問題。通常說來,軟件項目的規模越大,調試起來就會越困難,越需要一個強大而高效的調試器作為後盾。對於Linux程序員來講,目前可供使用的調試器非常多,GDB(GNU DeBugger)就是其中較為優秀的。 初識GDB GDB是自由軟件基金會(Free Software Foundation,FSF)的軟件工具之一。它的作用是協助程序員找到代碼中的錯誤。如果沒有GDB的幫助,程序員要想跟蹤代碼的執行流程,唯一的辦法就是添加大量的語句來產生特定的輸出。但這一手段本身就可能會引入新的錯誤,從而也就無法對那些導致程序崩潰的錯誤代碼進行分析。GDB的出現減輕了開發人員的負擔,他們可以在程序運行的時候單步跟蹤自己的代碼,或者通過斷點暫時中止程序的執行。此外,他們還能夠隨時察看變量和內存的當前狀態,並監視關鍵的數據結構是如何影響代碼運行的。 調試方法 如果想對程序進行調試,必須先在用GCC編譯源代碼時加上-g選項,以便產生GDB所需要的調試符號信息。例如,debugme.c是一個存在錯誤程序,可以使用如下的命令對其進行編譯,同時產生調試符號: # gcc -g debugme.c -o debugme 如果願意的話,還可以在編譯時使用“-ggdb”選項來生成更多的調試信息。由於這些調試信息中的相當一部分是GDB所特有的,所以生成的代碼將無法在其它調試器中正常調試。對於大多數情況來說,普通的-g選項就足夠了。需要注意的是,GCC雖然允許同時使用-g(調試)和-o(優化)選項,但優化會影響最終生成的代碼,導致程序源代碼和二進制代碼之間的關系變得復雜起來。如果不想為調試制造障礙,建議不要將-g和-o選項一同使用,並且只在程序徹底調試完後才開始進行代碼優化。這樣調試過程將變得相對輕松和愉快。 基本應用 現在可以啟動GDB來調試已經生成的可執行程序debugme,命令如下: # gdb debugme GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb) 如果一切正常,GDB將被啟動並在屏幕上輸出版權信息,但如果使用了-q或--quiet選項則不會顯示它們。啟動GDB時另外一個有用的命令行選項是“-d dirname”,其中dirname是一個目錄名。該目錄名告訴GDB應該到哪裡去尋找源代碼。 一旦出現GDB的命令提示符(gdb),就表明GDB已經准備好接收來自用戶的各種調試命令了。如果想在調試環境下運行這個程序,可以使用GDB提供的“run”命令,而程序在正常運行時所需的各種參數可以作為“run”命令的參數傳入,或者使用單獨的“set args”命令進行設置。如果在執行“run”命令時沒有給出任何參數,GDB將使用上一次“run”或“set args”命令指定的參數。如果想取消上次設置的參數,可以執行不帶任何參數的“set args”命令。下面嘗試在調試器中運行這個程序: (gdb) run …… Program received signal SIGSEGV, Segmentation fault. 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2 最後一行輸出表明程序在調用動態鏈接庫/lib/ld-linux.so.2中的_dl_fini()函數時出現了錯誤,地址是0x4000c6ac。這些對調試是非常重要的線索。另外還有一種信息對調試也很重要,就是錯誤發生時的函數調用層級關系,可以通過執行“backtrace”命令來獲得。在使用GDB調試命令時,用戶可以不必輸入完整的命令名稱,使用任何惟一的縮寫都可以。例如“backtrace”命令就可以縮寫成“back”甚至“BT”。GDB還支持很多常用的Shell命令編輯特征,比如可以像在bash或tcsh中那樣按Tab鍵補齊命令。如果相關命令不惟一的話,則列出所有可能的匹配項。此外鍵盤上的方向鍵可用來翻動歷史命令。 GDB是一個源代碼級的調試器,使用“list”命令可以查看當前調試對象的源代碼。該命令的通用格式為“list [m,n]”,表示顯示從m行開始到n行結束的代碼段,而不帶任何參數的“list”命令將顯示最近10行源代碼。 設置斷點 在調試有問題的代碼時,在某一點停止運行往往很管用。這樣程序運行到此外時會暫時掛起,等待用戶的進一步輸入。GDB允許在幾種不同的代碼結構上設置斷點,包括行號和函數名等,並且還允許設置條件斷點,讓程序只有在滿足一定的條件時才停止執行。要根據行號設置斷點,可以使用“ break linenum”命令。要根據函數名設置斷點,則應該使用“break funcname”命令。 在以上兩種情況中,GDB將在執行指定的行號或進入指定的函數之前停止執行程序。此時可以使用“print”顯示變量的值,或者使用“list”查看將要執行的代碼。對於由多個源文件組成的項目,如果想在執行到非當前源文件的某行或某個函數時停止執行,可以使用如下形式的命令: # break filename:linenum # break filename:funcname 條件斷點允許當一定條件滿足時暫時停止程序的執行。它對於調試來講非常有用。設置條件斷點的正確語法如下: break linenum if eXPr break funcname if expr 其中expr是一個邏輯表達式。當該表達式的值為真時,程序將在該斷點處暫時掛起。例如,下面的命令將在debugme程序的第38行設置一個條件斷點。當程序運行到該行時,如果count的值等於3,就將暫時停止執行: (gdb) break 38 if count==3 設置斷點是調試程序時最常用到的一種手段。它可以中斷程序的運行,給程序員一個單步跟蹤的機會。使用命令“ break main”在main函數上設置斷點可以在程序啟動時就開始進行跟蹤。 接下去使用“continue”命令繼續執行程序,直到遇到下一個斷點。如果在調試時設置了很多斷點,可以隨時使用“info breakpoints”命令來查看設置的斷點。此外,開發人員還可以使用“delete”命令刪除斷點,或者使用“disable”命令來使設置的斷點暫時無效。被設置為無效的斷點在需要的時候可以用“enable”命令使其重新生效。 觀察變量 GDB最有用的特性之一是能夠顯示被調試程序中幾乎任何表達式、變量或數組的類型和值,並且能夠用編寫程序所用的語言打印出任何合法表達式的值。查看數據最簡單的辦法是使用“print”命令,只需在“print”命令後面加上變量表達式,就可以打印出此變量表達式的當前值,示例如下: (gdb) print str $1 = 0x40015360 "Happy new year!\n" 從輸出信息中可以看出,輸入字符串被正確地存儲在了字符指針str所指向的內存緩沖區中。除了給出變量表達式的值外,“print”命令的輸出信息中還包含變量標號($1)和對應的內存地址(0x40015360)。變量標號保存著被檢查數值的歷史記錄,如果此後還想訪問這些值,就可以直接使用別名而不用重新輸入變量表達式。 如果想知道變量的類型,可以使用“whatis”命令,示例如下: (gdb) whatis str type = char * 對於第一次調試別人的代碼,或者面對的是一個異常復雜的系統時,“whatis”命令的作用不容忽視。 單步執行 為了單步跟蹤代碼,可以使用單步跟蹤命令“step”,它每次執行源代碼中的一行。 在GDB中可以使用許多方法來簡化操作,除了可以將“step”命令簡化為“s”之外,還可以直接輸入回車鍵來重復執行前面一條命令。 除了可以用“step”命令來單步運行程序之外,GDB還提供了另外一條單步調試命令“next”。兩者功能非常相似,差別在於如果將要被執行的代碼行中包含函數調用,使用step命令將跟蹤進入函數體內,而使用next命令則不進入函數體內。 在進入下一部分之前,使用下面的命令退出GDB: (gdb) quit 分析核心(core)文件 在程序發生崩潰時,有時可能無法直接運行GDB來進行調試。比如程序可能是在另外一台機器上運行的,或者因為程序對時間比較敏感,所以手動跟蹤調試會產生無法接受的延遲等。遇到這些情況,就只能等到程序運行結束後才能判斷崩潰的原因了。這時需要用到Linux提供的core dump機制。當程序中出現內存操作錯誤時,會發生崩潰並產生核心文件。使用GDB可以對產生的核心文件進行分析,找出程序是在什麼時候崩潰的和在崩潰之前程序都做了些什麼。當然,如果要用GDB來分析核心文件,也必須在編譯時加上-g選項來產生調試符號表。 在分析核心文件之前必須確認系統是否允許生成核心文件,很多Linux發行版在默認時禁止生成核心文件。為了生成核心文件,首先必須執行下面的命令: # ulimit -c unlimited 然後就可以生成核心文件了。這裡仍以前面的debugme程序為例,再次執行下面命令將產生核心文件: # ./debugme Enter a string to count Words:Happy new year! The number of words is 3. Segmentation fault (core dumped) 生成的核心文件名根據系統配置的不同會有所差異。要在GDB中分析核心文件,除了要給出核心文件的文件名外,還必須給出生成該核心文件的可執行程序的名稱,示例如下: #gdb debugme core.547 …… Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. …… 從GDB的輸出信息中可以看出,產生這個核心文件的原因是因為程序收到了序號為11的信號。如果想知道程序