程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Linux環境下的C/C+基礎調試技術2——程序控制

Linux環境下的C/C+基礎調試技術2——程序控制

編輯:關於C語言

1.讓程序停下來的三種模式

斷點(breakpoint):讓程序在特定的地點停止執行。
觀察點(watchpoint):讓程序在特定的內存地址(或者是一個涉及多個地址的表達式)的值發生變化時停止執行。注意,你不能給一個尚沒有在棧幀中的表達式或變量設定觀察點,換句話說,常常在程序停下來後才去設置觀察點。在設定觀察點後,棧幀中不存在所監控的變量時,觀察點自動刪除。
捕捉點(catchpoint):讓程序在發生特定事件時停止執行。
注:

GDB文檔中統稱這三種程序暫停手段為breakpoint,例如在GDB的delete命令的幫助手冊中就是這麼描述的,它實際上指代的是這三種暫停手段,本文中以breakpoints統稱三種模式,以中文進行分別稱呼。
GDB執行程序到斷點(成為斷點被hit)時,它並沒有執行斷點指向的那一行,而是將要指向斷點指向的那一行。
GDB是以機器指令為單位進行執行的,並非是以程序代碼行來進行的,這個可能會帶來一些困惑,下文有例子詳述。
2.GDB breakpoints的查看

命令:i b = info breakpoints。返回列表每一列的含義如下:

Identifier :breakpoints的唯一標識。
Type :該breakpoints屬於上述三種模式中的哪一個(breakpoint, watchpoint, catchpoint)
Disposition:該breakpoints下次被hit以後的狀態(keep,del,dis分別對應保留、刪除、不使能)
Enable Status:該breakpoints是否使能。
Address:該breakpoints物理地址。
Location :若屬於斷點則指的是斷點在哪個文件的第幾行,若是觀察點則指的是被觀察的變量
3.GDB 程序控制的設置

斷點設置:
設置普通斷點:break function/line_number/filename:line_number/filename:function. 該斷點在被刪除或不使能前一直有效。
設置臨時斷點:tbreak function/line_number/filename:line_number/filename:function. 該斷點在被hit一次後自動刪除。
設置一次性斷點:enable once breakpoint-list,這個與臨時斷點的不同是該斷點會在被hit一次後不使能,而不是刪除。
設置正則表達式斷點:rbreak regexp 注意該正則表達式是grep型的正則,不是perl或shell的正則語法。
設置條件斷點:break break-args if (condition) ,例如break main if argc > 1。
這個與觀察點不同的是,觀察點只要所觀察的表達式或變量的值有變化程序就停下,而條件斷點必須滿足所指條件。條件斷點在調試循環的時候非常有用,例如break if (i == 70000) 。
在已經添加的斷點上加上條件使用cond id condition,例如:cond 3 i == 3;想去掉條件轉化為普通斷點則直接使用cond id,例如,cond 3。
注意,這裡的條件外的括號有沒有都行,條件中可以使用<, <=, ==, !=, >, >=, &&, ||,&, |, ^, >>, <<,+, -, x, /, %等運算符,也可以使用方法,例如:break test.c:myfunc if ! check_variable_sanity(i),這裡的方法返回值一定要是int,否則該條件就會被誤讀。
刪除斷點:
delete breakpoint_list     列表中為斷點的ID,以空格隔開
delete          刪除全部斷點
clear           刪除下一個GDB將要執行的指令處的斷點
clear function/filename:function/line_number/filename:line_number 刪除特定地點的斷點
使能斷點:enable breakpoint-list
不使能斷點:disable breakpoint-list
跳過斷點:ignore id numbers 表示跳過id表示的斷點numbers次。
注意:
若設置斷點是以函數名進行的話,C++中函數的重載會帶來麻煩,該同名函數會都被設置上斷點。請使用如下格式在C++中進行函數斷點的設置:TestClass::testFunc(int)
設置的斷點可能並非是你想放置的那一行。例如:
  1: int main(void)

  2: {

  3:     int i;

  4:     i=3;

  5:     return 0;

  6: }
我們不使用編譯器優化進行編譯,然後加載到GDB中,如下:
$ gcc -g3 -Wall -Wextra -o test1 test1.c
$ gdb test1
(gdb) break main
Breakpoint 1 at 0x6: file test1.c, line 4.
我們發現顯然#4並非是main函數的入口,這是因為這一行是該函數第一行雖然產生了機器碼,但是GDB並不認為這對調試有幫助,於是它就將斷點設置在第一行對調試有幫助的代碼上。

我們使用編譯器優化再進行編譯,情況會更加令人困惑,如下:

$ gcc -O9 -g3 -Wall -Wextra -o test1 test1.c
$ gdb test1
(gdb) break main
Breakpoint 1 at 0x3: file test1.c, line 6.

GCC發現i變量一直就沒有使用,所以通過優化直接忽略了,於是程序第一行產生機器碼的代碼恰好是main函數的最後一行。

因此,建議在不影響的情況下,程序調試時將編譯器優化選項關閉。

 

同一行有多個斷點時,程序只會停下一次,實際上GDB使用的是其中ID最小的那個。
在多文件調試中,常常希望GDB在並非當前文件的部分設置斷點,而GDB默認關注的是含有main函數的文件,此時你可以使用list functionname、或者單步調試等方式進入另一個文件的源代碼中進行設置,此時你設置的行號就是針對這個文件的源代碼了。
當你利用代碼行進行斷點設置時,重新編譯程序並在GDB中reload後,斷點可能因為你代碼行數的變化而發生相對位置變化(GDB指向的行數),這樣的情況下使用DDD直接對原斷點進行拖動是最方便的方法,它不會影響該斷點的狀態和條件,只會改變它所指的位置,從而省去了del一個斷點後再在新位置添加設置新斷點的麻煩。
在DDD中還可以Redo和Undo對斷點的操作。
觀察點設置:
設置寫觀察點:watch i ; watch (i | j > 12) && i > 24 && strlen(name) > 6,這是兩種觀察點設置的方式(變量或表達式)。寫觀察點在該變量的值被程序修改後立刻中止。注意,很多平台都有硬件支持的觀察點,默認GDB是優先使用的就是這些,若暫時不可用,GDB會使用VM技術實現觀察點,這樣的好處是硬件的速度較快。
設置讀觀察點:rwatch。
設置讀寫觀察點:awatch
舉例:下列簡單程序,可以首先在main函數入口設置斷點,然後在該斷點被hit時設置觀察點。   1: #include <stdio.h>

  2:

  3: int main(int argc, char **argv)

  4: {

  5:   int x = 30;

  6:   int y = 10;

  7:

  8:   x = y;

  9:

 10:   return 0;

 11: }
這是個非常簡單的程序,在main函數入口處斷點被hit後我們可以設置rwatch x進行變量監視。


程序恢復:
單步執行:
單步跳過:n = next跳過調用方法的細節,將該行視為一行代碼進行執行。next 3表示連續執行三次next。
單步進入:s = step 進入調用方法的細節。
執行到下一斷點:c = continue,程序繼續執行直到hit下一個斷點。
執行到下一棧幀:fin = finish,程序繼續執行直到當前棧幀完成。這個常常被用來完成所謂step out的工作,在你不小心按到了step時(你本意其實是想單步跳過),你就可以使用finish跳出該方法。當然,如果你進入了一個迭代函數中的多層以內,可能一個臨時斷點+continue或者until會更加有用,後者見下文。
執行到具有更高內存地址的機器指令:u = until (後邊可以跟上funtionname/linenumber),應用的場景見下邊的代碼,在我們進入了這個循環後我們想跳出來執行循環後的代碼,此時我們當然可以在循環後的第一行代碼設置臨時斷點,然後continue到那,但這個操作會比較麻煩,最好的方式是使用until,該命令使程序繼續運行知道遇到一個具有更高內存地址的機器指令時停止,而在循環被編譯成機器指令時,會將循環條件放在循環體的最底部,所以利用until正好跳出循環進入下一機器指令(P.S. 你可以使用GCC的-s查看生成的機器指令以便更好的理解這個命令的運行方式):   1: ...previous code...

  2: int i = 9999;

  3: while (i--) {

  4:    printf("i is %d ", i);

  5:    ... lots of code ...

  6: }

  7: ...future code...
程序反向調試:
這是GDB7以後新加入的功能,如果你在調試的時候發現自己已經錯過了想調試的地方,這個操作可以使你不必重新開始調試而直接返回已經執行過的代碼進行調試。我們使用下邊一個非常簡單的程序對這個新版本的功能加以說明:   1: #include <stdio.h>

  2: void foo() {    

  3:     printf("inside foo()");    

  4:     int x = 6;    

  5:     x += 2;

  6: }

  7:

  8: int main() {    

  9:     int x = 0;    

 10:     x = x+2;    

 11:     foo();    

 12:     printf("x = %d ", x);  &n

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved