程序調試的基本思想是“分析現象->假設錯誤原因->產生新的現象去驗證假設”這樣一個循環過程,根據現象如何假設錯誤原因,以及如何設計新的現象去驗證假設,需要非常嚴密的分析和思考。程序中除了一目了然的Bug之外都需要一定的調試手段來分析到底錯在哪,到目前為止自己使用過的調試手段只有一種:
根據程序執行時的出錯現象假設錯誤原因,然后在代碼中適當的位置插入printf(驅動使用printk函數),執行程序並分析打印結果,如果結果和心里預期的一樣,就基本上證明了自己假設的錯誤原因,就可以動手修正Bug了,如果結果和預期的不一樣,就根據結果做進一步的假設和分析。
printf這種方法對於小程序簡單有效,面對一些較大的程序時難免有些力不從心。GDB是Unix/Linux下非常強大的程序調試工具,最近對其基本使用方法進行學習,做一下總結。
1、gdb工具基本使用
#include <stdio.h> int add(int low,int high) { int i,sum; for(i=low;i<=high;i++) sum = sum + i; return sum; } int main(int argc,char **argv) { int result[100]; result[0] = add(1,10); result[1] = add(1,100); printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]); return 0; }
將這段程序利用gcc gdb.c -o gdb
進行編譯,運行結果如下:
[peterwang@localhost gdb]$ gcc gdb.c -o gdb [peterwang@localhost gdb]$ ./gdb result[0] is 11413250 result[1] is 11418300 [peterwang@localhost gdb]$
很明顯打印出來的結果是錯誤的,程序完成的功能是計算1加到10和1加到100的和,打印出來的結果應該是55和5050(這個例子只是為了展示gdb的調試步驟,程序本身的問題很輕松就可以發現),下面利用gdb對程序進行調試。
利用gdb調試,需要在gcc編譯過程中加上-g
選項,這樣編譯生成的可執行文件才可以利用gdb進行源碼調試。
[peterwang@localhost gdb]$ gdb gdb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done. (gdb)
-g
選項的作用是在可執行文件中加入源代碼的信息,比如可執行文件中第幾條機器指令對應源代碼的第幾行,但並不是把整個源文件嵌入到可執行文件中,所以在調試時必須保證gdb能找到源文件。如果把當前的gdb.c改名為g.c或者將gdb.c移動到其他地方,則gdb無法進行調試。
[peterwang@localhost gdb]$ mv gdb.c g.c [peterwang@localhost gdb]$ gdb gdb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done. (gdb) start Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18. Starting program: /home/peterwang/TestProgram/gdb/gdb Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18 18 gdb.c: No such file or directory. in gdb.c (gdb)
gdb提供一個類似Shell的命令行環境,上面的(gdb)就是提示符,在這個提示符下輸入help
可以查看命令的類別:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files internals -- Maintenance commands obscure -- Obscure features running -- Running the program stack -- Examining the stack status -- Status inquiries support -- Support facilities tracepoints -- Tracing of program execution without stopping the program user-defined -- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Command name abbreviations are allowed if unambiguous. (gdb)
可以看出來gdb的命令是分類的,利用help 類別(比如 help data)
可以進一步查看data類別下的命令幫助。
我們可以利用list(簡寫為l)
命令列出當前程序的代碼,默認列出10行,如果想列出更多,可以再次輸入list,也可以直接敲擊回車鍵,gdb有個很好用的功能,直接敲擊回車鍵表示執行上一條命令。
(gdb) list 9 { 10 int i,sum; 11 for(i=low;i<=high;i++) 12 sum = sum + i; 13 return sum; 14 } 15 int main(int argc,char **argv) 16 { 17 int result[100]; 18 result[0] = add(1,10); (gdb) 19 result[1] = add(1,100); 20 printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]); 21 return 0; 22 } (gdb)
gdb調試工具需要首先用start
命令開始執行程序,
(gdb) start Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18. Starting program: /home/peterwang/TestProgram/gdb/gdb Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10); (gdb)
gdb停在main函數中變量定義之后的第一條語句處等待我們發命令, gdb列出的這條語句是即將執行
的下一條語句。退出gdb調試環境可以利用quit
命令
(gdb) quit
A debugging session is active. Inferior 1 [process 3878] will be killed. Quit anyway? (y or n) y [peterwang@localhost gdb]$
2、單步執行和跟蹤函數調用
根據上一小節的介紹,我們已經知道如何進入gdb調試工具以及基本知識,本小節將通過gdb調試工具找出上一小節函數輸出錯誤的原因。
利用start
命令開始gdb調試,我們可以看到程序停在了main函數的result[0] = add(1,10);
這一行:
[peterwang@localhost gdb]$ gdb gdb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/peterwang/TestProgram/gdb/gdb...done. (gdb) start Temporary breakpoint 1 at 0x80483f5: file gdb.c, line 18. Starting program: /home/peterwang/TestProgram/gdb/gdb Temporary breakpoint 1, main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10);
下面我們利用next(簡寫為n)
命令控制程序向下執行:
(gdb) n
19 result[1] = add(1,100); (gdb) 20 printf("result[0] is %d\nresult[1] is %d \n",result[0],result[1]); (gdb) result[0] is 1218306 result[1] is 1223356 21 return 0; (gdb) 22 } (gdb) __libc_start_main (main=0x80483e9 <main>, argc=1, ubp_av=0xbffff324, init=0x8048460 <__libc_csu_init>, fini=0x8048450 <__libc_csu_fini>, rtld_fini=0x11f4c0 <_dl_fini>, stack_end=0xbffff31c) at libc-start.c:258 258 exit (result); (gdb) Program exited normally.
雖然程序正常打印,並且顯示正常退出,可以並沒有找到程序的問題所在。重新運行start
命令,利用step(簡寫s)
跳入add(1,10)函數中進行調試,在add()函數中利用backtrace(簡寫為bt
命令查看函數調用的棧幀:
(gdb) start
Temporary breakpoint 2 at 0x80483f5: file gdb.c, line 18.
Starting program: /home/peterwang/TestProgram/gdb/gdb
Temporary breakpoint 2, main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10); (gdb) s add (low=1, high=10) at gdb.c:11 11 for(i=low;i<=high;i++) (gdb) bt #0 add (low=1, high=10) at gdb.c:11 #1 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18 (gdb)
可以看出函數add()被main()函數調用,main傳進來的參數是low=1,high=10。main函數的棧幀編號為1, add_range的棧幀編號為0。現在可以利用info(簡寫為i)
查看add()函數中局部變量的值:
(gdb) i locals
i = 1224252
sum = 1218251
可以看到當前add()函數中變量i
和變量sum
為很大的數,看到這里基本就可以猜出程序錯誤是由於i
和sum
未進行初始化導致。
如果想查看main函數當前局部變量的值也可以做到,先用frame(簡寫為f)
選擇1號棧幀然后再查看局部變量:
(gdb) frame 1 #1 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10); (gdb) i locals result = {136, 1241028, 0, 1114836, 1114932, 7, 0, 1114112, 0, 1152850, 1325276, 134513214, -1207961936, -1208025086, 1177933, 134513164, 1244128, 1241028, 1264592, 1, -1073745444, 1154825, 61145, 1241028, -1073745288, 1128753, 1242396, 1243824, 0, 0, 0, 0, 0, 0, 0, 0, -1207961984, 0, 1245544, 1265112, 1223968, -1073745500, 1300312, 14, 129100401, -1207961984, -163754450, 0, 3, 1243384, 0, 0, 1, 2200, -1207961936, -1207962672, 134513196, 1302392, 134513100, 1, 1241028, -1073745200, 1243824, -1073745244, 1155338, -1073745260, 134513100, -1073745272, 1243732, 0, -1207961936, 1, 0, 1, 1243384, 1, 13238272, 0, 15773951, 1, 194, 16372, 2892252, 0, -1073745200, 134518456, -1073745336, 134513344, 6291456, 134518456, -1073745288, 134513785, 2892252, 134513196, 2903264, 2899956, 134513760, 134513424, 134513771, 2899956}
由於result[]數組也沒有進行初始化操作,數組中的數據都是雜亂無章的。繼續利用s
或者n
命令往下走,然后用print(簡寫為p)打印出變量sum的值:
(gdb)
12 sum = sum + i; (gdb) p sum $1 = 1218251 (gdb)
這里sum
值打印已經出錯了,可以利用finish
命令讓程序一直運行到從當前函數返回為止或者利用continue(簡寫為c)
命令運行到程序結束,然后修改源代碼。
(gdb) finish
Run till exit from #0 add (low=1, high=10) at gdb.c:12 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10); Value returned is $2 = 1218306
(gdb) c
Continuing.
result[0] is 1218306 result[1] is 1223356 Program exited normally
也可以利用set var 變量=XX
命令進行變量賦值,運行調試程序,驗證思路正確性。
(gdb) set var sum=0 (gdb) s 11 for(i=low;i<=high;i++) (gdb) 12 sum = sum + i; (gdb) p sum $10 = 1 (gdb) finish Run till exit from #0 add (low=1, high=10) at gdb.c:12 0x08048409 in main (argc=1, argv=0xbffff324) at gdb.c:18 18 result[0] = add(1,10); Value returned is $11 = 55
從運行結果來看,當把sum
變量賦值為0后,add()函數結束時sum
的值為55,符合預期的結果。
3、斷點調試
斷點調試是指自己在程序的某一行設置一個斷點,調試時,程序運行到這一行就會停住,然后可以一步一步往下調試。斷點調試是一種非常有效的調試方法。本小節將通過例子進行斷點調試的學習。
#include <stdio.h> int main(void) { int sum = 0, i = 0; char input[5]; while (1) { scanf("%s", input); for (i = 0; input[i] != '\0'; i++) sum = sum*10 + input[i] - '0'; //字符型'2'-'0'的ASCII碼正好是整形2 printf("input=%d\n", sum); } return 0; }
程序運行結果如下:
[peterwang@localhost gdb]$ ./gdb_breakpoint
123 input=123 123 input=123123 ^C [peterwang@localhost gdb]$
可以看到程序運行第一次輸入結果是正確的,第二次輸入123,卻輸出123123(例子只是單純的進行gdb測試,高手勿噴),利用gcc -g gdb_breakpoint.c -o gdb_breakpoint
編譯該代碼,運行./gdb gdb_breadpoint
並執行start
命令
[peterwang@localhost gdb]$ gdb gdb_breakpoint GNU gdb (GDB) Red Hat Enterprise Linux (7.2-83.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/peterwang/TestProgram/gdb/gdb_breakpoint...done. (gdb) start Temporary breakpoint 1 at 0x804841d: file gdb_breakpoint.c, line 10. Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint Temporary breakpoint 1, main () at gdb_breakpoint.c:10 10 int sum = 0, i = 0; (gdb)
執行next(n)
命令,根據之前的經驗,需要重點懷疑sum
和input數組
的值的變化,可以利用display sum/input
命令時刻觀察值的變化。通過undisplay 編號
進行取消跟蹤顯示。
(gdb) display sum 1: sum = 0 (gdb) display input 2: input = "\b`\203\004\b" (gdb) n 123 14 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 0 (gdb) 15 sum = sum*10 + input[i] - '0'; 2: input = "123\000\b" 1: sum = 0 (gdb) 14 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 1 (gdb) 15 sum = sum*10 + input[i] - '0'; 2: input = "123\000\b" 1: sum = 1 (gdb) 14 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 12 (gdb) 15 sum = sum*10 + input[i] - '0'; 2: input = "123\000\b" 1: sum = 12 (gdb) 14 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 123 (gdb) 16 printf("input=%d\n", sum); 2: input = "123\000\b" 1: sum = 123 (gdb) input=123 18 } 2: input = "123\000\b" 1: sum = 123 (gdb) 123 14 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 123 (gdb)
可以看出第14步第二次輸入數據的時候sum=123
這顯然不符合程序的本意,可以推斷出問題出在sum
變量在每次while(1)
的時候沒有進行賦0操作。問題是找出來了,可是可以看出這樣調試效率不是很高,我們可以利用break命令(簡寫為b)
設置斷點。break
命令可以跟行數也可以跟函數名設置斷點。利用info命令(簡寫為i) breakpoints
查看當前的斷點信息,利用delete breakpoints 斷點編號(info 出來的編號)
進行斷點刪除,delete breakpoints
命令是刪除所有斷點,如果一個斷點我們暫時不想使用可以利用disable breakpoints 斷點編號
直接禁用,利用enbale 斷點編號
啟用斷點。
(gdb) l 5 > Created Time: Tue 12 Apr 2016 05:21:15 PM CST 6 ************************************************************************/ 7 #include <stdio.h> 8 int main(void) 9 { 10 int sum = 0, i = 0; 11 char input[5]; 12 while (1) { 13 printf("請輸入一個6位以下的數字:\n"); 14 scanf("%s", input); (gdb) 15 for (i = 0; input[i] != '\0'; i++) 16 sum = sum*10 + input[i] - '0'; 17 printf("input=%d\n", sum); 18 19 } 20 return 0; 21 } (gdb) b 15 Breakpoint 2 at 0x804847e: file gdb_breakpoint.c, line 15. (gdb) c Continuing. 請輸入一個6位以下的數字: 123 Breakpoint 2, main () at gdb_breakpoint.c:15 15 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 0 (gdb) c Continuing. input=123 請輸入一個6位以下的數字: 123 Breakpoint 2, main () at gdb_breakpoint.c:15 15 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 123 (gdb) (gdb) info breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x0804847e in main at gdb_breakpoint.c:15 breakpoint already hit 2 times (gdb) disable breakpoints 2 (gdb) enable 2 (gdb) delete breakpoints Delete all breakpoints? (y or n) y (gdb)
我們在程序的15行設置斷點,利用continue(c)
命令執行程序,直到斷點處停下,可以看到第二次輸入123的時候,sum
的值為123,按照程序邏輯這時候sum
應該為0,這里也可以判斷出,while(1)
中缺少了sum
的賦0操作。break
命令非常靈活,我們還可以利用條件語句設置斷點,比如break 15 if sum!=0
僅當sum!=0的時候在15行設置斷點,然后利用run(r)
命令從頭進行調試。
gdb) b 15 if sum!=0 Breakpoint 2 at 0x804847e: file gdb_breakpoint.c, line 15. (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint 請輸入一個6位以下的數字: 123 input=123 請輸入一個6位以下的數字: 123 Breakpoint 2, main () at gdb_breakpoint.c:15 15 for (i = 0; input[i] != '\0'; i++) 2: input = "123\000\b" 1: sum = 123
3、觀察點調試
根據上一小節,我們知道在while(1)
中加入sum=0
程序可以得到正確的結果,可是如果scanf
輸入數組越界會是什么情況呢?
[peterwang@localhost gdb]$ ./gdb_breakpoint
請輸入一個5位以下的數字: 123 input=123 請輸入一個5位以下的數字: 12 input=12 請輸入一個5位以下的數字: 12345 input=12345090 請輸入一個5位以下的數字:
可以看到輸出了一個詭異的結果,進入gdb
調試,利用x/7 input
命令查看input
數組值的變化情況,x
代表打印指定存儲單元的內容,7
代表打印7組。
(gdb) start
The program being debugged has been started already. Start it from the beginning? (y or n) y Temporary breakpoint 2 at 0x804844d: file gdb_breakpoint.c, line 10. Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint Temporary breakpoint 2, main () at gdb_breakpoint.c:10 10 int sum = 0, i = 0; (gdb) n 13 sum = 0; (gdb) 14 printf("請輸入一個5位以下的數字:\n"); (gdb) 請輸入一個5位以下的數字: 15 scanf("%s", input); (gdb) 12345 16 for (i = 0; input[i] != '\0'; i++) (gdb) p input $2 = "12345" (gdb) x/7 input 0xbffff253: 0x31 0x32 0x33 0x34 0x35 0x00 0x00 (gdb)
我們知道斷點是當程序執行到某一代碼行時中斷,而觀察點是當程序訪問某個存儲單
元時中斷,如果我們不知道某個存儲單元是在哪里被改動的,這時候觀察點尤其有
用。用watch
命令設置觀察點,跟蹤input[4]后面那個字節(可以用input[5]表示,雖然這是訪問越界),利用 info(i) watchpoints
查詢當前觀察點。
(gdb) start
Temporary breakpoint 1 at 0x804844d: file gdb_breakpoint.c, line 10. Starting program: /home/peterwang/TestProgram/gdb/gdb_breakpoint Temporary breakpoint 1, main () at gdb_breakpoint.c:10 10 int sum = 0, i = 0; (gdb) n 13 sum = 0; (gdb) 14 printf("請輸入一個5位以下的數字:\n"); (gdb) 請輸入一個5位以下的數字: 15 scanf("%s", input); (gdb) 1234 16 for (i = 0; input[i] != '\0'; i++) (gdb) watch input[4] Hardware watchpoint 2: input[4] (gdb) c Continuing. input=1234 請輸入一個5位以下的數字: 12345 Hardware watchpoint 2: input[4] Old value = 0 '\000' New value = 53 '5' 0x00183f74 in _IO_vfscanf_internal (s=0x35, format=0x400 <Address 0x400 out of bounds>, argptr=0x4850000 <Address 0x4850000 out of bounds>, errp=0x8) at vfscanf.c:1031 1031 *str++ = c; (gdb)
4、段錯誤調試
#include <stdio.h> int main(void) { int man = 0; scanf("%d", man); return 0; }
利用命令gcc -g gdb_segdefault.c -o gdb_segdefault
編譯這段程序,運行出現段錯誤提示。
[peterwang@localhost gdb]$ ./gdb_segdefault 123 Segmentation fault (core dumped) [peterwang@localhost gdb]$
進入gdb
調試
(gdb) start
Temporary breakpoint 1 at 0x80483ed: file gdb_segdefault.c, line 10. Starting program: /home/peterwang/TestProgram/gdb/gdb_segdefault Temporary breakpoint 1, main () at gdb_segdefault.c:10 10 int man = 0; (gdb) n 11 scanf("%d", man); (gdb) 123 Program received signal SIGSEGV, Segmentation fault. 0x00182aa5 in _IO_vfscanf_internal (s=0x333231, format=0xffffffff <Address 0xffffffff out of bounds>, argptr=0x0, errp=0xb) at vfscanf.c:1772 1772 *ARG (unsigned int *) = (unsigned int) num.ul; (gdb) bt #0 0x00182aa5 in _IO_vfscanf_internal (s=0x333231, format=0xffffffff <Address 0xffffffff out of bounds>, argptr=0x0, errp=0xb) at vfscanf.c:1772 #1 0x0018f939 in __isoc99_scanf (format=0x80484e4 "%d") at isoc99_scanf.c:37 #2 0x0804840a in main () at gdb_segdefault.c:11 (gdb)
可以看到,gdb
提示Program received signal SIGSEGV, Segmentation fault.
錯誤,利用bt
命令查看_IO_vfscanf_internal
是被__isoc99_scanf
調用,可以確定main
函數中的scanf
出現問題,可以檢查出scanf
函數中忘記加入&
導致段錯誤。
還有一種經常發生的段錯誤,算是一條規律,如果某個函數的局部變量發生訪問越界
,有可能並不立即產生段錯誤,而是在函數返回時產生段錯誤
。
附錄(常用的gdb命令)
一.基本命令
1)進入GDB #gdb test
test是要調試的程序,由gcc test.c -g -o test生成。進入后提示符變為(gdb)
2)查看源碼 (gdb) l
源碼會進行行號提示。如果需要查看在其他文件中定義的函數,在l后加上函數名即可定位到這個函數的定義及查看附近的其他源碼。或者:使用斷點或單步運行,到某個函數處使用s進入這個函數。
3)啟動gdb,並且分屏顯示源代碼 gdb -tui
這樣,使用了'-tui'選項,啟動可以直接將屏幕分成兩個部分,上面顯示源代碼,比用list方便多了。這時候使用上下方向鍵可以查看源代碼,想要命令行使用上下鍵就用[Ctrl]n和[Ctrl]p
4)設置斷點 (gdb) b 6
這樣會在運行到源碼第6行時停止,可以查看變量的值、堆棧情況等;這個行號是gdb的行號。
5)查看斷點處情況 (gdb) info b
可以鍵入"info b"來查看斷點處情況,可以設置多個斷點;
6)運行代碼 (gdb) r
7)顯示變量值 (gdb) p n
在程序暫停時,鍵入"p 變量名"(print)即可;
GDB在顯示變量值時都會在對應值之前加上"$N"標記,它是當前變量值的引用標記,以后若想再次引用此變量,就可以直接寫作"$N",而無需寫冗長的變量名;
8)觀察變量 (gdb) watch n
在某一循環處,往往希望能夠觀察一個變量的變化情況,這時就可以鍵入命令"watch"來觀察變量的變化情況,GDB在"n"設置了觀察點;
9)單步運行(不進入函數) (gdb) n
10)單步運行(進入函數) (gdb) s
11)程序繼續運行 (gdb) c
使程序繼續往下運行,直到再次遇到斷點或程序結束;
12)退出GDB (gdb) q
二.斷點調試
break + 設置斷點的行號 break n 在n行處設置斷點
tbreak + 行號或函數名 tbreak n/func 設置臨時斷點,到達后被自動刪除
break + filename + 行號 break main.c:10 用於在指定文件對應行設置斷點
break + <0x...> break 0x3400a 用於在內存某一位置處暫停
break + 行號 + if + 條件 break 10 if i==3 用於設置條件斷點,在循環中使用非常方便
info breakpoints/watchpoints [n] info break n表示斷點號,查看斷點/觀察點的情況
clear + 要清除的斷點行號 clear 10 用於清除對應行的斷點,要給出斷點的行號,清除時GDB會給出提示
delete + 要清除的斷點編號 delete 3 用於清除斷點和自動顯示的表達式的命令,要給出斷點的編號,清除時GDB不會給出任何提示
disable/enable + 斷點編號 disable 3 讓所設斷點暫時失效/使能,如果要讓多個編號處的斷點失效/使能,可將編號之間用空格隔開
awatch/watch + 變量 awatch/watch i 設置一個觀察點,當變量被讀出或寫入時程序被暫停
rwatch + 變量 rwatch i 設置一個觀察點,當變量被讀出時,程序被暫停
catch 設置捕捉點來補捉程序運行時的一些事件。如:載入共享庫(動態鏈接庫)或是C++的異常
tcatch 只設置一次捕捉點,當程序停住以后,應點被自動刪除
三.數據命令
display +表達式 display a 用於顯示表達式的值,每當程序運行到斷點處都會顯示表達式的值
info display 用於顯示當前所有要顯示值的表達式的情況
delete + display 編號 delete
3 用於刪除一個要顯示值的表達式,被刪除的表達式將不被顯示
disable/enable + display 編號 disable/enable 3 使一個要顯示值的表達式暫時失效/使能
undisplay + display 編號 undisplay 3 用於結束某個表達式值的顯示
whatis + 變量 whatis i 顯示某個表達式的數據類型
print(p) + 變量/表達式 p n 用於打印變量或表達式的值
set 變量 = 變量值 set i = 3 改變程序中某個變量的值
在使用print命令時,可以對變量按指定格式進行輸出,其命令格式為print /變量名 + 格式
其中常用的變量格式:x:十六進制;d:十進制;u:無符號數;o:八進制;c:字符格式;f:浮點數。
四.調試運行環境相關命令
set args set args arg1 arg2 設置運行參數
show args show args 參看運行參數
set width + 數目 set width 70 設置GDB的行寬
cd + 工作目錄 cd ../ 切換工作目錄
run r/run 程序開始執行
step(s) s 進入式(會進入到所調用的子函數中)單步執行,進入函數的前提是,此函數被編譯有debug信息
next(n) n 非進入式(不會進入到所調用的子函數中)單步執行
finish finish 一直運行到函數返回並打印函數返回時的堆棧地址和返回值及參數值等信息
until + 行數 u 3 運行到函數某一行
continue(c) c 執行到下一個斷點或程序結束
return <返回值> return 5 改變程序流程,直接結束當前函數,並將指定值
返回
call + 函數 call func 在當前位置執行所要運行的函數
五.堆棧相關命令
backtrace/bt bt 用來打印棧幀指針,也可以在該命令后加上要打印的棧幀指針的個數,查看程序執行到此時,是經過哪些函數呼叫的程序,程序“調用堆棧”是當前函數之前的所有已調用函數的列表(包括當前函數)。每個函數及其變量都被分配了一個“幀”,最近調用的函數在 0 號幀中(“底部”幀)
frame frame 1 用於打印指定棧幀
info reg info reg 查看寄存器使用情況
info stack info stack 查看堆棧使用情況
up/down up/down 跳到上一層/下一層函數
六.跳轉執行
jump 指定下一條語句的運行點。可以是文件的行號,可以是file:line格式,可以是+num這種偏移量格式。表式着下一條運行語句從哪里開始。相當於改變了PC寄存器內容,堆棧內容並沒有改變,跨函數跳轉容易發生錯誤。
七.信號命令
signal signal SIGXXX 產生XXX信號,如SIGINT。一種速查Linux查詢信號的方法:# kill -l
handle 在GDB中定義一個信號處理。信號可以以SIG開頭或不以SIG開頭,可以用定義一個要處理信號的范圍(如:SIGIO-SIGKILL,表示處理從SIGIO信號到SIGKILL的信號,其中包括SIGIO,SIGIOT,SIGKILL三個信號),也可以使用關鍵字all來標明要處理所有的信號。一旦被調試的程序接收到信號,運行程序馬上會被GDB停住,以供調試。其可以是以下幾種關鍵字的一個或多個:
nostop/stop
當被調試的程序收到信號時,GDB不會停住程序的運行,但會打出消息告訴你收到這種信號/GDB會停住你的程序
print/noprint
當被調試的程序收到信號時,GDB會顯示出一條信息/GDB不會告訴你收到信號的信息
info signals
info handle
可以查看哪些信號被GDB處理,並且可以看到缺省的處理方式
single命令和shell的kill命令不同,系統的kill命令發信號給被調試程序時,是由GDB截獲的,而single命令所發出一信號則是直接發給被調試程序的。
8.運行Shell命令
如(gdb)shell ls來運行ls。
九.更多程序運行選項和調試
1、程序運行參數。
set args 可指定運行時參數。(如:set args 10 20 30 40 50)
show args 命令可以查看設置好的運行參數。
2、運行環境。
path 可設定程序的運行路徑。
show paths 查看程序的運行路徑。
set environment varname [=value] 設置環境變量。如:set env USER=hchen
show environment [varname] 查看環境變量。
3、工作目錄。
cd 相當於shell的cd命令。
pwd 顯示當前的所在目錄。
4、程序的輸入輸出。
info terminal 顯示你程序用到的終端的模式。
使用重定向控制程序輸出。如:run > outfile
tty命令可以指寫輸入輸出的終端設備。如:tty /dev/ttyb
5、調試已運行的程序
兩種方法:
(1)在UNIX下用ps查看正在運行的程序的PID(進程ID),然后用gdb PID格式掛接正在運行的程序。
(2)先用gdb 關聯上源代碼,並進行gdb,在gdb中用attach命令來掛接進程的PID。並用detach來取消掛接的進程。
6、暫停 / 恢復程序運行
當進程被gdb停住時,你可以使用info program來查看程序的是否在運行,進程號,被暫停的原因。 在gdb中,我們可以有以下幾種暫停方式:斷點(BreakPoint)、觀察點(WatchPoint)、捕捉點(CatchPoint)、信號(Signals)、線程停止(Thread Stops),如果要恢復程序運行,可以使用c或是continue命令。
7、線程(Thread Stops)
如果程序是多線程,可以定義斷點是否在所有的線程上,或是在某個特定的線程。
break thread
break thread if ...
linespec指定了斷點設置在的源程序的行號。threadno指定了線程的ID,注意,這個ID是GDB分配的,可以通過“info threads”命令來查看正在運行程序中的線程信息。如果不指定thread 則表示斷點設在所有線程上面。還可以為某線程指定斷點條件。
如: (gdb) break frik.c:13 thread 28 if bartab > lim
當你的程序被GDB停住時,所有的運行線程都會被停住。這方便查看運行程序的總體情況。而在你恢復程序運行時,所有的線程也會被恢復運行。
參考文檔
《Linux C編程一站式學習》
http://www.jb51.net/article/36393.htm
http://blog.chinaunix.net/uid-9525959-id-2001805.html
作者:Manfred_Zone
鏈接:http://www.jianshu.com/p/30ffc01380a0
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。