Linux下gdb調試(tui)

1 處於TUI模式的GDB

爲了以TUI模式運行GDB,能夠在調用GDB時在命令行上指定-tui選項,或者處於非TUI模式時在GDB中使用Ctrl+X+A組合鍵。若是當前處於TUI模式,後一種命令方式就會使你離開TUI模式。程序員

在TUI模式中,GDB窗口劃分爲兩個子窗口——一個用於輸入GDB命令,而另外一個用於查看源代碼。數組

例如:app

源代碼爲ins.c函數

#include <stdio.h>

int x[10],
        y[10], 
        num_inputs,                                     
        num_y = 0;


void get_args(int ac,char **av){
        int i;
        num_inputs = ac - 1;
        for(i = 0;i < num_inputs;++i)
                x[i] = atoi(av[i + 1]);
}

void scoot_over(int jj){
        int k;
        for(k = num_y;k > jj;--k)
                y[k] = y[k - 1];
}

void insert(int new_y){
        int j;
        //
        if(num_y==0){
                y[0] = new_y;
                return;
        }

        for(j = 0;j < num_y;++j){
                if(new_y < y[j]){
                        scoot_over(j);
                        y[j] = new_y;
                        return;
                }
        }
        y[num_y]=new_y;
}

void process_data(){
        for(num_y = 0;num_y < num_inputs;++num_y)
                insert(x[num_y]);
}

void print_results(){
        int i;
        for(i = 0;i < num_inputs;++i)
                printf("%d\n",y[i]);
}

int main(int argc,char **argv){
        get_args(argc,argv);
        process_data();
        print_results();
}

編譯後:工具

gcc -g3 -Wall -o insert_sort ins.cui

注意,在GCC中能夠用-g選項讓編譯器將符號表(即對應於程序的變量和代碼行的內存地址列表)保存在生成的可執行文件(這裏是insert_sort)中。這是一個絕對必要的步驟,這樣才能在調試會話過程當中引用源代碼中的變量名和行號。spa

使用GDB調試insert_sort命令行

 

若是正在使用GDB但沒有使用TUI模式,則位於下方的子窗口確切地顯示你將看到的內容。此處,該子窗口顯示以下內容。3d

1)發出一條break命令,在當前源文件第12行處設置斷點。調試

2)執行run命令運行程序,而且向該程序傳遞命令行參數十二、五、6.在此以後,調試器在指定的斷點處中止執行。GDB會提醒用戶斷點位於ins.c的第12行,而且通知該源代碼行的機器代碼駐留在內存地址0xbffff484中。

3)發出next命令,步進到下一個代碼行,即第13行。

2 主要的調試操做

退出GDB:quit或者Ctrl+d

執行程序:run

2.1 單步調試源代碼

安排程序的執行在某個地方暫停,以便檢查變量的值,從而獲得關於程序錯誤所在位置的線索。

  • 斷點

調試工具會在指定斷點處暫停程序的執行。在GDB中是經過break命令及其行號完成的。

普通斷點和條件斷點

(gdb) break 30

Breakpoint 1 at 0x80483fc: file  ins.c,line 30.

(gdb) condition 1 num_y==1

第一個命令在第30行放置一個斷點。這裏的第二個命令condition 1 num_y==1使得該斷點稱爲有條件的斷點:只有當知足條件num_y==1時,GDB纔會暫停程序的執行。

注意,與接受行號(或函數名)的break命令不一樣,condition接受斷點號。老是能夠用命令info  break來查詢要查找的斷點的編號。

用break if能夠將break和condition命令組合成一個步驟,以下所示:

(gdb) break 30 if num_y==1

 

  • 單步調試

前面提到過,在GDB中next命令會讓GDB執行下一行,而後暫停。step命令的做用與此類型,只是函數調用時step命令會進入函數,而next致使程序執行的暫停出如今下次調用函數時。

  • 恢復操做

在GDB中,continue命令通知調試器恢復執行並繼續,直到遇到斷點爲止。

  • 臨時斷點

在GDB中,tbreak命令與break類似,可是這一命令設置的斷點的有效期限只到首次到達指定行時爲止。

2.2 檢查變量

(gdb) print j

$1=1

對GDB的這一查詢的輸出代表j的值爲1.$1標籤意味着這是你要求GDB輸出的第一個值。($一、$二、$3等表示的值統稱爲調試會話的值歷史。)

2.3 在GDB中設置監視點以應對變量值的改變

監視點結合了斷點和變量檢查的概念。最基本形式的監視點通知調試器,每當指定變量的值發生變化時都暫停程序的執行

(gdb) watch z

當運行程序時,每當z的值發生變化,GDB都會暫停執行。

更好的方法是,能夠基於條件表達式來設置監視點。例如,查找執行期間z 的值大於28的第一個位置

(gdb) watch(z>28)

2.4 上下移動調用棧

在函數調用期間,與調用關聯的運行時信息存儲在稱爲棧幀的內存區域中。幀中包含函數的局部變量的值、其形參,以及調用該函數的記錄。每次發生函數調用時,都會建立一個新幀,並將其推導一個系統維護的棧上;棧最上方的幀表示當前正在執行的函數,當函數退出時,這個幀被彈出棧,而且被釋放。

在GDB中可用用以下命令查看之前的幀:

(gdb) frame 1

當執行GDB的frame命令時,當前正在執行的函數的幀被編號爲0,其父幀(即該函數的調用者的棧幀)被編號爲1,父幀的父幀被編號爲2,以此類推。GDB的up命令將你帶到調用棧中的下一個父幀(例如,從幀0到幀1),down則引向相反方向。

顯示整個棧:backtrace

瀏覽之前的GDB命令:上一個Ctrl+P、下一個Ctrl+N

3 聯機幫助

在GDB中,能夠經過help命令訪問文檔。例如:

(gdb) help breakpoints

4 啓動文件的使用

在從新編譯代碼時,最好不要退出GDB。這樣,你的斷點和創建的其餘各類動做都會保留。要是退出GDB,就不得再也不次重複鍵入這些內存。

然而,在完成調試前可能須要退出GDB。若是你要離開一段時間,並且不能保持登陸在計算機中,則須要退出GDB。爲了避免丟失它們,能夠將斷點和設置的其餘命令放在一個GDB啓動文件中,而後每次啓動GDB時都會自動加載它們。

GDB的啓動文件默認名爲.gdbinit。

在調用GDB時能夠指定啓動文件。例如,

$ gdb -command=z x

表示要在可執行文件x上運行GDB,首先要從文件z中讀取命令。

5 gdb暫停機制

有3種方式能夠通知GDB暫停程序的執行。

1)斷點:通知GDB在程序中的特定位置暫停執行。

2)監視點:通知GDB當特定內存位置的值發生變化時暫停執行

3)捕獲點:通知GDB當特定事件發生時暫停執行。

GDB中使用delete命令刪除斷點:

(gdb) help delete

5.1 斷點概述

GDB中關於斷點「位置」的含義很是靈活,它能夠指各類源代碼行、代碼地址、源代碼文件中的行號或者函數的入口等。

例如:

break 35

這裏指GDB執行到第34行,可是第35行尚未執行。斷點顯示的是將要執行的代碼行。

5.2 跟蹤斷點

程序員建立的每一個斷點(包括斷點、監視點和捕獲點)都被標識爲從1開始的惟一整數標識符。這個標識符用來執行該斷點上的各類操做。

5.2.1 GDB中的斷點列表

當建立斷點時,GDB會告知你分配給該斷點的編號。例如,

 

(gdb) break main
Breakpoint 1 at 0x8048569: file ins.c, line 52.

被分配的編號是1.若是忘記了分配給哪一個斷點的編號是什麼可使用info  breakpoints命令來提示。

(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048569 in main at ins.c:52
2 breakpoint keep y 0x0804847e in insert at ins.c:25
3 breakpoint keep y 0x08048491 in insert at ins.c:30

若是想要刪除斷點,能夠經過delete命令以及斷點標識符,例如

(gdb) delete 1 2 3

5.3 設置斷點

5.3.1 在GDB中設置斷點

1)break function

在函數function()的入口處設置斷點。

(gdb) break main

在main函數的入口處設置斷點

2)break line_number

在當前活動源代碼文件的line_number處設置斷點。

(gdb) break 35

在當前顯示的源文件的35行設置了一個斷點。

3)break filename:line_number

在源代碼文件filename的line_number處設置斷點。若是filename不在當前工做目錄中,則能夠給出相對路徑名或者徹底路徑名來幫助GDB查找該文件,例如:

(gdb) break source/bed.c:35

4)break filename:function

在文件filename中的函數function()的入口處設置斷點。重載函數或者使用同名靜態函數的程序可能須要使用這種形式,例如:

(gdb) break bed.c:parseArguments

正如咱們看到的,當設置一個斷點時,該斷點的有效性會持續到刪除、禁用或者退出GDB時。然而,臨時斷點時首次到達後就會被自動刪除的斷點。臨時斷點使用tbreak命令設置。

C++容許重載函數,使用break function會在全部具備相同名稱的函數上設置斷點。若是要在函數的某個特定實例上設置斷點,須要沒有歧義,則使用源文件中的行號。

 

GDB實際設置斷點的位置可能和咱們請求將斷點放置的位置不一樣。

 

好比下列代碼:
int main(void)  
{  
    int i;  
    i = 3;  
  
   return 0;  
}  

若是咱們嘗試在函數main入口處設置斷點,斷點實際會被設置在第4行。由於GDB會認爲第三行的機器碼對咱們的調試目的來講沒有用處。

5.4 多文件中的斷點設置

例如:

main.c

#include<stdio.h>
void swap(int *a,int *b);

int main()
{
    int i=3;
    int j=5;
    printf("i:%d,j:%d\n",i,j);
    swap(&i,&j);
    printf("i:%d,j:%d\n",i,j);

    return 0;
}

swap.c

void swap(int *a,int *b)
{
    int c=*a;
    *a=*b;
    *b=c;
}

在main上設置斷點:

(gdb) break main
Breakpoint 1 at 0x80483cd: file main.c, line 6.

在swap上設置斷點的方法:

(gdb) break swapper.c:1
Breakpoint 2 at 0x804843a: file swapper.c, line 1.
(gdb) break swapper.c:swap
Note: breakpoint 2 also set at pc 0x804843a.
Breakpoint 3 at 0x804843a: file swapper.c, line 3.
(gdb) break swap
Note: breakpoints 2 and 3 also set at pc 0x804843a.
Breakpoint 4 at 0x804843a: file swapper.c, line 3.

每一個GDB都有一個焦點,能夠將它看做當前「活動」文件。這意味着除非對命令作了限定,不然都是在具備GDB的焦點的文件上執行命令。默認狀況下,具備GDB的初始焦點的文件是包含main()函數的文件,可是當發生以下任一動做時,焦點會轉移到不一樣的文件上。

1)向不一樣的源文件應用list命令

2)進入位於不一樣的源代碼文件中的代碼

3)當在不一樣的源代碼文件中執行代碼時GDB遇到斷點

例如:

(gdb) break 6
Note: breakpoint 1 also set at pc 0x80483cd.
Breakpoint 5 at 0x80483cd: file main.c, line 6.

當前焦點是main.c,因此在main.c中設置。

(gdb) list swap
(gdb) break 6
Breakpoint 6 at 0x8048454: file swapper.c, line 6.

如今的焦點是swapper.c。

 

(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483cd in main at main.c:6
2 breakpoint keep y 0x0804843a in swap at swapper.c:1
3 breakpoint keep y 0x0804843a in swap at swapper.c:3
4 breakpoint keep y 0x0804843a in swap at swapper.c:3
5 breakpoint keep y 0x080483cd in main at main.c:6
6 breakpoint keep y 0x08048454 in swap at swapper.c:6

5.5 斷點的持久性

若是在修改和從新編譯代碼時沒有退出GDB,那麼在下次執行GDB的run命令時,GDB會感知到代碼已修改,並自動從新加載新版本。

5.6 刪除和禁用斷點

在調試會話期間,有時會發現有的斷點再也不使用。若是確認再也不須要斷點,能夠刪除它。也許你不想刪除它,而是打算將它虛置起來,這稱爲禁用斷點。若是之後再次須要,能夠從新啓用斷點。

2.6.1 在GDB中刪除斷點

若是確認再也不須要當前斷點,那麼能夠刪除該斷點。

delete命令用來基於標識符刪除斷點,clear命令使用和建立斷點的語法刪除相同。

1)delete breakpointer_list

刪除斷點使用數值標識符。斷點能夠是一個數字,好比delete 2 刪除第2個斷點;也能夠是數字列表,否則delete 2 4 刪除第二個和第四個斷點。

2)delete

刪除全部斷點。

3)clear

清除GDB將執行的下一個指令處的斷點。這種方法適用於要刪除GDB已經到達的斷點額狀況。

4)clear function、clear filename:function、clear line_number和clear filename:line_number

2.6.2 在GDB中禁用斷點

每一個斷點均可以禁用和啓用。只有遇到啓用的斷點時,纔會暫停程序的執行;它會忽略禁用的斷點。

爲何要禁用斷點呢?在調試會話期間,會遇到大量斷點。對於常常重複的循環結構或函數,這種狀況使得調試極不方便。若是要保留斷點以便之後使用,暫時又不但願GDB中止執行,能夠禁用它們,在之後須要時再啓用。

使用disable breakpoint-list命令禁用斷點,使用enable breakpoint-list命令啓用斷點。

例如,

(gdb) disable 3

將禁用第三個斷點

(gdb) enable 1 5

將啓用第一個和第五個斷點。

不帶任何參數地執行disable命令將禁用全部現有斷點。相似的,不帶任何參數的enable命令將啓用全部斷點。

還有一個enable once命令,在獲得下次引發GDB暫停執行後被禁用。語法爲:

enable once breakpoint-list

例如,enable once 3 會使得斷點3 在下次致使GDB中止程序的執行後被禁用。這個命令與tbreak命令很是相似,可是當遇到斷點時,它是禁用斷點,而不是刪除斷點。

2.6.3 瀏覽斷點屬性

info breakpoints命令(簡寫 i b)來得到設置的全部斷點的清單,以及它們的屬性。

例如:

 

(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483cd in main at main.c:6
2 breakpoint keep y 0x0804843a in swap at swapper.c:1
3 breakpoint keep y 0x0804843a in swap at swapper.c:3
4 breakpoint keep y 0x0804843a in swap at swapper.c:3
5 breakpoint keep y 0x080483cd in main at main.c:6
6 breakpoint keep y 0x08048454 in swap at swapper.c:6

7 hw watchpoint keep y                         counter

 

讓咱們分析info breakpoints的這一輸出:

1)標識符(num):斷點的惟一標識符

2)類型(type):這個字段指出該斷點是斷點、監視點仍是捕獲點

3)部署(disp):每一個斷點都有一個部署,指示斷點下次引發GDB暫停程序的執行後該斷點上會發生什麼事情。

保持(keep),下次到達斷點後不改變斷點

刪除(del),下次到達斷點後刪除斷點,臨時斷點(tbreak設置)

禁用(dis),下次到達後會禁用斷點,使用enable once命令設置的

4)啓用狀態(enb):這個字段說明斷點當前是啓用仍是禁用的

5)地址(Address):這是內存中設置斷點的位置。

6)位置(what):what字段顯示了斷點所在的位置的行號和文件名

6 恢復執行

恢復執行的方法有3類。第一類是使用step和next「單步」調試程序,僅執行代碼的下一行而後再次暫停。第二類由使用continue組成,使GDB無條件地恢復程序的執行,直到遇到另外一個斷點或程序結束。最後一類方法涉及條件:用finish或until命令恢復。在這種狀況下,GDB會恢復執行;程序繼續運行直到遇到某個預先肯定的條件(好比,到達函數的末尾),到達另外一個斷點,或者程序完成。

6.1 使用step和next單步調試

一旦GDB在斷點處中止,可使用next(簡寫n)和step(簡寫s)命令來單步調試代碼。

這兩個命令的不一樣之處在於它們如何處理函數調用:next執行函數,不會在其中暫停,而後在調用以後的第一條語句處暫停。而step在函數中的第一個語句處暫停。step命令會進入調用的函數,這稱爲單步進入函數。而next永遠不會離開main()。這是兩個命令的主要區別。next將函數調用看作一行代碼,並在一個操做中執行整個函數,這稱爲單步越過函數。

然而,彷佛next越過調用的函數主體,可是它並未真的單步「越過」任何內容。GDB安靜地執行調用函數的每一行,不向咱們展現細節。

 

6.2 使用continue恢復程序執行

第二種恢復執行的方法是使用continue命令,簡寫爲c。這個命令使GDB恢復程序的執行,直到觸發斷點或者程序結束。

continue命令能夠接受一個可選的整數參數n。這個數字要求GDB忽略下面n個斷點。例如,continue 3讓GDB恢復程序執行,並忽略接下來的3個斷點。

6.3 使用finish恢復程序執行

一旦觸發了斷點,就使用next和step命令逐行執行程序。有時這是一個痛苦的過程。

有時使用step進入的調用的函數,查看了幾個變量的信息,若是沒有興趣單步調試其他部分,想返回到單步進入被調用函數以前GDB所在的調用函數。然而,若是要作的只是跳過函數的其他部分,那麼再設置一個無關斷點並使用continue彷佛比較浪費。這是可使用finish命令。

finish命令(簡寫爲fin)指示GDB恢復執行,直到剛好在當前棧幀完成以後爲止。也就是說,這意味着若是你在一個不是main()的函數中,finish命令會致使GDB恢復執行,直到剛好在函數返回以後爲止。

雖然能夠鍵入next 3 而不是finish,可是後者更容易。

finish的另外一個常見用途是當不當心單步進入本來但願單步越過的函數時(換言之,當須要使用next時使用了step)。在這種狀況下,使用finish能夠講你正好放回到使用next會位於的位置。

若是在一個遞歸函數中,finish只會將你帶到遞歸的上一層。

6.4 使用until恢復程序執行

finish命令在不進一步在函數中暫停(除了中間斷點)的狀況想完成當前函數的執行。相似地,until命令(簡寫爲u)一般用來在不進一步在循環中暫停(除了循環中的中間斷點)的狀況下完成正在執行的循環。

 

 

當i很大是,使用next須要屢次。而使用until會執行循環的其他部分,讓GDB在循環後面的第一行代碼處暫停。固然,若是GDB在離開循環前遇到一個斷點,它就會在那裏暫停。

7 條件斷點

只要啓用了斷點,調試器就老是在該斷點處中止。然而,有時有必要告訴調試器只有當符合某種添條件時纔在斷點處中止。

7.1 設置條件斷點

break break-args if (condition)

其中brea-args是能夠傳遞給break以指定斷點位置的任何參數。括着condition的圓括號是可選的。

例如:

break main if argc>1

例如,在循環中,知足必定次數以後發生中斷:

break if (i==7000) 

條件中斷中的condition能夠包含以下形式,可是必須是布爾值:

 

能夠對正常斷點設置條件以將它轉變爲條件斷點。例如,若是設置了斷點3爲無條件斷點,可是但願添加添加i==3,只有鍵入:

(gdb) cond 3 i==3

若是之後要刪除條件,可是保持該斷點,只要鍵入:

(gdb) cond 3

8 斷點命令列表

當GDB遇到斷點時,幾乎老是要查看某個變量。若是反覆遇到同一個斷點,將反覆查看相同的變量。讓GDB在每次到達某個斷點時自動執行一組命令,從而自動完成這一過程。

事實上,使用「斷點命令列表」就能夠作這件事。

使用commands命令設置命令列表。

其中breakpoint-number是要將命令添加到其上的斷點的標識符,commands是用行分隔的任何有效GDB命令列表。逐條輸入命令,而後鍵入end表示輸入命令完畢。從那之後,每當GDB在這個斷點處中斷時,它都會執行輸入的任何命令。

例如:

fibonacci.c

#include<stdio.h>
int fibonacci(int n);

int main(void)
{
    printf("Fibonacci(3) is %d\n",fibonacci(3));

    return 0;
}

int fibonacci(int n)
{
    if(n<=0||n==1)
        return 1;
    else
        return fibonacci(n-1)+fibonacci(n-2);
}

gdb調試:

 

若是以爲輸出太冗長了,可使用silent命令使GDB更安靜地觸發斷點。

 

如今輸出結果不錯,可是每次要鍵入continue,能夠修改以下:

也可使用define定義宏來代替:

 

9 監視點

監視點是一種特殊類型的斷點,它相似於正常斷點,是要求GDB暫停程序執行的指令。監視點是指示GDB每當某個表達式改變了值就暫停執行的指令。

(gdb) watch i

它會使得每當i改變值時GDB就暫停。

9.1 設置監視點

當變量var存在且在做用域中時,能夠經過使用以下命令來設置監視點

watch  var

該命令會致使每當var改變值時GDB都中斷。

例如:

#include<stdio.h>
int i=0;

int main()
{
    i=3;
    printf("i is %d.\n",i);

    i=5;
    printf("i is %d.\n",i);

    return 0;
}

咱們每當i大於4時獲得通知。所以在main()的入口處放一個斷點,以便讓i在做用域中,並設置一個監視點以指出i什麼時候大於4.不能在i上設置監視點,由於在程序運行以前,i不存在。所以必須如今main()上設置斷點,而後在i上設置監視點

既然i已經在做用域中了,如今設置監視點並通知GDB繼續執行程序。

 

10 顯示數值中的值

好比聲明數組:

int x[25];

方法是經過鍵入:

(gdb) p x

可是,若是是動態建立的數組會是什麼樣呢?好比:

int *x

...

x=(int *)malloc(25*sizeof(int));

若是要在GDB中輸出數組,就不能輸入:

(gdb) p x

能夠簡單打印數組地址。或者鍵入:

(gdb) p *x

這樣只會輸出數組的一個元素——x[0]。仍然能夠像在命令 p x[5]中那樣輸入單個元素,可是不能簡單地在x上使用print命令輸出整個數組。

1)在GDB的解決方案

在GDB中,能夠經過建立一我的工數組來解決這個問題。以下:

#include<stdio.h>
#include<stdlib.h>
int *x;
void main()
{
    x=(int*)malloc(25*sizeof(int));
    x[3]=12;
}

而後執行:

 

咱們能夠看到,通常形式爲:

*pointer@number_of_elements

GDB還容許在適當的時候使用類型強制轉換,好比:

(gdb) p (int [25])*x

$2={0,0,0,12,0 <repeats 21 times>}

相關文章
相關標籤/搜索