使用gdb調試程序

上篇文章是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我還是相當喜歡的。不過還是建議初學者使用全稱,因爲只使用首字母有時候會產生歧義,比如disabledelete,gdb在你輸入d時,會默認調用delete,如果你想禁用某個斷點,卻輸入了d 5,那這個斷點就會被刪除了。

當然,熟悉了之後,還是使用快捷命令吧,會提高很多的效率。

二、編寫測試程序

因爲要調試,satanness寫一個有點BUG的程序,以方便調試,希望同學們不要學習我這種自殘的行爲。

  1. [serious@localhost c]$ vim gdbtest.c 
  2. [serious@localhost c]$ cat gdbtest.c 
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. #include <string.h> 
  6.  
  7. void reverse(char* string) 
  8.     int nLength = strlen(string); 
  9.     char* temp = (char*)malloc(nLength + 1); 
  10.  
  11.     for(int i = 0; i < nLength; i++) 
  12.     { 
  13.         temp[i] = string[nLength - i]; 
  14.     } 
  15.  
  16.     strcpy(string, temp); 
  17.  
  18. free(temp);
  19.  
  20. int main(int argc, char** argv) 
  21.     char * str = malloc(20); 
  22.     memset(str, 0, 20); 
  23.     strcpy(str, "hello world!"); 
  24.  
  25.     reverse(str); 
  26.  
  27.     printf("%s\n", str); 
  28.  
  29. free(str);
  30.     return 0; 

編譯之:

  1. [serious@localhost c]$gcc -g -o gdbtest gdbtest.c -std=c99  //添加-g選項以產生調試信息 

這個程序的用意是將一個字符串給逆序化。請無視渣算法,一切爲了調試。

運行看看我們的程序:

  1. [serious@localhost c]$ ./gdbtest  
  2.  
  3. [serious@localhost c]$  

咦?爲什麼沒有輸出呢?

那就來調試一下。

三、調試程序

因爲剛纔我們在編譯程序時,已經加上了-g選項,就可以直接使用gdb進行調試了。

  1. [serious@localhost c]$ gdb gdbtest 
  2. GNU gdb (GDB) 7.5 
  3. Copyright (C) 2012 Free Software Foundation, Inc. 
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 
  5. This is free software: you are free to change and redistribute it. 
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying" 
  7. and "show warranty" for details. 
  8. This GDB was configured as "x86_64-unknown-linux-gnu"
  9. For bug reporting instructions, please see: 
  10. <http://www.gnu.org/software/gdb/bugs/>... 
  11. Reading symbols from /home/serious/Desktop/c/gdbtest...done. 
  12. (gdb) //這裏就可以開始輸入命令進行調試了

首先列出程序的內容(當然,你也可以另開一個終端界面使用vim的set nu功能顯示行號)

  1. (gdb) list 1 
  2. 1   #include <stdio.h> 
  3. 2   #include <stdlib.h> 
  4. 3   #include <string.h> 
  5. 4    
  6. 5   void reverse(char* string) 
  7. 6   { 
  8. 7       int nLength = strlen(string); 
  9. 8       char * temp = (char*)malloc(nLength); 
  10. 9    
  11. 10      for(int i = 0; i < nLength; i++) 

list命令會一次性列出10行源代碼,list後面加上1是確保可以從第一行開始顯示源代碼。

後面的就不列出了。我們在幾個關鍵的位置加上斷點:

  1. (gdb) break 7 
  2. Breakpoint 1 at 0x4006ac: file gdbtest.c, line 7. 
  3. (gdb) break 12 
  4. Breakpoint 2 at 0x4006d5: file gdbtest.c, line 12. 
  5. (gdb) b 15 
  6. Breakpoint 3 at 0x400707: file gdbtest.c, line 15. 
  7. (gdb) b 27 
  8. Breakpoint 4 at 0x400777: file gdbtest.c, line 27. 
  9. (gdb) b 29 
  10. Breakpoint 5 at 0x400783: file gdbtest.c, line 29. 

然後看看我們所設置的斷點:

  1. (gdb) info break 
  2. Num     Type           Disp Enb Address            What 
  3. 1       breakpoint     keep y   0x00000000004006ac in reverse at gdbtest.c:7 
  4. 2       breakpoint     keep y   0x00000000004006d5 in reverse at gdbtest.c:12 
  5. 3       breakpoint     keep y   0x0000000000400707 in reverse at gdbtest.c:15 
  6. 4       breakpoint     keep y   0x0000000000400777 in main at gdbtest.c:27 
  7. 5       breakpoint     keep y   0x0000000000400783 in main at gdbtest.c:29 

Num代表斷點的序號,如果我們想要禁用某個斷點,可以使用disable n命令;如果想要刪除,則使用delete n命令。如果要全部禁用(或刪除),則不需要帶n。

Type代表斷點的類型。有breakpoint, watchpointcatchpoint三種類型。

Disp代表當走到當前斷點時,如果此斷點已被禁用,是否要顯示該信息。

Enb是斷點當前是否是啓用的。啓用爲y,禁用爲n

Address是斷點的地址。每一個斷點也是有分配相應的內存的。

What是斷點的詳細信息,包括所在的函數名及所在源代碼行數。

開始調試:

  1. (gdb) run  // 運行程序
  2. Starting program: /home/serious/Desktop/c/gdbtest  
  3.  
  4. Breakpoint 4, main (argc=1, argv=0x7fffffffe1a8) at gdbtest.c:27 
  5. 27      reverse(str); 
  6.  
  7. (gdb) continue  // 繼續運行程序直到下一個斷點
  8. Continuing. 
  9.  
  10. Breakpoint 1, reverse (string=0x601010 "hello world!"at gdbtest.c:7 
  11. 7       int nLength = strlen(string); 
  12.  
  13. (gdb) next  // 執行一行代碼
  14. 8       char * temp = (char*)malloc(nLength); 
  15.  
  16. (gdb) print nLength  // 查看nLength的值
  17. $10 = 12  // 長度是正確的
  18. (gdb) next 
  19. 10      for(int i = 0; i < nLength; i++) 
  20. (gdb) next 
  21.  
  22. Breakpoint 2, reverse (string=0x601010 "hello world!"at gdbtest.c:12 
  23. 12          temp[i] = string[nLength - i]; 
  24. (gdb) next 
  25. 10      for(int i = 0; i < nLength; i++) 
  26. (gdb) next 
  27.  
  28. Breakpoint 2, reverse (string=0x601010 "hello world!"at gdbtest.c:12 
  29. 12          temp[i] = string[nLength - i]; 
  30. (gdb) print temp  // 查看temp的值
  31. $11 = 0x601030 "" 
  32. (gdb) continue
  33. Continuing. 
  34.  
  35. Breakpoint 2, reverse (string=0x601010 "hello world!"at gdbtest.c:12 
  36. 12          temp[i] = string[nLength - i]; 
  37. (gdb) print temp 
  38. $12 = 0x601030 ""  // 已經發現問題了,連續賦值兩次,temp卻一直是""
  39. (gdb)  

 程序的BUG在於,string[nLength - i]的值是'\0',如果賦給temp[0],則字符串temp的確就變成了""。

我們可以使用examine命令查看一下temp所在的內存區域:

  1. (gdb) examine/12ub temp 
  2. 0x601030:   0   33  100 0   0   0   0   0 
  3. 0x601038:   0   0   0   0 

解釋一下命令:

12ub表示temp所在內存的顯示方式,12u代表顯示12個計量單位,b代表字節,所以此命令是顯示以temp作爲起始地址,向後顯示12個字節的內容。可以看到,第一個字節是0,第二個字節是33,即'!'。

更多格式,請man gdb。

不需要退出程序,我們直接修改程序:

  1. (gdb) shell 
  2. [serious@localhost c]$ vim gdbtest.c 
  3. [serious@localhost c]$ gcc -g -o gdbtest gdbtest.c -std=c99 
  4. [serious@localhost c]$ exit 
  5. exit 
  6. (gdb)  

將源代碼第12行修改爲:

  1. temp[i] = string[nLength - i - 1]; 

再次調試到給temp賦值的部分,看一下temp:

  1. (gdb) p temp 
  2. $13 = 0x601030 "!dlr" 

有童鞋問了,老這樣輸出temp好煩啊,有什麼其他辦法嗎?

當然有。使用watch命令。

在調試到第10行時,使用watch

  1. (gdb) n 10 for(int i = 0; i < nLength; i++)
  2. (gdb) watch temp[i] 
  3. Hardware watchpoint 12: temp[i] 
  4. (gdb) c 
  5. Continuing. 
  6. Hardware watchpoint 12: temp[i] 
  7.  
  8. Old value = 0 '\000' 
  9. New value = 33 '!' 
  10. reverse (string=0x601010 "hello world!"at gdbtest.c:10 
  11. 10      for(int i = 0; i < nLength; i++) 
  12. (gdb)  

可以看到,當temp[i]有變化時,gdb會提示你,值有變化,從'\000\變化到了'!'。

程序最後的運行結果:

  1. [serious@localhost c]$ ./gdbtest  
  2. !dlrow olleh 

四、容易出現的問題

1. 在使用gdb時,有時會出現這樣的問題:

  1. (gdb) n 
  2. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6.x86_64 
  3. (gdb) 

解決辦法:編輯一下debuginfo的源,並使用debuginfo-install命令更新debuginfo庫。

  1. [serius@localhost c]$ vim /etc/yum.repos.d/CentOS-Base-debuginfo.repo: 
  2. // 添加如下源
  3. [base-debuginfo] 
  4. name=CentOS-$releasever -DebugInfo 
  5. baseurl=http://debuginfo.centos.org/$releasever/$basearch/ 
  6. gpgcheck=0 
  7. enabled=0 
  8. protect=1 
  9. priority=1 

2. 也有可能在調試你的程序時出現:

  1. (gdb) n 
  2. Single stepping until exit from function yourfunc, which has no line number information. 
  3. (gdb) 

yourfunc是你自己的函數,這表示着含有此函數的代碼在編譯時並沒有加上-g選項。

還有一種可能,是你的gcc版本過高而gdb版本過低,比如我。CentOS6.4,4.8.0版本gcc,卻是7.2.0版本的gdb,無奈先升級了gdb,才避免了這個錯誤。

3. 在watch變量時出現:

  1. (gdb) watch var
  2. No symbol "var" in current context. 

很明顯了,gdb在當前的代碼域內找不到var變量,就會報這種錯誤。

五、總結

總的來說,gdb是個強大的調試器,我在上面說的這一堆,也只是滄海一粟而已,gdb的官方pdf文檔已經堆到了662頁。。我也不可能在一篇文章裏說清楚,所以,如果你想知道更多,就去下載文檔閱讀吧。

兇猛的傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章