GDB調(diào)試工具總結(jié)

程序調(diào)試的基本思想是“分析現(xiàn)象->假設(shè)錯誤原因->產(chǎn)生新的現(xiàn)象去驗證假設(shè)”這樣一個循環(huán)過程,根據(jù)現(xiàn)象如何假設(shè)錯誤原因,以及如何設(shè)計新的現(xiàn)象去驗證假設(shè),需要非常嚴密的分析和思考。程序中除了一目了然的Bug之外都需要一定的調(diào)試手段來分析到底錯在哪,到目前為止自己使用過的調(diào)試手段只有一種:

根據(jù)程序執(zhí)行時的出錯現(xiàn)象假設(shè)錯誤原因,然后在代碼中適當(dāng)?shù)奈恢貌迦雙rintf(驅(qū)動使用printk函數(shù)),執(zhí)行程序并分析打印結(jié)果,如果結(jié)果和心里預(yù)期的一樣,就基本上證明了自己假設(shè)的錯誤原因,就可以動手修正Bug了,如果結(jié)果和預(yù)期的不一樣,就根據(jù)結(jié)果做進一步的假設(shè)和分析。

printf這種方法對于小程序簡單有效,面對一些較大的程序時難免有些力不從心。GDB是Unix/Linux下非常強大的程序調(diào)試工具,最近對其基本使用方法進行學(xué)習(xí),做一下總結(jié)。

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進行編譯,運行結(jié)果如下:

[peterwang@localhost gdb]$ gcc gdb.c -o gdb
[peterwang@localhost gdb]$ ./gdb 
result[0] is 11413250
result[1] is 11418300 
[peterwang@localhost gdb]$

很明顯打印出來的結(jié)果是錯誤的,程序完成的功能是計算1加到10和1加到100的和,打印出來的結(jié)果應(yīng)該是55和5050(這個例子只是為了展示gdb的調(diào)試步驟,程序本身的問題很輕松就可以發(fā)現(xiàn)),下面利用gdb對程序進行調(diào)試。

利用gdb調(diào)試,需要在gcc編譯過程中加上-g選項,這樣編譯生成的可執(zhí)行文件才可以利用gdb進行源碼調(diào)試。

[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 選項的作用是在可執(zhí)行文件中加入源代碼的信息,比如可執(zhí)行文件中第幾條機器指令對應(yīng)源代碼的第幾行,但并不是把整個源文件嵌入到可執(zhí)行文件中,所以在調(diào)試時必須保證gdb能找到源文件。如果把當(dāng)前的gdb.c改名為g.c或者將gdb.c移動到其他地方,則gdb無法進行調(diào)試。

[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的命令行環(huán)境,上面的(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)命令列出當(dāng)前程序的代碼,默認列出10行,如果想列出更多,可以再次輸入list,也可以直接敲擊回車鍵,gdb有個很好用的功能,直接敲擊回車鍵表示執(zhí)行上一條命令。

(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調(diào)試工具需要首先用start命令開始執(zhí)行程序,

(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函數(shù)中變量定義之后的第一條語句處等待我們發(fā)命令, gdb列出的這條語句是即將執(zhí)行
的下一條語句。退出gdb調(diào)試環(huán)境可以利用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、單步執(zhí)行和跟蹤函數(shù)調(diào)用

根據(jù)上一小節(jié)的介紹,我們已經(jīng)知道如何進入gdb調(diào)試工具以及基本知識,本小節(jié)將通過gdb調(diào)試工具找出上一小節(jié)函數(shù)輸出錯誤的原因。

利用start命令開始gdb調(diào)試,我們可以看到程序停在了main函數(shù)的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)命令控制程序向下執(zhí)行:

(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)函數(shù)中進行調(diào)試,在add()函數(shù)中利用backtrace(簡寫為bt命令查看函數(shù)調(diào)用的棧幀:

(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) 

可以看出函數(shù)add()被main()函數(shù)調(diào)用,main傳進來的參數(shù)是low=1,high=10。main函數(shù)的棧幀編號為1, add_range的棧幀編號為0。現(xiàn)在可以利用info(簡寫為i)查看add()函數(shù)中局部變量的值:

(gdb) i locals
i = 1224252
sum = 1218251

可以看到當(dāng)前add()函數(shù)中變量i和變量sum為很大的數(shù),看到這里基本就可以猜出程序錯誤是由于isum未進行初始化導(dǎo)致。
如果想查看main函數(shù)當(dāng)前局部變量的值也可以做到,先用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[]數(shù)組也沒有進行初始化操作,數(shù)組中的數(shù)據(jù)都是雜亂無章的。繼續(xù)利用s或者n命令往下走,然后用print(簡寫為p)打印出變量sum的值:

(gdb) 
12          sum = sum + i;
(gdb) p sum
$1 = 1218251
(gdb) 

這里sum值打印已經(jīng)出錯了,可以利用finish命令讓程序一直運行到從當(dāng)前函數(shù)返回為止或者利用continue(簡寫為c)命令運行到程序結(jié)束,然后修改源代碼。

(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命令進行變量賦值,運行調(diào)試程序,驗證思路正確性。

(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

從運行結(jié)果來看,當(dāng)把sum變量賦值為0后,add()函數(shù)結(jié)束時sum的值為55,符合預(yù)期的結(jié)果。

3、斷點調(diào)試

斷點調(diào)試是指自己在程序的某一行設(shè)置一個斷點,調(diào)試時,程序運行到這一行就會停住,然后可以一步一步往下調(diào)試。斷點調(diào)試是一種非常有效的調(diào)試方法。本小節(jié)將通過例子進行斷點調(diào)試的學(xué)習(xí)。

#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;
}

程序運行結(jié)果如下:

[peterwang@localhost gdb]$ ./gdb_breakpoint 
123
input=123
123
input=123123
^C
[peterwang@localhost gdb]$ 

可以看到程序運行第一次輸入結(jié)果是正確的,第二次輸入123,卻輸出123123(例子只是單純的進行g(shù)db測試,高手勿噴),利用gcc -g gdb_breakpoint.c -o gdb_breakpoint編譯該代碼,運行./gdb gdb_breadpoint并執(zhí)行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) 

執(zhí)行next(n)命令,根據(jù)之前的經(jīng)驗,需要重點懷疑suminput數(shù)組的值的變化,可以利用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步第二次輸入數(shù)據(jù)的時候sum=123這顯然不符合程序的本意,可以推斷出問題出在sum變量在每次while(1)的時候沒有進行賦0操作。問題是找出來了,可是可以看出這樣調(diào)試效率不是很高,我們可以利用break命令(簡寫為b)設(shè)置斷點。break命令可以跟行數(shù)也可以跟函數(shù)名設(shè)置斷點。利用info命令(簡寫為i) breakpoints查看當(dāng)前的斷點信息,利用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位以下的數(shù)字:\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位以下的數(shù)字:
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位以下的數(shù)字:
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行設(shè)置斷點,利用continue(c)命令執(zhí)行程序,直到斷點處停下,可以看到第二次輸入123的時候,sum的值為123,按照程序邏輯這時候sum應(yīng)該為0,這里也可以判斷出,while(1)中缺少了sum的賦0操作。
break命令非常靈活,我們還可以利用條件語句設(shè)置斷點,比如break 15 if sum!=0僅當(dāng)sum!=0的時候在15行設(shè)置斷點,然后利用run(r)命令從頭進行調(diào)試。

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位以下的數(shù)字:
123
input=123
請輸入一個6位以下的數(shù)字:
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、觀察點調(diào)試

根據(jù)上一小節(jié),我們知道在while(1)中加入sum=0程序可以得到正確的結(jié)果,可是如果scanf輸入數(shù)組越界會是什么情況呢?

[peterwang@localhost gdb]$ ./gdb_breakpoint 
請輸入一個5位以下的數(shù)字:
123
input=123
請輸入一個5位以下的數(shù)字:
12
input=12
請輸入一個5位以下的數(shù)字:
12345
input=12345090
請輸入一個5位以下的數(shù)字:

可以看到輸出了一個詭異的結(jié)果,進入gdb調(diào)試,利用x/7 input命令查看input數(shù)組值的變化情況,x代表打印指定存儲單元的內(nèi)容,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位以下的數(shù)字:\n");
(gdb) 
請輸入一個5位以下的數(shù)字:
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) 

我們知道斷點是當(dāng)程序執(zhí)行到某一代碼行時中斷,而觀察點是當(dāng)程序訪問某個存儲單
元時中斷,如果我們不知道某個存儲單元是在哪里被改動的,這時候觀察點尤其有
用。用watch命令設(shè)置觀察點,跟蹤input[4]后面那個字節(jié)(可以用input[5]表示,雖然這是訪問越界),利用 info(i) watchpoints查詢當(dāng)前觀察點。

(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位以下的數(shù)字:\n");
(gdb) 
請輸入一個5位以下的數(shù)字:
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位以下的數(shù)字:
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、段錯誤調(diào)試
#include <stdio.h>
int main(void)
{
    int man = 0;
    scanf("%d", man);
    return 0;
}

利用命令gcc -g gdb_segdefault.c -o gdb_segdefault編譯這段程序,運行出現(xiàn)段錯誤提示。

[peterwang@localhost gdb]$ ./gdb_segdefault 
123
Segmentation fault (core dumped)
[peterwang@localhost gdb]$

進入gdb調(diào)試

(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調(diào)用,可以確定main函數(shù)中的scanf出現(xiàn)問題,可以檢查出scanf函數(shù)中忘記加入&導(dǎo)致段錯誤。


還有一種經(jīng)常發(fā)生的段錯誤,算是一條規(guī)律,如果某個函數(shù)的局部變量發(fā)生訪問越界,有可能并不立即產(chǎn)生段錯誤,而是在函數(shù)返回時產(chǎn)生段錯誤


附錄(常用的gdb命令)

一.基本命令


1)進入GDB  #gdb test
test是要調(diào)試的程序,由gcc test.c -g -o test生成。進入后提示符變?yōu)?gdb)


2)查看源碼  (gdb) l
源碼會進行行號提示。如果需要查看在其他文件中定義的函數(shù),在l后加上函數(shù)名即可定位到這個函數(shù)的定義及查看附近的其他源碼。或者:使用斷點或單步運行,到某個函數(shù)處使用s進入這個函數(shù)。


3)啟動gdb,并且分屏顯示源代碼  gdb -tui
這樣,使用了'-tui'選項,啟動可以直接將屏幕分成兩個部分,上面顯示源代碼,比用list方便多了。這時候使用上下方向鍵可以查看源代碼,想要命令行使用上下鍵就用[Ctrl]n和[Ctrl]p


4)設(shè)置斷點  (gdb) b 6
這樣會在運行到源碼第6行時停止,可以查看變量的值、堆棧情況等;這個行號是gdb的行號。


5)查看斷點處情況  (gdb) info b
可以鍵入"info b"來查看斷點處情況,可以設(shè)置多個斷點;


6)運行代碼  (gdb) r


7)顯示變量值  (gdb) p n
在程序暫停時,鍵入"p 變量名"(print)即可;
GDB在顯示變量值時都會在對應(yīng)值之前加上"$N"標(biāo)記,它是當(dāng)前變量值的引用標(biāo)記,以后若想再次引用此變量,就可以直接寫作"$N",而無需寫冗長的變量名;


8)觀察變量  (gdb) watch n
在某一循環(huán)處,往往希望能夠觀察一個變量的變化情況,這時就可以鍵入命令"watch"來觀察變量的變化情況,GDB在"n"設(shè)置了觀察點;


9)單步運行(不進入函數(shù))  (gdb) n


10)單步運行(進入函數(shù))  (gdb) s


11)程序繼續(xù)運行  (gdb) c
使程序繼續(xù)往下運行,直到再次遇到斷點或程序結(jié)束;


12)退出GDB  (gdb) q


二.斷點調(diào)試


break + 設(shè)置斷點的行號  break n      在n行處設(shè)置斷點


tbreak + 行號或函數(shù)名   tbreak n/func    設(shè)置臨時斷點,到達后被自動刪除


break + filename + 行號  break main.c:10  用于在指定文件對應(yīng)行設(shè)置斷點


break + <0x...>  break 0x3400a      用于在內(nèi)存某一位置處暫停


break + 行號 + if + 條件  break 10 if i==3   用于設(shè)置條件斷點,在循環(huán)中使用非常方便


info breakpoints/watchpoints [n]  info break  n表示斷點號,查看斷點/觀察點的情況


clear + 要清除的斷點行號  clear 10    用于清除對應(yīng)行的斷點,要給出斷點的行號,清除時GDB會給出提示


delete + 要清除的斷點編號  delete 3    用于清除斷點和自動顯示的表達式的命令,要給出斷點的編號,清除時GDB不會給出任何提示


disable/enable + 斷點編號  disable 3    讓所設(shè)斷點暫時失效/使能,如果要讓多個編號處的斷點失效/使能,可將編號之間用空格隔開


awatch/watch + 變量  awatch/watch i    設(shè)置一個觀察點,當(dāng)變量被讀出或?qū)懭霑r程序被暫停


rwatch + 變量      rwatch i        設(shè)置一個觀察點,當(dāng)變量被讀出時,程序被暫停


catch                  設(shè)置捕捉點來補捉程序運行時的一些事件。如:載入共享庫(動態(tài)鏈接庫)或是C++的異常


tcatch                  只設(shè)置一次捕捉點,當(dāng)程序停住以后,應(yīng)點被自動刪除


三.數(shù)據(jù)命令


display +表達式  display a  用于顯示表達式的值,每當(dāng)程序運行到斷點處都會顯示表達式的值


info display      用于顯示當(dāng)前所有要顯示值的表達式的情況


delete + display 編號  delete
3  用于刪除一個要顯示值的表達式,被刪除的表達式將不被顯示


disable/enable + display 編號  disable/enable 3  使一個要顯示值的表達式暫時失效/使能


undisplay + display 編號  undisplay 3  用于結(jié)束某個表達式值的顯示


whatis + 變量  whatis i  顯示某個表達式的數(shù)據(jù)類型


print(p) + 變量/表達式  p n  用于打印變量或表達式的值


set 變量 = 變量值  set i = 3  改變程序中某個變量的值
  在使用print命令時,可以對變量按指定格式進行輸出,其命令格式為print /變量名 + 格式
  其中常用的變量格式:x:十六進制;d:十進制;u:無符號數(shù);o:八進制;c:字符格式;f:浮點數(shù)。


四.調(diào)試運行環(huán)境相關(guān)命令


set args  set args arg1 arg2  設(shè)置運行參數(shù)


show args  show args  參看運行參數(shù)


set width + 數(shù)目  set width 70  設(shè)置GDB的行寬


cd + 工作目錄  cd ../  切換工作目錄


run  r/run  程序開始執(zhí)行


step(s)  s  進入式(會進入到所調(diào)用的子函數(shù)中)單步執(zhí)行,進入函數(shù)的前提是,此函數(shù)被編譯有debug信息


next(n)  n  非進入式(不會進入到所調(diào)用的子函數(shù)中)單步執(zhí)行


finish  finish  一直運行到函數(shù)返回并打印函數(shù)返回時的堆棧地址和返回值及參數(shù)值等信息


until + 行數(shù)  u 3  運行到函數(shù)某一行


continue(c)  c  執(zhí)行到下一個斷點或程序結(jié)束


return <返回值>  return 5  改變程序流程,直接結(jié)束當(dāng)前函數(shù),并將指定值
返回


call + 函數(shù)  call func  在當(dāng)前位置執(zhí)行所要運行的函數(shù)


五.堆棧相關(guān)命令


backtrace/bt  bt  用來打印棧幀指針,也可以在該命令后加上要打印的棧幀指針的個數(shù),查看程序執(zhí)行到此時,是經(jīng)過哪些函數(shù)呼叫的程序,程序“調(diào)用堆棧”是當(dāng)前函數(shù)之前的所有已調(diào)用函數(shù)的列表(包括當(dāng)前函數(shù))。每個函數(shù)及其變量都被分配了一個“幀”,最近調(diào)用的函數(shù)在 0 號幀中(“底部”幀)


frame  frame 1  用于打印指定棧幀


info reg  info reg  查看寄存器使用情況


info stack  info stack  查看堆棧使用情況


up/down  up/down  跳到上一層/下一層函數(shù)


六.跳轉(zhuǎn)執(zhí)行


jump 指定下一條語句的運行點。可以是文件的行號,可以是file:line格式,可以是+num這種偏移量格式。表式著下一條運行語句從哪里開始。相當(dāng)于改變了PC寄存器內(nèi)容,堆棧內(nèi)容并沒有改變,跨函數(shù)跳轉(zhuǎn)容易發(fā)生錯誤。


七.信號命令


signal   signal SIGXXX   產(chǎn)生XXX信號,如SIGINT。一種速查Linux查詢信號的方法:# kill -l


handle   在GDB中定義一個信號處理。信號可以以SIG開頭或不以SIG開頭,可以用定義一個要處理信號的范圍(如:SIGIO-SIGKILL,表示處理從SIGIO信號到SIGKILL的信號,其中包括SIGIO,SIGIOT,SIGKILL三個信號),也可以使用關(guān)鍵字all來標(biāo)明要處理所有的信號。一旦被調(diào)試的程序接收到信號,運行程序馬上會被GDB停住,以供調(diào)試。其可以是以下幾種關(guān)鍵字的一個或多個:


nostop/stop
    當(dāng)被調(diào)試的程序收到信號時,GDB不會停住程序的運行,但會打出消息告訴你收到這種信號/GDB會停住你的程序


print/noprint
    當(dāng)被調(diào)試的程序收到信號時,GDB會顯示出一條信息/GDB不會告訴你收到信號的信息


info signals
  info handle
    可以查看哪些信號被GDB處理,并且可以看到缺省的處理方式
  single命令和shell的kill命令不同,系統(tǒng)的kill命令發(fā)信號給被調(diào)試程序時,是由GDB截獲的,而single命令所發(fā)出一信號則是直接發(fā)給被調(diào)試程序的。


8.運行Shell命令
  如(gdb)shell ls來運行l(wèi)s。


九.更多程序運行選項和調(diào)試


1、程序運行參數(shù)。
  set args 可指定運行時參數(shù)。(如:set args 10 20 30 40 50)
  show args 命令可以查看設(shè)置好的運行參數(shù)。


2、運行環(huán)境。
  path 可設(shè)定程序的運行路徑。
  show paths 查看程序的運行路徑。
  set environment varname [=value] 設(shè)置環(huán)境變量。如:set env USER=hchen
  show environment [varname] 查看環(huán)境變量。


3、工作目錄。
  cd   相當(dāng)于shell的cd命令。
  pwd  顯示當(dāng)前的所在目錄。


4、程序的輸入輸出。
  info terminal 顯示你程序用到的終端的模式。
  使用重定向控制程序輸出。如:run > outfile
  tty命令可以指寫輸入輸出的終端設(shè)備。如:tty /dev/ttyb


5、調(diào)試已運行的程序
兩種方法:
  (1)在UNIX下用ps查看正在運行的程序的PID(進程ID),然后用gdb PID格式掛接正在運行的程序。
  (2)先用gdb 關(guān)聯(lián)上源代碼,并進行g(shù)db,在gdb中用attach命令來掛接進程的PID。并用detach來取消掛接的進程。


6、暫停 / 恢復(fù)程序運行
當(dāng)進程被gdb停住時,你可以使用info program來查看程序的是否在運行,進程號,被暫停的原因。 在gdb中,我們可以有以下幾種暫停方式:斷點(BreakPoint)、觀察點(WatchPoint)、捕捉點(CatchPoint)、信號(Signals)、線程停止(Thread Stops),如果要恢復(fù)程序運行,可以使用c或是continue命令。


7、線程(Thread Stops)
如果程序是多線程,可以定義斷點是否在所有的線程上,或是在某個特定的線程。
  break thread
  break thread if ...
  linespec指定了斷點設(shè)置在的源程序的行號。threadno指定了線程的ID,注意,這個ID是GDB分配的,可以通過“info threads”命令來查看正在運行程序中的線程信息。如果不指定thread 則表示斷點設(shè)在所有線程上面。還可以為某線程指定斷點條件。
  如: (gdb) break frik.c:13 thread 28 if bartab > lim
當(dāng)你的程序被GDB停住時,所有的運行線程都會被停住。這方便查看運行程序的總體情況。而在你恢復(fù)程序運行時,所有的線程也會被恢復(fù)運行。


參考文檔

《Linux C編程一站式學(xué)習(xí)》
http://www.jb51.net/article/36393.htm
http://blog.chinaunix.net/uid-9525959-id-2001805.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容