本篇文章修改參考這篇 文章製作範例和教學,首先我們先寫一個有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指令執行程式。
如果有程式有接收參數,可以直接放在run後面
1 (gdb) run <arg1> <arg2> ... <arg n>
如果要用檔案作為輸入,可以用<
start指令 用start指令執行程式,並且在main函式的第一行停下來。
如果有程式有接收參數,可以直接放在run後面
1 (gdb) start <arg1> <arg2> ... <arg n>
如果要用檔案作為輸入,可以用<
quit指令 離開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 #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 ; } 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
在GDB session中執行程式,會發現程式輸出錯誤的值
如果要結束GDB session指令可以用quit。
中斷點 break指令 如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>
指令
1 (gdb) break <filename>:<line number>
你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>
1 (gdb) break <filename>:<function>
列出全部中斷點 要列出全部的中斷點可以用以下指令
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,除了輸入函式的參數以外。
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裡面。
next指令 執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。
continue指令 執行程式直到碰到下一個中斷點
where指令 印出function call stack
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會重複你上一個指令。 接下來我們預期執行到這裡,i
和f
應該要等於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++;
然後依次印出所有區域變數
我們不斷重複這個動作,可以發現前面都還運作正常,直到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 #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" ); }
{:file=’corrupted_linked_list.c’}
首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。
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記錄功能
用以下指令設定打開Core Dumps記錄功能
通常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
, next
和 continue
這些指令這時候是沒有功能的。
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 #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" ); }
{: file=’broken_linked_list.c’} 編譯並且執行程式,可以看到程式出現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
建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit
GDB 專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。
基礎用法 例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit
寫入下面這行。
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 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"); }
{:file=’linked_list.c’}
首先先把下面這行加入~/.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
{:file=’.gdbinit’}
在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針
接下來印出 linked list的內容。
接下來把把linked list的指針指向下一個元素
運行結果如下
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的方式一樣
移除Watchpoints 使用以下指令移除Watchpoints
1 (gdb) disable <watchpoint_number>
display 使用以下指令設定display
1 (gdb) display expression
查看所有display 查看所有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 2 (gdb) break <file_name> : <line_number> (gdb) break <function_name>
查看所有中斷點 下面指令可以查看目前已經設定過的中斷點。
設定條件 首先我們必須要知道中斷點的編號,並且用以下指令設定條件
1 (gdb) condition <breakpoint_number> condition
移除中斷點的停止條件 如果想要移除中斷點的停止條件,可以用以下指令
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 #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 ; } int factorial (int n) { int f = 1 ; int i = 1 ; while (i <= n) { f = f * i; i++; } return f; }
{:file=’factorial.c’}
編譯程式並啟動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
每當有新的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/