GDB程序啟動和斷點設置
前面章節介紹了如何啟動GDB調試器,本節介紹如何在 GDB 調試器中啟動(運行)程序,啟動程序過程中的一些注意事項 以及借助 GDB 調試器在程序中的某個地方設置斷點。
程序啟動
根據不同場景的需要,GDB 調試器提供了多種方式來啟動目標程序,其中最常用的就是 run 指令,其次為 start 指令。也就是說,run 和 start 指令都可以用來在 GDB 調試器中啟動程序,它們之間的區別是:
(1)、默認情況下,run 指令會一直執行程序,直到執行結束。如果程序中手動設置有斷點,則 run 指令會執行程序至第一個斷點處;
(2)、start 指令會執行程序至 main() 主函數的起始位置,即在 main() 函數的第一行語句處停止執行。
不僅如此,在進行 run 或者 start 指令啟動目標程序之前,還可能需要做一些必要的准備工作,大致包括以下幾個方面:
(1)、如果啟動 GDB 調試器時未指定要調試的目標程序,或者由於各種原因 GDB 調試器並為找到所指定的目標程序,這種情況下就需要再次手動指定;
(2)、有些 C 或者 C++ 程序的執行,需要接收一些參數(程序中用 argc 和 argv[] 接收);
(3)、目標程序在執行過程中,可能需要臨時設置 PATH 環境變量;
(4)、默認情況下,GDB 調試器將啟動時所在的目錄作為工作目錄,但很多情況下,該目錄並不符合要求,需要在啟動程序手動為 GDB 調試器指定工作目錄。
(5)、默認情況下,GDB 調試器啟動程序后,會接收鍵盤臨時輸入的數據,並將執行結果會打印在屏幕上。但 GDB 調試器允許對執行程序的輸入和輸出進行重定向,使其從文件或其它終端接收輸入,或者將執行結果輸出到文件或其它終端。
示例程序:
#include<stdio.h> int main(int argc,char* argv[]) { FILE * fp; if((fp = fopen(argv[1],"r")) == NULL){ printf("file open fail"); } else{ printf("file open true"); } return 0; }
命令行打開GDB后,是位於當前目錄下,假設我們在root的家目錄啟動gdb。則在執行 main 之前,有以下幾項要做:
1) 首先,對於已啟動的 GDB 調試器,我們可以先通過 l (小寫的 L)指令驗證其是否已找到指定的目標程序文件:

可以看到,對於找不到目標程序文件的 GDB 調試器,l 指令的執行結果顯示“無法加載符號表”。這種情況下,我們就必須手動為其指定要調試的目標程序,例如:

可以看到,通過借助 file 命令,則無需重啟 GDB 調試器也能指定要調試的目標程序文件。
2) 通過分析 main.c 中程序的邏輯不難發現,要想其正確執行,必須在執行程序的同時給它傳遞一個目標文件的文件名。總的來說,為 GDB 調試器指定的目標程序傳遞參數,常用的方法有 3 種:
(1)、啟動 GDB 調試器時,可以在指定目標調試程序的同時,使用 --args 選項指定需要傳遞給該程序的數據。仍以 main程序為例:
[root@all c]# gdb -args main a.txt
整個指令的意思是:啟動 GDB 調試器調試 main.exe 程序,並為其傳遞 "a.txt" 這個字符串(其會被 argv[] 字符串數組接收)。
(2)、GDB 調試器啟動后,可以借助 set args 命令指定目標調試程序啟動所需要的數據。仍以 main 為例:
(gdb) set args a.txt
該命令表示將 "a.txt" 傳遞給將要調試的目標程序。
(3)、還可以使用 run 或者 start 啟動目標程序時,指定其所需要的數據。例如:
(gdb) run a.txt
(gdb) start a.txt3)
3)默認情況下,GDB 調試器的工作目錄為啟動時所使用的目錄。當然,GDB 調試器提供有修改工作目錄的指令,即 cd 指令。例如,將 GDB 調試器的工作目錄修改為 /root/c,則執行指令為:
(gdb) cd /root/c
4) 某些場景中,目標調試程序的執行還需要臨時修改 PATH 環境變量,此時就可以借助 path 指令,例如:
(gdb) path /temp/demo Executable and object file path: /root/c:/usr/local/sbin:/usr/local/bin...
此修改方式只是臨時的,退出 GDB 調試后會失效。
5) 默認情況下,GDB 調試的程序會接收 set args 等方式指定的參數,同時會將輸出結果打印到屏幕上。而通過對輸入、輸出重定向,可以令調試程序接收指定文件或者終端提供的數據,也可以將執行結果輸出到文件或者某個終端上。
例如,將 main 文件的執行結果輸出到 a.txt 文件中,執行如下命令:
(gdb) run > a.txt
在 GDB 調試的工作目錄下就會生成一個 a.txt 文件,其中存儲的即為 main 的執行結果。
總的來說,只有將調試程序所需的運行環境搭建好后,才能使用 run 或者 start 命令開始調試。如下是一個完整的實例,演示了 GDB 調試 mian之前所做的准備工作[root@all c]# pwd <--顯示當前工作路徑
[root@all c]# ls <-- 顯示當前路徑下的文件 a.txt main.c main.exe [root@all c]# cd ~ <-- 進入 home 目錄 [root@all ~]# gdb -q <-- 開啟 GDB 調試器 (gdb) cd /root/c <-- 修改 GDB 調試器的工作目錄 Working directory /root/c. (gdb) file main <-- 指定要調試的目標文件 Reading symbols from main... (gdb) set args a.txt <-- 指定傳遞的數據 (gdb) run <-- 運行程序 Starting program: /root/c/main a.txt file open true Program exited normally.
設置斷點
在 GDB 調試器中對 C/C++ 程序打斷點,最常用的就是 break 命令,有些場景中還會用到 tbreak 或者 rbreak 命令,本節將對這 3 個命令的功能和用法做詳細的講解。以一下一段C代碼演示斷點的使用:
#include<stdio.h> int main(int argc,char* argv[]) { int num = 1; while(num<100) { num *= 2; } printf("num=%d",num); return 0; }
GDB break命令
break 命令(可以用 b 代替)常用的語法格式有以下 2 種。
1、(gdb) break location // b location 2、(gdb) break ... if cond // b .. if cond
1) 第一種格式中,location 用於指定打斷點的具體位置,其表示方式有多種,如表 1 所示。
| location 的值 | 含 義 |
|---|---|
| linenum | linenum 是一個整數,表示要打斷點處代碼的行號。要知道,程序中各行代碼都有對應的行號,可通過執行 l(小寫的 L)命令看到。 |
| filename:linenum | filename 表示源程序文件名;linenum 為整數,表示具體行數。整體的意思是在指令文件 filename 中的第 linenum 行打斷點。 |
| + offset - offset |
offset 為整數(假設值為 2),+offset 表示以當前程序暫停位置(例如第 4 行)為准,向后數 offset 行處(第 6 行)打斷點;-offset 表示以當前程序暫停位置為准,向前數 offset 行處(第 2 行)打斷點。 |
| function | function 表示程序中包含的函數的函數名,即 break 命令會在該函數內部的開頭位置打斷點,程序會執行到該函數第一行代碼處暫停。 |
| filename:function | filename 表示遠程文件名;function 表示程序中函數的函數名。整體的意思是在指定文件 filename 中 function 函數的開頭位置打斷點。 |
2) 第二種格式中,... 可以是表 1 中所有參數的值,用於指定打斷點的具體位置;cond 為某個表達式。整體的含義為:每次程序執行到 ... 位置時都計算 cond 的值,如果為 True,則程序在該位置暫停;反之,程序繼續執行。
如下演示了以上 2 種打斷點方式的具體用法:
[root@all c]# gdb main -q Reading symbols from /root/c/main...done. (gdb) l 1 #include<stdio.h> 2 int main(int argc,char* argv[]) 3 { 4 int num = 1; 5 while(num<100) 6 { 7 num *= 2; 8 } 9 printf("num=%d",num); 10 return 0; (gdb) 11 } (gdb) b 4 # 程序第4行打斷點 Breakpoint 1 at 0x4004d3: file main.c, line 4. (gdb) r # 運行程序,到第4行暫停 Starting program: /root/c/main Breakpoint 1, main (argc=1, argv=0x7fffffffe278) at main.c:4 4 int num = 1; Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64 (gdb) b +1 # 在第4行的基礎上,在第5行代碼處打斷點 Breakpoint 2 at 0x4004da: file main.c, line 5. (gdb) c # 繼續執行程序,到第5行暫停 Continuing. Breakpoint 2, main (argc=1, argv=0x7fffffffe278) at main.c:5 5 while(num<100) (gdb) b 7 if num>10 # 如果 num > 10 ,在第7行打斷點 Breakpoint 3 at 0x4004dc: file main.c, line 7. (gdb) c # 繼續執行 Continuing. Breakpoint 3, main (argc=1, argv=0x7fffffffe278) at main.c:7 7 num *= 2; # 程序在第7行暫停 (gdb) p num # p 命令查看 num 的當前值 $1 = 16
GDB tbreak命令
tbreak 命令可以看到是 break 命令的另一個版本,tbreak 和 break 命令的用法和功能都非常相似,唯一的不同在於,使用 tbreak 命令打的斷點僅會作用 1 次,即使程序暫停之后,該斷點就會自動消失。
tbreak 命令的使用格式和 break 完全相同,有以下 2 種:
1、(gdb) tbreak location 2、(gdb) tbreak ... if cond
仍以 main 為例,如下演示了 tbreak 命令的用法:
[root@all c]# gdb main -q Reading symbols from /root/c/main...done. (gdb) tbreak 7 if num>10 Temporary breakpoint 1 at 0x4004dc: file main.c, line 7. (gdb) r Starting program: /root/c/main Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe278) at main.c:7 7 num *= 2; Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64 (gdb) p num $1 = 16 (gdb) c Continuing. num=128 Program exited normally. (gdb)
可以看到,自num=16開始,后續循環過程中 num 的值始終大於 10,則num>10表達式的值永遠為 True,理應在第 7 行暫停多次。但由於打斷點采用的是 tbreak 命令,因此斷點的作用只起 1 次。
GDB rbreak命令
和 break 和 tbreak 命令不同,rbreak 命令的作用對象是 C、C++ 程序中的函數,它會在指定函數的開頭位置打斷點。
tbreak 命令的使用語法格式為:
(gdb) tbreak regex
其中 regex 為一個正則表達式,程序中函數的函數名只要滿足 regex 條件,tbreak 命令就會其內部的開頭位置打斷點。tbreak 命令打的斷點和 break 命令打斷點的效果是一樣的,會一直存在,不會自動消失。
這里我們對 main.c 源文件的程序做如下修改:
(gdb) l <-- 顯示源碼 1 #include<stdio.h> 2 void rb_one(){ 3 printf("rb_one\n"); 4 } 5 void rb_second(){ 6 printf("rb_second"); 7 } 8 int main(int argc,char* argv[]) 9 { 10 rb_one(); (gdb) 11 rb_second(); 12 return 0; 13 } (gdb) rbreak rb_* <--匹配所有以 rb_ 開頭的函數 Breakpoint 1 at 0x1169: file main.c, line 2. void rb_one(); Breakpoint 2 at 0x1180: file main.c, line 5. void rb_second(); (gdb) r Starting program: /home/ubuntu64/demo/main.exe Breakpoint 1, rb_one () at main.c:2 2 void rb_one(){ (gdb) c Continuing. rb_one Breakpoint 2, rb_second () at main.c:5 5 void rb_second(){ (gdb) c Continuing. rb_second[Inferior 1 (process 7882) exited normally] (gdb)
可以看到,通過執行rbreak rb_*指令,找到了程序中所有以 tb_* 開頭的函數,並在這些函數內部的開頭位置打上了斷點(如上所示,分別為第 2 行和第 5 行)。
