Home gdb教學一基本操作
Post
Cancel

gdb教學一基本操作

本篇文章修改參考這篇文章製作範例和教學,首先我們先寫一個有bug的程式factorial.c

基本操作

編譯程式給GDB

如果要給GDB除錯,一定要加上-g選項

1
gcc -g <any other flags e.g. -Wall> -o <file> <file.c>

開啟GDB session

在終端機輸入gdb和執行檔名稱即可開啟gdb session

1
2
3
4
5
6
7
8
9
gdb <program_name>
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from factorial...
(gdb) 

run指令

用run指令執行程式。

1
(gdb) run

如果有程式有接收參數,可以直接放在run後面

1
(gdb) run <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) run < <data>

start指令

用start指令執行程式,並且在main函式的第一行停下來。

1
(gdb) start

如果有程式有接收參數,可以直接放在run後面

1
(gdb) start <arg1> <arg2> ... <arg n>

如果要用檔案作為輸入,可以用<

1
(gdb) start < <data>

quit指令

離開GDB可以用quit指令

1
(gdb) quit

範例

以下面程式為範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {
	
	int n = 5;
	int f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);
	n = 17;
	f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);

	return 0;
		
}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
	int f = 1;
	int i = 1;
	while (i <= n) {
		f = f * i;
		i++;
	}
	return f;	
}

用以下指令編譯

1
gcc -Wall -g -o factorial factorial.c

接下來開啟GDB session

1
gdb factorial

在GDB session中執行程式,會發現程式輸出錯誤的值

1
(gdb) run

如果要結束GDB session指令可以用quit。

1
(gdb) quit

中斷點

break指令

如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>指令

1
(gdb) break <filename>:<line number>

你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>

1
(gdb) break <filename>:<function>

列出全部中斷點

要列出全部的中斷點可以用以下指令

1
(gdb) info break

delete指令

如果要刪除中斷點可以先用info break所有中斷點的號碼再刪除

1
(gdb) delete <breakpoint number>

範例

接著前面的例子,我們懷疑程式的第15行有錯誤,因此在GDB session中把中斷點放在第15行並且執行程式

1
2
(gdb) break 15
(gdb) run

我們應該會看到類似以下的輸出,你可以看到GDB輸出我們的第15行程式,代表現在程式停在第15行。 注意!!這時候程式並還沒有執行第15行。GDB告訴我們的是他下一行將要執行的程式。

1
2
3
4
5
6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
The factorial of 5 is 120.

Breakpoint 1, main () at factorial.c:15
15              f = factorial(n);

查看資料

print指令

印出變數

1
(gdb) print <variable_name>

info locals指令

印出當前所有的local variable,除了輸入函式的參數以外。

1
(gdb) info locals

info args指令

印出輸入給函式的參數

1
(gdb) info args

範例

接續前面的範例,們想查看程式運作到中斷點時變數的值。首先查看f和n的值

1
2
3
4
(gdb) print f
$1 = 120
(gdb) print n
$2 = 17

我們也可以用info locals來查看所有區域變數。

1
2
3
(gdb) info locals
n = 17
f = 120

查看程式運作

當程式在中斷點停下後,利用step, next and continue可以控制程式運作以便我們逐行觀察程式運作的情形。

step指令

執行下一行程式,如果下一行程式是呼叫function,gdb就會進到function裡面。

1
(gdb) step

next指令

執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。

1
(gdb) next

continue指令

執行程式直到碰到下一個中斷點

1
(gdb) continue

where指令

印出function call stack

1
(gdb) where

list指令

印出目前所在的行數以及前後各兩行。

1
(gdb) list

範例

接續前面的範例。我們想要查看我們的factorial函式如何運作的。因此用step指令進入factorial函式。

1
2
3
(gdb) step
factorial (n=17) at factorial.c:24
24              int f = 1;

接下來我們想一步一步看看factorial函式如和運作的

1
2
3
4
5
6
7
8
(gdb) next
25              int i = 1;
(gdb) next
26              while (i <= n) {
(gdb) next
27                      f = f * i;
(gdb) next
28                      i++;

除了不斷輸入重複的指令,你也可以直接按Enter,GDB會重複你上一個指令。
接下來我們預期執行到這裡,if應該要等於1

1
2
3
4
(gdb) print i
$2 = 1
(gdb) print f
$3 = 1

如果我們想查看目前在程式哪一行,可以用where指令來印出call stack

1
2
3
(gdb) where
#0  factorial (n=17) at factorial.c:28
#1  0x0000555555555196 in main () at factorial.c:15

如果要印出目前行數前後的程式可以用list

1
2
3
4
5
6
7
8
(gdb) list
23      int factorial(int n) {
24              int f = 1;
25              int i = 1;
26              while (i <= n) {
27                      f = f * i;
28                      i++;
29              }

我們也可以用continue指令和中斷點來加速除錯,首先先下一個中斷點在第28行。

1
2
(gdb) break 28
Breakpoint 2 at 0x5555555551e1: file factorial.c, line 28.

接下來用continue指令直接跳到這個中斷點

1
2
3
4
5
(gdb) continue
Continuing.

Breakpoint 2, factorial (n=17) at factorial.c:28
28                      i++;

然後依次印出所有區域變數

1
info locals

我們不斷重複這個動作,可以發現前面都還運作正常,直到i=13時,答案開始出出錯了,如果繼續執行會發現答案越來越小,甚至變成負的。這個錯誤原因是int這個資料型態無法儲存這麼大的值,我們必須使用更大的資料型態才能儲存。

Call Stack

call Stack是由stack frames所組成。stack frames是用來儲存呼叫函式的時候函式的區域變數。如果函式內又呼叫另一個函式,新的一個stack frames會被放當前函式的stack frames的上面。當一個函式完成後,就會移除一個stack frames。

where指令

印出call stack並且包含檔名和行數。

up指令

往上移動stack一層frame

1
2
(gdb) up
(gdb) up <n_frames>

down指令

往下移動stack一層frame

1
2
(gdb) down
(gdb) down <n_frames>

frame指令

移動到指定的frame

1
(gdb) frame <frame_number>

範例

如果錯誤出現在函式庫的程式碼,這時候用call stack來debug就會很有用,我們可以利用call stack來尋找我們的程式在什麼地方出錯。用下面範例corrupted_linked_list.c來講解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
    struct node *list1 = create_list(7);
    print_list(list1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list){
    struct node *curr = list;

    while (curr != NULL) {
        printf("%d->", curr->data);

        curr == curr->next;
    }
    printf("X\n");
}

首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。

1
2
3
4
$ gcc -g -o corrupted_linked_list corrupted_linked_list.c
./corrupted_linked_list
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->
**ctrl + c**

我們預期程式的輸出應該像下面這樣

1
2
./corrupted_linked_list
0->1->2->3->4->5->6->X

為了要了解程式到底錯在哪裡,我們在GDB session裡面執行程式。並且中斷程式

1
2
3
4
5
6
7
$ gdb corrupted_linked_list
(gdb) run
0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0
**ctrl + c**
Program received signal SIGINT, Interrupt.
0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
84      ../sysdeps/unix/syscall-template.S: No such file or directory.

中斷後我們可以用where指令看一下目前所在的位置,輸出會類似如下

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) where
#0  0x00007fffff1272c0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1  0x00007fffff0a8bff in _IO_new_file_write (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=0x6020f0, n=512) at fileops.c:1263
#2  0x00007fffff0aa409 in new_do_write (to_do=512,
    data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., fp=0x7fffff3f5620 <_IO_2_1_stdout_>) at fileops.c:518
#3  _IO_new_do_write (fp=0x7fffff3f5620 <_IO_2_1_stdout_>,
    data=0x6020f0 "0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0->0-"..., to_do=512) at fileops.c:494
#4  0x00007fffff0a947d in _IO_new_file_xsputn (f=0x7fffff3f5620 <_IO_2_1_stdout_>, data=<optimised out>, n=2) at fileops.c:1331
#5  0x00007fffff07d92d in _IO_vfprintf_internal (s=0x7fffff3f5620 <_IO_2_1_stdout_>, format=<optimised out>, ap=ap@entry=0x7ffffffedf08) at vfprintf.c:1663
#6  0x00007fffff085899 in __printf (format=<optimised out>) at printf.c:33
#7  0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
#8  0x0000000000400628 in main () at corrupted_linked_list.c:17

可以看到程式被中斷在標準函式庫的程式,不過我們想看一看輸入函式庫的參數是什麼。因此我們可以用up指令從frame 0 移動到 frame 1。或者我們直接用frame指令移動到我們要的地方

1
2
3
(gdb) frame 7
#7  0x000000000040071b in print_list (list=0x602010) at corrupted_linked_list.c:50
50              printf("%d->", curr->data);

首先我們先看一下區域變數

1
2
(gdb) info locals
curr = 0x602010

可以用ptype指令查看curr的型態,可以發現他是一個node struct的指針。

1
2
3
4
5
(gdb) ptype curr
type = struct node {
    int data;
    struct node *next;
} *

我們dereference查看一下內容

1
2
(gdb) print *curr
$1 = {data = 0, next = 0x602030}

也可以查看其他的內容

1
2
3
4
5
(gdb) print *(curr->next)
$2 = {data = 1, next = 0x602050}
(gdb) print *(curr->next->next)
$3 = {data = 2, next = 0x602070}
(gdb)

Core Dumps

程式當機當下程式的狀態對於除錯十分有幫助,我們可以利用core dump檔來記錄這些狀態。對於一些不定時發生的錯誤,這些除錯資訊就十分珍貴了。

Core Dumps設定

首先查看Core Dumps記錄功能有沒有被開啟,如果回傳0代表沒有打開Core Dumps記錄功能

1
ulimit -c

用以下指令設定打開Core Dumps記錄功能

1
ulimit -c unlimited

通常Core Dumps檔產生的位置會記路在/proc/sys/kernel/core_pattern這個設定,用以下指令查看Core Dumps檔的位置。

產生Core Dump

當Core Dumps紀錄功能打開後,如果程式遇到Segmentation fault的錯誤,就會產生Core Dump檔。

用GDB查看Core Dump檔

要查看Core Dump檔可以用以下指令。注意當我們利用Core Dump檔來除錯的時候,程式實際上並沒有在運作,所以step, nextcontinue 這些指令這時候是沒有功能的。

1
gdb <binary-file> <core-dump-file>

範例

利用下面範例broken_linked_list.c將說明如何使用Core Dump除錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list, int length);

int main(void){
    int length1 = 7;
    struct node *list1 = create_list(length1);
    print_list(list1, length1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list, int length){
    struct node *curr = list;
    int i = 0;
    while (i <= length) {
        printf("%d->", curr->data);
        curr = curr->next;
        i++;
    }
    printf("X\n");
}

編譯並且執行程式,可以看到程式出現Segmentation fault (core dumped)錯誤

1
2
3
$ gcc -g -o broken_linked_list broken_linked_list.c
$ ./broken_linked_list
Segmentation fault (core dumped)

接下來讀取Core Dump檔

1
gdb broken_linked_list core

GDB將會顯示程式出錯的位置

1
2
3
4
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000055be9593e283 in print_list (list=0x55be96c20260, length=7)
    at broken_linked_list.c:51
51          printf("%d->", curr->data);

從這裡可以知道我們在第51行正在嘗試存取一個非法的記憶體位置。因此我們可以推測可能是curr的位址不正確或者是data是不可以讀取的位址。於是先嘗試印出curr的值

1
2
(gdb) print curr
$1 = (struct node *) 0x0

可以看到curr是一個NULL址針。接下來我們在印出當前狀況的區域變數

1
2
3
(gdb) info locals
curr = 0x0
i = 7

可以看到i是7,也就是現在是第8次執行for迴圈,但是我們的Linking list只有7個節點,而且依照我們的建立Linking list的方式,第8個節點會是NULL址針,所以程式會出錯。 我們在檢查一下Linking list本身是否有問題。

1
2
(gdb) print *list
$2 = {data = 0, next = 0x55be96c20280}

可以看到Linking list的位址沒問題,因此可以更加確定問題就是迴圈多執行了一次。我們可以用list指令看一下程式現在所在的位置。

1
2
3
4
5
6
7
8
9
10
11
(gdb) list
46
47  void print_list(struct node *list, int length){
48      struct node *curr = list;
49      int i = 0;
50      while (i <= length) {
51          printf("%d->", curr->data);
52          curr = curr->next;
53          i++;
54      }
55      printf("X\n");

GDB Init File

如果單純使用GDB的指令,有些變數就會變得難以查看,例如如果想要查看linked list的所有成員就會變得很麻煩。而GDB提供讓使用者自定義指令讓我們可以容易議處想要的結果。

GDB User Initialization File

GEB啟動時會載入User Initialization File所記錄的指令,你可以建立一個新的User Initialization File,他的檔名是.gdbinit,放置的位置是home directory

1
~/.gdbinit

建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit

GDB

專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。

1
~/<file_path>/.gdbinit

基礎用法

例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit寫入下面這行。

1
break <function_name>

GDB腳本語法

定義命令

你可以用以下命令定義一個自訂義命令

1
2
3
define <command>
    <code>
end

增加命令說明

你可以用以下命令為自訂義命令增加註解

1
2
3
document <command>
    <information about the command>
end

如果要查看命令說明可以用以下命令

1
2
(gdb) help <command>
<information about the command>

命令參數

如果需要傳入參數給自訂義命令可以用以下方式

1
(gdb) <command> <arg0> <arg1> <arg2> ...

如果要在指令內使用參數可以用以下方式

1
2
3
4
5
$argc
$arg0
$arg1
$arg2
...

方便的內建變數

可以用以下方式定義

1
$<variable_name>

設定變數

用以下指令設定變數

1
set $<variable_name> = <value_or_expression>

if 聲明

用以下方式可以做if聲明

1
2
3
4
5
if <condition>
    <code>
else
    <code>
end

while loop

1
2
3
while <condition>
    <code>
end

Printing

GDB的print語法跟c十分相似

1
printf "<format string>", <arg0>, <arg1>, ...

使用自訂義命令

使用者自訂義命令的用法跟內建指令的用法相同。

1
(gdb) <command> <arg0> <arg1> <arg2> ...

範例:除錯Linked lists

下面將以除錯Linked list作為範例linked_list.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Makes a linked list of length 7 and prints it out
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


struct node {
    int data;
    struct node *next;
};

struct node *create_node(int data);
struct node *create_list(int length);
void print_list(struct node *list);

int main(void){
    struct node *list1 = create_list(7);
    print_list(list1);

    return 0;
}

struct node *create_node(int data){
    struct node *new = malloc(sizeof(struct node));
    assert(new != NULL);
    new->data = data;
    new->next = NULL;
    return new;
}

struct node *create_list(int length) {

    struct node *head = NULL;
    if (length > 0) {
        head = create_node(0);
        int i = 1;
        struct node *curr = head;
        while (i < length) {
            curr->next = create_node(i);
            curr = curr->next;
            i++;
        }
    }
    return head;
}

void print_list(struct node *list){
    struct node *curr = list;

    while (curr != NULL) {
        printf("%d->", curr->data);
        curr = curr->next;
    }
    printf("X\n");
}

首先先把下面這行加入~/.gdbinit

1
set auto-load safe-path /

接下來撰寫自訂義命令,在專案根目錄也建立一個.gdbinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define p_generic_list
  set var $n = $arg0
  while $n
    print *($n)
    set var $n = $n->next
  end
end

document p_generic_list
        p_generic_list LIST_HEAD_POINTER
        Print all the fields of the nodes in the linked list pointed to by LIST_HEAD_POINTER. Assumes there is a next field in the struct.
end



define indentby
    printf "\n"
    set $i_$arg0 = $arg0
    while $i_$arg0 > 10
        set $i_$arg0 = $i_$arg0 - 1
        printf "%c", ' '
    end
end

在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針

1
set var $n = $arg0

接下來印出 linked list的內容。

1
print *($n)

接下來把把linked list的指針指向下一個元素

1
set var $n = $n->next

運行結果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc -g -o linked_list linked_list.c
$ gdb -q ./linked_list
Reading symbols from ./linked_list...
(gdb) br 18
Breakpoint 1 at 0x11c3: file linked_list.c, line 18.
(gdb) r
Starting program: /home/steven/tmp/gcc_practice/linked_list 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at linked_list.c:18
18          print_list(list1);
(gdb) p_generic_list list1
$1 = {data = 0, next = 0x5555555592c0}
$2 = {data = 1, next = 0x5555555592e0}
$3 = {data = 2, next = 0x555555559300}
$4 = {data = 3, next = 0x555555559320}
$5 = {data = 4, next = 0x555555559340}
$6 = {data = 5, next = 0x555555559360}
$7 = {data = 6, next = 0x0}
(gdb) 

watch and display

watch可以監看一個變數,每當這個變數數值改變的時候就暫停程式。display 則是每當程式停止的時候顯示變數。

watch

使用以下指令設定想要監看的變數

1
(gdb) watch <variable_name>

查看Watchpoints

查看Watchpoints的方式跟查看breakpoints的方式一樣

1
(gdb) info breakpoints

移除Watchpoints

使用以下指令移除Watchpoints

1
(gdb) disable <watchpoint_number>

display

使用以下指令設定display

1
(gdb) display expression

查看所有display

查看所有display

1
(gdb) info display

移除display

用以下指令移除display

1
(gdb) delete display <display_number>

範例

以下將以計算階乘的程式factorial.c來做示範。程式在計算階層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {
	
	int n = 5;
	int f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);
	n = 17;
	f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);

	return 0;
		
}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
	int f = 1;
	int i = 1;
	while (i <= n) {
		f = f * i;
		i++;
	}
	return f;	
}

接著編譯程式並且用gdb讀取

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

首先先設定中斷點,可以看到n=5的時候晟是正常運作,讓嘗試繼續執行讓n=17

1
2
3
4
5
6
7
8
9
10
11
(gdb) br factorial
Breakpoint 1 at 0x11a5: file factorial.c, line 24.
(gdb) r
Starting program: ~/factorial
Breakpoint 1, factorial (n=5) at factorial.c:24
24          int f = 1;
(gdb) c
Continuing.
The factorial of 5 is 120.
Breakpoint 1, factorial (n=17) at factorial.c:24
24          int f = 1;

接下來設定watch和display,我們希望i初始化之後再設定watch和display,

1
2
3
4
(gdb) n
25          int i = 1;
(gdb) n
26          while (i <= n) {

然後設定watch和display

1
2
3
4
(gdb) watch f
Hardware watchpoint 2: f
(gdb) display i
1: i = 1

然後我們就可以觀察程式計算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 1
New value = 2
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 2
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 2
New value = 6
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 3
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 6
New value = 24
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 4
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 24
New value = 120
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 5
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 120
New value = 720
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 6
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 720
New value = 5040
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 7
(gdb) c

Continuing.

Hardware watchpoint 2: f

Old value = 5040
New value = 40320
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 8
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 40320
New value = 362880
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 9
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 362880
New value = 3628800
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 10
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 3628800
New value = 39916800
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 11
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 39916800
New value = 479001600
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 12
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 479001600
New value = 1932053504
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 13

我們可以觀察到當n=13的時候程式就開始出錯。

條件式Breakpoints

利用conditional breakpoints可以讓程式達到特定條件的時候才停下來。

設定條件式Breakpoints的方法

  1. 建立一個中斷點 如同前面所教的方法先建立一個中斷點。
    1
    2
    
    (gdb) break <file_name> : <line_number>
    (gdb) break <function_name>
    
  2. 查看所有中斷點 下面指令可以查看目前已經設定過的中斷點。
    1
    
    (gdb) info breakpoints
    
  3. 設定條件 首先我們必須要知道中斷點的編號,並且用以下指令設定條件
    1
    
    (gdb) condition <breakpoint_number> condition
    
  4. 移除中斷點的停止條件 如果想要移除中斷點的停止條件,可以用以下指令
    1
    
    (gdb) condition <breakpoint_number>
    

範例

下面將繼續沿用階層計算程式factorial.c來示範conditional breakpoints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


#include <stdio.h>
#include <stdlib.h>

int factorial(int n);

int main(void) {
	
	int n = 5;
	int f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);
	n = 17;
	f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);

	return 0;
		
}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
	int f = 1;
	int i = 1;
	while (i <= n) {
		f = f * i;
		i++;
	}
	return f;	
}

編譯程式並啟動GDB

1
2
3
$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

設定conditional breakpoints

我們已經知道程式在i <= 5 之前都正常運作,所以我們不必在確認i <= 5 之前的輸出結果。因此我們設定的條件是 i > 5。

1
2
3
4
5
$ gdb factorial
Reading symbols from factorial...done.
(gdb) br 28
Breakpoint 1 at 0x11bf: file factorial.c, line 28.
(gdb) condition 1 i > 5

開始除錯

接下來就可以執行程式並且觀察不同i之下的輸出變化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
(gdb) r
Starting program: ~/factorial
The factorial of 5 is 120.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 720
i = 6
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 5040
i = 7
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 40320
i = 8
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 362880
i = 9
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 3628800
i = 10
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 39916800
i = 11
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 479001600
i = 12
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 1932053504
i = 13
(gdb)

我們可以發現i=13之後數值就開始出現異常了。

除錯shared library

gcc印出所使用到的shared library

1
gcc -Wl,-t your_program.c -o your_program > ld_output.txt

gdb查看被載入的shared library

1
info share

每當有新的shared library被載入的時候就暫停

set stop-on-solib-events 1

https://jasonblog.github.io/note/gdb/li_yong_gdb_jin_xing_shared_library_de_chu_cuo.html

參考:
https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_basic_use/

This post is licensed under CC BY 4.0 by the author.

Python檢查記憶體用量

gdb教學二顯示Matrix或是Tensor