上篇文章是2012年8月8日,除了是北京奧運會4週年之外,沒啥特殊的意義。。
時隔半年,又回來看了一看,發現還是有很多人在訪問我的博客,突然感覺有點愧疚。這半年一直在打理個人博客,這邊就有點荒廢了。
媽媽說,這樣做是不對的。
所以這邊重新開始弄起來。技術爲主,口水爲輔。
廢話一說就多,這篇文章來講講Linux下強大的調試工具--gdb。
------------------性感的分割線--------------------
一、gdb常用的命令:
跟在Windows下使用Visual Studio進行調試一樣,gdb的調試功能與其大同小異。
命令 | 功能 | 快捷方式 |
file | 加載需要調試的可執行文件 | file |
kill | 終止正在調試的程序 | k |
list | 列出源代碼的一部分 | l |
info | 列出某個項目的信息 | i |
next | 執行一行代碼(不進入函數內部) | n |
step | 執行下一行代碼 | s |
continue | 繼續運行程序,直到下一個斷點 | c |
run | 執行已經加載的程序 | r |
quit | 終止gdb | q |
watch | 監視某個變量的值 | w |
examine | 監視某個變量的值 | x |
break | 設置斷點 | b |
disable / enable | 使斷點無效/有效 | disable/enable |
delete | 刪除斷點 | d |
make | 在不退出gdb的情況下,重新生成可執行文件 | make |
shell | 在不退出gdb的情況下,使用shell命令 | shell |
這只是一部分哦。關於整個gdb調試可以寫成一本書呢。
看到全稱不要緊張,畢竟還是有些對英文不太感冒的同學,在gdb下按Tab,gdb也會幫你自動補全,避免誤輸入。而且按上下鍵也可以翻看歷史命令。(這樣不是個辦法,還是好好學英語吧^^)
還有一個辦法,是使用簡化的命令。這個方法satanness我還是相當喜歡的。不過還是建議初學者使用全稱,因爲只使用首字母有時候會產生歧義,比如disable和delete,gdb在你輸入d時,會默認調用delete,如果你想禁用某個斷點,卻輸入了d 5,那這個斷點就會被刪除了。
當然,熟悉了之後,還是使用快捷命令吧,會提高很多的效率。
二、編寫測試程序
因爲要調試,satanness寫一個有點BUG的程序,以方便調試,希望同學們不要學習我這種自殘的行爲。
- [serious@localhost c]$ vim gdbtest.c
- [serious@localhost c]$ cat gdbtest.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- void reverse(char* string)
- {
- int nLength = strlen(string);
- char* temp = (char*)malloc(nLength + 1);
- for(int i = 0; i < nLength; i++)
- {
- temp[i] = string[nLength - i];
- }
- strcpy(string, temp);
- free(temp);
- }
- int main(int argc, char** argv)
- {
- char * str = malloc(20);
- memset(str, 0, 20);
- strcpy(str, "hello world!");
- reverse(str);
- printf("%s\n", str);
- free(str);
- return 0;
- }
編譯之:
- [serious@localhost c]$gcc -g -o gdbtest gdbtest.c -std=c99 //添加-g選項以產生調試信息
這個程序的用意是將一個字符串給逆序化。請無視渣算法,一切爲了調試。
運行看看我們的程序:
- [serious@localhost c]$ ./gdbtest
- [serious@localhost c]$
咦?爲什麼沒有輸出呢?
那就來調試一下。
三、調試程序
因爲剛纔我們在編譯程序時,已經加上了-g選項,就可以直接使用gdb進行調試了。
- [serious@localhost c]$ gdb gdbtest
- GNU gdb (GDB) 7.5
- Copyright (C) 2012 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 "x86_64-unknown-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/serious/Desktop/c/gdbtest...done.
- (gdb) //這裏就可以開始輸入命令進行調試了
首先列出程序的內容(當然,你也可以另開一個終端界面使用vim的set nu功能顯示行號)
- (gdb) list 1
- 1 #include <stdio.h>
- 2 #include <stdlib.h>
- 3 #include <string.h>
- 4
- 5 void reverse(char* string)
- 6 {
- 7 int nLength = strlen(string);
- 8 char * temp = (char*)malloc(nLength);
- 9
- 10 for(int i = 0; i < nLength; i++)
list命令會一次性列出10行源代碼,list後面加上1是確保可以從第一行開始顯示源代碼。
後面的就不列出了。我們在幾個關鍵的位置加上斷點:
- (gdb) break 7
- Breakpoint 1 at 0x4006ac: file gdbtest.c, line 7.
- (gdb) break 12
- Breakpoint 2 at 0x4006d5: file gdbtest.c, line 12.
- (gdb) b 15
- Breakpoint 3 at 0x400707: file gdbtest.c, line 15.
- (gdb) b 27
- Breakpoint 4 at 0x400777: file gdbtest.c, line 27.
- (gdb) b 29
- Breakpoint 5 at 0x400783: file gdbtest.c, line 29.
然後看看我們所設置的斷點:
- (gdb) info break
- Num Type Disp Enb Address What
- 1 breakpoint keep y 0x00000000004006ac in reverse at gdbtest.c:7
- 2 breakpoint keep y 0x00000000004006d5 in reverse at gdbtest.c:12
- 3 breakpoint keep y 0x0000000000400707 in reverse at gdbtest.c:15
- 4 breakpoint keep y 0x0000000000400777 in main at gdbtest.c:27
- 5 breakpoint keep y 0x0000000000400783 in main at gdbtest.c:29
Num代表斷點的序號,如果我們想要禁用某個斷點,可以使用disable n命令;如果想要刪除,則使用delete n命令。如果要全部禁用(或刪除),則不需要帶n。
Type代表斷點的類型。有breakpoint, watchpoint和catchpoint三種類型。
Disp代表當走到當前斷點時,如果此斷點已被禁用,是否要顯示該信息。
Enb是斷點當前是否是啓用的。啓用爲y,禁用爲n。
Address是斷點的地址。每一個斷點也是有分配相應的內存的。
What是斷點的詳細信息,包括所在的函數名及所在源代碼行數。
開始調試:
- (gdb) run // 運行程序
- Starting program: /home/serious/Desktop/c/gdbtest
- Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at gdbtest.c:27
- 27 reverse(str);
- (gdb) continue // 繼續運行程序直到下一個斷點
- Continuing.
- Breakpoint 1, reverse (string=0x601010 "hello world!") at gdbtest.c:7
- 7 int nLength = strlen(string);
- (gdb) next // 執行一行代碼
- 8 char * temp = (char*)malloc(nLength);
- (gdb) print nLength // 查看nLength的值
- $10 = 12 // 長度是正確的
- (gdb) next
- 10 for(int i = 0; i < nLength; i++)
- (gdb) next
- Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
- 12 temp[i] = string[nLength - i];
- (gdb) next
- 10 for(int i = 0; i < nLength; i++)
- (gdb) next
- Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
- 12 temp[i] = string[nLength - i];
- (gdb) print temp // 查看temp的值
- $11 = 0x601030 ""
- (gdb) continue
- Continuing.
- Breakpoint 2, reverse (string=0x601010 "hello world!") at gdbtest.c:12
- 12 temp[i] = string[nLength - i];
- (gdb) print temp
- $12 = 0x601030 "" // 已經發現問題了,連續賦值兩次,temp卻一直是""
- (gdb)
程序的BUG在於,string[nLength - i]的值是'\0',如果賦給temp[0],則字符串temp的確就變成了""。
我們可以使用examine命令查看一下temp所在的內存區域:
- (gdb) examine/12ub temp
- 0x601030: 0 33 100 0 0 0 0 0
- 0x601038: 0 0 0 0
解釋一下命令:
12ub表示temp所在內存的顯示方式,12u代表顯示12個計量單位,b代表字節,所以此命令是顯示以temp作爲起始地址,向後顯示12個字節的內容。可以看到,第一個字節是0,第二個字節是33,即'!'。
更多格式,請man gdb。
不需要退出程序,我們直接修改程序:
- (gdb) shell
- [serious@localhost c]$ vim gdbtest.c
- [serious@localhost c]$ gcc -g -o gdbtest gdbtest.c -std=c99
- [serious@localhost c]$ exit
- exit
- (gdb)
將源代碼第12行修改爲:
- temp[i] = string[nLength - i - 1];
再次調試到給temp賦值的部分,看一下temp:
- (gdb) p temp
- $13 = 0x601030 "!dlr"
有童鞋問了,老這樣輸出temp好煩啊,有什麼其他辦法嗎?
當然有。使用watch命令。
在調試到第10行時,使用watch:
- (gdb) n 10 for(int i = 0; i < nLength; i++)
- (gdb) watch temp[i]
- Hardware watchpoint 12: temp[i]
- (gdb) c
- Continuing.
- Hardware watchpoint 12: temp[i]
- Old value = 0 '\000'
- New value = 33 '!'
- reverse (string=0x601010 "hello world!") at gdbtest.c:10
- 10 for(int i = 0; i < nLength; i++)
- (gdb)
可以看到,當temp[i]有變化時,gdb會提示你,值有變化,從'\000\變化到了'!'。
程序最後的運行結果:
- [serious@localhost c]$ ./gdbtest
- !dlrow olleh
四、容易出現的問題
1. 在使用gdb時,有時會出現這樣的問題:
- (gdb) n
- Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6.x86_64
- (gdb)
解決辦法:編輯一下debuginfo的源,並使用debuginfo-install命令更新debuginfo庫。
- [serius@localhost c]$ vim /etc/yum.repos.d/CentOS-Base-debuginfo.repo:
- // 添加如下源
- [base-debuginfo]
- name=CentOS-$releasever -DebugInfo
- baseurl=http://debuginfo.centos.org/$releasever/$basearch/
- gpgcheck=0
- enabled=0
- protect=1
- priority=1
2. 也有可能在調試你的程序時出現:
- (gdb) n
- Single stepping until exit from function yourfunc, which has no line number information.
- (gdb)
yourfunc是你自己的函數,這表示着含有此函數的代碼在編譯時並沒有加上-g選項。
還有一種可能,是你的gcc版本過高而gdb版本過低,比如我。CentOS6.4,4.8.0版本gcc,卻是7.2.0版本的gdb,無奈先升級了gdb,才避免了這個錯誤。
3. 在watch變量時出現:
- (gdb) watch var
- No symbol "var" in current context.
很明顯了,gdb在當前的代碼域內找不到var變量,就會報這種錯誤。
五、總結
總的來說,gdb是個強大的調試器,我在上面說的這一堆,也只是滄海一粟而已,gdb的官方pdf文檔已經堆到了662頁。。我也不可能在一篇文章裏說清楚,所以,如果你想知道更多,就去下載文檔閱讀吧。