Linux下gdb調試(tui)


1 處於TUI模式的GDB

為了以TUI模式運行GDB,可以在調用GDB時在命令行上指定-tui選項,或者處於非TUI模式時在GDB中使用Ctrl+X+A組合鍵。如果當前處於TUI模式,后一種命令方式就會使你離開TUI模式。

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

例如:

源代碼為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.c

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

使用GDB調試insert_sort

 

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

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

2)執行run命令運行程序,並且向該程序傳遞命令行參數12、5、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輸出的第一個值。($1、$2、$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>}

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM