標籤:
用 GDB 偵錯工具
原著:Rick McMullin
用 gdb 調試 GCC 程式
轉自:http://blog.csdn.net/bonnshore/article/details/7955422
Linux 包含了一個叫 gdb 的 GNU 偵錯工具. gdb 是一個用來調試 C 和 C++ 程式的強力調試器. 它使你能在程式運行時觀察程式的內部結構和記憶體的使用方式. 以下是 gdb 所提供的一些功能:
- 它使你能監視你程式中變數的值.
- 它使你能設定斷點以使程式在指定的程式碼上停止執行.
- 它使你能一行行的執行你的代碼.
在命令列上鍵入 gdb 並按斷行符號鍵就可以運行 gdb 了, 如果一切正常的話, gdb 將被啟動並且你將在螢幕上看到類似的內容:
GDB is free software and you are welcome to distribute copies of itunder certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.(gdb)
當你啟動 gdb 後, 你能在命令列上指定很多的選項. 你也可以以下面的方式來運行 gdb :
gdb <fname>
當你用這種方式運行 gdb , 你能直接指定想要調試的程式. 這將告訴gdb 裝入名為 fname 的可執行檔. 你也可以用 gdb 去檢查一個因程式異常終止而產生的 core 檔案, 或者與一個正在啟動並執行程式相連. 你可以參考gdb 指南頁或在命令列上鍵入 gdb -h 得到一個有關這些選項的說明的簡單列表.
為調試編譯代碼(Compiling Code for Debugging)
為了使 gdb 正常工作, 你必須使你的程式在編譯時間包含調試資訊. 調試資訊包含你程式裡的每個變數的類型和在可執行檔裡的地址映射以及原始碼的行號. gdb 利用這些資訊使原始碼和機器碼相關聯.
在編譯時間用 -g 選項開啟調試選項.
gdb 基本命令
gdb 支援很多的命令使你能實現不同的功能. 這些命令從簡單的檔案裝入到允許你檢查所調用的堆棧內容的複雜命令, 表27.1列出了你在用 gdb 調試時會用到的一些命令. 想瞭解 gdb 的詳細使用請參考 gdb 的指南頁.
表 27.1. 基本 gdb 命令.
| 命 令 |
描 述 |
| file |
裝入想要調試的可執行檔. |
| kill |
終止正在調試的程式. |
| list |
列出產生執行檔案的原始碼的一部分. |
| next |
執行一行原始碼但不進入函數內部. |
| step |
執行一行原始碼而且進入函數內部. |
| run |
執行當前被調試的程式 |
| quit |
終止 gdb |
| watch |
使你能監視一個變數的值而不管它何時被改變. |
| break |
在代碼裡設定斷點, 這將使程式執行到這裡時被掛起. |
| make |
使你能不退出 gdb 就可以重新產生可執行檔. |
| shell |
使你能不離開 gdb 就執行 UNIX shell 命令. |
gdb 支援很多與 UNIX shell 程式一樣的命令編輯特徵. 你能象在 bash 或 tcsh裡那樣按 Tab 鍵讓 gdb 幫你補齊一個唯一的命令, 如果不唯一的話 gdb 會列出所有匹配的命令. 你也能用游標鍵上下翻動曆史命令.
gdb 應用舉例
本節用一個執行個體教你一步步的用 gdb 偵錯工具. 被調試的程式相當的簡單, 但它展示了 gdb 的典型應用.
下面列出了將被調試的程式. 這個程式被稱為 greeting , 它顯示一個簡單的問候, 再用反序將它列出.
#include <stdio.h>main (){ char my_string[] = "hello there"; my_print (my_string); my_print2 (my_string);}void my_print (char *string){ printf ("The string is %s\n", string);}void my_print2 (char *string){ char *string2; int size, i; size = strlen (string); string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size - i] = string[i]; string2[size+1] = `\0‘; printf ("The string printed backward is %s\n", string2);}
用下面的命令編譯它:
gcc -o test test.c
這個程式執行時顯示如下結果:
The string is hello thereThe string printed backward is
輸出的第一行是正確的, 但第二行列印出的東西並不是我們所期望的. 我們所設想的輸出應該是:
The string printed backward is ereht olleh
由於某些原因, my_print2 函數沒有正常工作. 讓我們用 gdb 看看問題究竟出在哪兒, 先鍵入如下命令:
gdb greeting
-
注意: 記得在編譯 greeting 程式時把調試選項開啟.
如果你在輸入命令時忘了把要調試的程式作為參數傳給 gdb , 你可以在 gdb 提示符下用 file 命令來載入它:
(gdb) file greeting
這個命令將載入 greeting 可執行檔就象你在 gdb 命令列裡裝入它一樣.
這時你能用 gdb 的 run 命令來運行 greeting 了. 當它在 gdb 裡被運行後結果大約會象這樣:
(gdb) runStarting program: /root/greetingThe string is hello thereThe string printed backward isProgram exited with code 041
這個輸出和在 gdb 外面啟動並執行結果一樣. 問題是, 為什麼反序列印沒有工作? 為了找出癥結所在, 我們可以在 my_print2 函數的 for 語句後設一個斷點, 具體的做法是在 gdb 提示符下鍵入 list 命令三次, 列出原始碼:
(gdb) list(gdb) list(gdb) list
-
技巧: 在 gdb 提示符下按斷行符號健將重複上一個命令.
第一次鍵入 list 命令的輸出如下:
1 #include <stdio.h>23 main ()4 {5 char my_string[] = "hello there";67 my_print (my_string);8 my_print2 (my_string);9 }10
如果按下斷行符號, gdb 將再執行一次 list 命令, 給出下列輸出:
11 my_print (char *string)12 {13 printf ("The string is %s\n", string);14 }1516 my_print2 (char *string)17 {18 char *string2;19 int size, i;20
再按一次斷行符號將列出 greeting 程式的剩餘部分:
21 size = strlen (string);22 string2 = (char *) malloc (size + 1);23 for (i = 0; i < size; i++)24 string2[size - i] = string[i];25 string2[size+1] = `\0‘;26 printf ("The string printed backward is %s\n", string2);27 }
根據列出的來源程式, 你能看到要設斷點的地方在第24行, 在 gdb 命令列提示符下鍵入如下命令設定斷點:
(gdb) break 24
gdb 將作出如下的響應:
Breakpoint 1 at 0x139: file greeting.c, line 24(gdb)
現在再鍵入 run 命令, 將產生如下的輸出:
Starting program: /root/greetingThe string is hello thereBreakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :2424 string2[size-i]=string[i]
你能通過設定一個觀察 string2[size - i] 變數的值的觀察點來看出錯誤是怎樣產生的, 做法是鍵入:
(gdb) watch string2[size - i]
gdb 將作出如下回應:
Watchpoint 2: string2[size - i]
現在可以用 next 命令來一步步的執行 for 迴圈了:
(gdb) next
經過第一次迴圈後, gdb 告訴我們 string2[size - i] 的值是 `h`. gdb 用如下的顯示來告訴你這個資訊:
Watchpoint 2, string2[size - i]Old value = 0 `\000‘New value = 104 `h‘my_print2(string = 0xbfffdc4 "hello there") at greeting.c:2323 for (i=0; i<size; i++)
這個值正是期望的. 後來的數次迴圈的結果都是正確的. 當 i=10 時, 運算式 string2[size - i] 的值等於`e`, size - i 的值等於 1, 最後一個字元已經拷到新串裡了.
如果你再把迴圈執行下去, 你會看到已經沒有值分配給 string2[0] 了, 而它是新串的第一個字元, 因為malloc 函數在分配記憶體時把它們初始化為空白(null)字元. 所以 string2 的第一個字元是Null 字元. 這解釋了為什麼在列印 string2 時沒有任何輸出了.
現在找出了問題出在哪裡, 修正這個錯誤是很容易的. 你得把代碼裡寫入 string2 的第一個字元的的位移量改為 size - 1 而不是 size. 這是因為 string2 的大小為 12, 但起始位移量是 0, 串內的字元從位移量 0 到 位移量 10, 位移量 11 為空白字元保留.
為了使代碼正常工作有很多種修改辦法. 一種是另設一個比串的實際大小小 1 的變數. 這是這種解決辦法的代碼:
#include <stdio.h>main (){ char my_string[] = "hello there"; my_print (my_string); my_print2 (my_string);}my_print (char *string){ printf ("The string is %s\n", string);}my_print2 (char *string){ char *string2; int size, size2, i; size = strlen (string); size2 = size -1; string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size2 - i] = string[i]; string2[size] = `\0‘; printf ("The string printed backward is %s\n", string2);}
用 gdb 調試 GCC 程式【轉】