本文介紹Linux下GDB調試器常用的基本命令。
測試均在Ubuntu12.10下完成。
先看看GDB調試的效果圖:
對應的源代碼:
- //插入排序,GDB調試測試代碼
- #include <stdio.h>
-
- int x[10],//存放輸入數(shù)據(jù)
- y[10],//工作空間數(shù)組
- num_inputs,//輸入數(shù)組長度
- num_y = 0;//y中當前元素個數(shù)
-
- //函數(shù)
- //功能:從參數(shù)中提取輸入數(shù)據(jù)
- void get_args(int ac,char **av){
- int i;
- num_inputs = ac - 1;
- for(i = 0;i < num_inputs;++i)
- x[i] = atoi(av[i + 1]);
- }
-
- //函數(shù)
- //功能:向右移動工作數(shù)組中索引jj及其后面的所有元素
- void scoot_over(int jj){
- int k;
- for(k = num_y;k > jj;++k)
- y[k] = y[k - 1];
- }
-
- //函數(shù):
- //功能:插入一個數(shù)據(jù)
- void insert(int new_y){
- int j;
- //工作數(shù)組為空
- if(0 == num_y){
- y[0] = new_y;
- return;
- }
-
- //將新元素插入到適當位置:第一個比new_y大的元素前面
- for(j = 0;j < num_y;++j){
- if(new_y < y[j]){
- //向右移動y[j]后面的所有元素
- scoot_over(j);
- y[j] = new_y;
- return;
- }
- }
- }
-
- //函數(shù):
- //功能:處理所有輸入數(shù)據(jù)
- void process_data(){
- for(num_y = 0;num_y < num_inputs;++num_y)
- insert(x[num_y]);
- }
-
- //函數(shù):
- //打印結果數(shù)據(jù)
- void print_results(){
- int i;
- for(i = 0;i < num_inputs;++i)
- printf("%d\n",y[i]);
- }
-
- int main(int argc,char **argv){
- get_args(argc,argv);
- process_data();
- print_results();
- }
源代碼編譯:
gcc -g -Wall -o insert_sort ins.c
這里面一個必要的步驟是:GCC的-g選項:讓編譯器將符號表(對應于程序的變量和代碼行的內存地址列表)保存在生成的可執(zhí)行文件中。這樣才能在調試會話過程中引用源代碼中的變量名和行號。
一些基本命令
啟動GDB:gdb exeFileName
gdb中中文顯示是亂碼,暫時還不知道怎么支持中文。
聯(lián)機幫助,可以通過help命令訪問文檔:(gdb) help breakpoints
以TUI模式運行GDB:在調用gdb的時候指定-tui選項或者在處于非TUI模式時在GDB中使用Ctrl+X+A組合鍵,可以將終端屏幕分成原文本窗口和控制臺的多個子窗口
退出GDB:quit或者Ctrl+d
可以直接按下回車再次執(zhí)行最近執(zhí)行過的那條命令
執(zhí)行程序:run
查看棧幀:frame num(棧幀編號)
注意棧幀編號規(guī)則,當前正在執(zhí)行的函數(shù)的幀被編號為0,其父幀(即該函數(shù)的調用者的棧幀)被編號為1,父幀的父幀被編號為2,以此類推。
跳到調用棧中的下一個父幀:up
引向相反方向:down
顯示整個棧,即當前存在的所有幀的集合:backtrace
滾動查看代碼:↑等箭頭鍵
輸出當前值:print或者p
瀏覽以前的GDB命令:上一個Ctrl+P、下一個Ctrl+N
注意:如果我們需要修改程序,并且重新編譯,不必重啟GDB。GDB會在運行程序之前自動重新加載新的二分表和新的符號表。
我們在重新編譯程序之前仍然不必退出GDB。
斷點相關操作
設置斷點
GDB設置點的后,該斷點的有效性會持續(xù)到刪除、禁用或退出GDB時。
在某一行設置斷點:break line_number(行數(shù))
break filename:line_number 在源代碼文件filename的line_number處設置斷點。
在某個函數(shù)的入口(第一行可執(zhí)行代碼)處設置斷點:break function(函數(shù)名)。這種方法相對以行數(shù)設置斷點有一個優(yōu)點:如果修改了源代碼,使得函數(shù)不再在這一行處開始,那么如果用函數(shù)名指定斷點,而不是用行號指定,則斷點仍然有效。
注意:break function會在所有具有相同名稱的函數(shù)上設置斷點,注意函數(shù)重載的情況。
break filename: function 在源代碼文件filename的函數(shù)function入口處處設置斷點。
當GDB使用多個斷點中斷一行源代碼時,它只會中斷一次。
GDB實際設置斷點的位置可能和我們請求將斷點放置的位置不同。
比如下列代碼:
- int main(void)
- {
- int i;
- i = 3;
-
- return 0;
- }
如果我們嘗試在函數(shù)main入口處設置斷點,斷點實際會被設置在第4行。因為GDB會認為第三行的機器碼對我們的調試目的來說沒有用處。
條件斷點
將正常斷點轉變?yōu)闂l件斷點:condition break_p_num(斷點編號) cond(條件)
在有效的C條件語句中幾乎可以用任何表達式。也可以用自己的函數(shù)或者庫函數(shù),只要他們被鏈接到程序中。
如:condition 1 num_y == 1
只有當滿足條件num_y == 1時,GDB才會在斷點1處暫停程序的執(zhí)行。
如果以后要刪除條件,但是保持斷點,只需鍵入:cond break_p_num(斷點編號)
用break if可以將break和condition命令組合成一個步驟:
break line_num(行號) if cond(條件)
例子:(gdb) nreak 30 if num_y == 1
注意,條件語句可以用括號()括起來,也可以不用。
臨時斷點:tbreak
與break類似,但是這一命令設置的斷點在首次到達該指定行后就不再有效。
刪除斷點
基于標識符刪除斷點:delete
delete break_point_num(斷點編號),可以在后面指定多個斷點編號連續(xù)刪除幾個斷點
delete:刪除所有斷點
另一種刪除斷點方式:clear
依據(jù)位置刪除斷點,工作方式和對應的break命令相似:
clear function
clear filename:funtion
clear line_number
clear filename:line_number
clear:清除GDB將執(zhí)行的下一個指令處的斷點
禁用與啟用斷點
禁用斷點:disable breakpoint-list(是用空格分隔開的多個斷點標識符)
禁用所有現(xiàn)存斷點:disable
啟用斷點:enable breakpoint-list
在下次引起GDB暫停執(zhí)行后禁用:enable once breakpoint-list
查看所有斷點信息:info break或者是info breakpoints或者簡寫為i b
斷點命令列表
讓GDB在每次到達某個斷點時自動執(zhí)行一組命令,從而自動完成某一任務。
使用commands命令設置命令列表:
commands breakpoint_number
...
commands
...
end
例如以下代碼:
我打算查看傳遞給fibonacci函數(shù)的值以及次序,但是不想在程序中插入printf語句并重新編譯代碼,可以這樣做:
在該函數(shù)入口處設置一個斷點,然后設置命令列表:
printf命令和C中printf函數(shù)類似,只是括號是可選的。
運行結果:
如果嫌GDB輸出太冗長,可以使用silent命令,只需將其添加到設置的命令列表最開始處即可。
恢復執(zhí)行
三種方法
(1)單步執(zhí)行:step(s)、next(n),僅執(zhí)行到代碼的下一行后再次暫停。
注意二者區(qū)別:在函數(shù)調用時step會進入函數(shù),next導致下一次暫停出現(xiàn)在調用函數(shù)之后。next被稱為單步越過(stepping over)函數(shù),而step被稱為單步進入(stepping into)函數(shù)。
next和step都可以采用一個可選的數(shù)值參數(shù),來表示要使用next或step執(zhí)行的額外行數(shù)。
(2)無條件恢復程序的執(zhí)行:continue(c)
直到遇到另一個斷點或者程序結束。
continue可以接受一個可選的數(shù)值參數(shù)n,要求GDB忽略下面n個斷點。
(3)用finish(fin)或until(u)命令恢復。
finish命令指示GDB恢復執(zhí)行,直到恰好在當前幀完成之后為止。
until命令通常用來在不進一步在循環(huán)中暫停(除了循環(huán)中的中間斷點)的情況下完成正在執(zhí)行的循環(huán)。until會執(zhí)行循環(huán)的其余部分(如果遇到斷點,還是會暫停),讓GDB在循環(huán)后面的第一行代碼處暫停。
until命令也可以接受源代碼中的位置作為參數(shù),其用法與break命令同。
比如下列代碼清單swapflaw.c:
- #include <stdio.h>
-
- void swap(int *a,int *b){
- int c = *a;
- *a = *b;
- *b = c;
- }
-
- int main(void){
- int i = 3;
- int j = 5;
-
- printf("i:%d,j:%d\n",i,j);
- swap(&i,&j);
- printf("i:%d,j:%d\n",i,j);
-
- return 0;
- }
如果GDB觸發(fā)了main函數(shù)入口處的一個斷點,那么可以使用下面這些命令方便地使程序一直執(zhí)行到swap()的入口:
until 13
until swap
until swapflaw.c:13
until swapflaw.c:swap
監(jiān)視點
監(jiān)視點是一種特殊類型的斷點,區(qū)別在于監(jiān)視點沒有“住在”某一行源代碼中,而是指示GDB每當某個表達式改變了值就暫停執(zhí)行。
設置監(jiān)視點:watch z(變量名)也可以是復雜的表達式
注意只能監(jiān)視存在且在作用域內的變量。一旦變量不再存在調用棧的任何幀中,GDB會自動刪除監(jiān)視點。
GDB實際上是在變量的內存位置改變值時中斷。
文件清單
(gdb) list line1,line2
查看源代碼
list lineNum 在lineNum的前后源代碼顯示出來
list + 列出當前行的后面代碼行
list - 列出當前行的前面代碼行
list function
set listsize count
設置顯示代碼的行數(shù)
show listsize
顯示打印代碼的行數(shù)
list first,last
顯示從first到last的源代碼行
GDB啟動文件的使用
有時候我們在完成調試前可能需要退出GDB,比如需要離開比較長的一段時間而且不能保持登錄在計算機中。為了不丟失某些信息,可以將斷點和設置的其他命令放在一個GDB啟動文件中,然后每次啟動GDB時會自動加載它們。
GDB啟動文件默認名為.gdbinit??梢詫⒁粋€文件放在主目錄中用于一般用途,另一個文件放在特定項目專用的目錄中。例如,可以將設置斷點的命令放在后一個目錄的啟動文件中,在主目錄的.gdbinit文件中存儲開發(fā)的一些通用的宏。最好不要將編程項目放在主目錄中,因為不能將項目特有的信息放在.gdbinit中。
在調用GDB時可以指定啟動文件,如:
$gdb -command=z x
表示要在可執(zhí)行文件x上運行GDB,首先要從文件z中讀取命令。