GDB 除錯技巧

contributed by < hsuedw >

用 x 命令檢視記憶體內容

用一個最簡單的 C 程式來學習如何用 GDB 檢視程式執行期間的記憶體內容。

int main()
{ 
   int i = 1337;
   return 0; 
}

在 C 語言中,變數被用來代表一塊記憶體空間,這塊記憶體空間可以用來儲存資料,表示數值。這塊記憶體空間就是 C99 Standard 中所謂的 object。

C99 Standard 3.14
object
region of data storage in the execution environment, the contents of which can represent values

一個變數有兩個重要的數字:

  1. object 在記憶體中所佔用區域的起始位址
  2. object 在記憶體中所佔用區域的大小。一般以 byte 為單位。
$ gcc test.c -g -o test
$ gdb ./test
   :
(gdb) l
1       int main()
2       {
3          int i = 1234;
4          return 0;
5       }
(gdb) b 4
Breakpoint 1 at 0x1138: file test.c, line 4.
(gdb) r
Starting program: /home/edward/workspace/linux_2022/gdb/test

Breakpoint 1, main () at test.c:4
4          return 0;

(gdb) p &i
$1 = (int *) 0x7fffffffe23c
(gdb) p sizeof(i)
$2 = 4

如上面實驗步驟所示, 0x7fffffffe23c 就是變數 i 的記憶體位址, 4 就是變數 i (型別為 int ) 所佔用的記憶體區塊大小。
接下來用 x 指令檢視變數 i 所佔用的記憶體空間中的數值。

(gdb) x/4bx &i
0x7fffffffe23c: 0xd2    0x04    0x00    0x00

參考資料: x command

在這個範例中,用 x 指令檢視記憶體內容時,指定印出格式為 4 個 byte ,且顯示為十六進位 ( x )。
因為我用的電腦是 Intel x86_64 CPU ,採用 little endian 表示資料,所以由 0x7fffffffe23c 起算 4 個 byte 應到著看。正確的資料應為 0x00 0x00 0x04 0xd2 。而十六進位 0x4d2 就是十進位的 1234

GDB 的圖形化介面

在執行 gdb 追蹤程式碼的運作時,可加入 -tui 選項。即使是在命令列模式下, gdb 也可以呈現圖形化介面。

$ gcc hello.c -g -o hello
$ gdb -tui ./hello

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

設定參數給被追蹤的程式

  • 第一種方法,在 gdb 提示訊息下執行 r (或 run )命令時,同時指定要傳給被追蹤程式的參數就可以了。
    ​​$ gdb -q ./test
    ​​Reading symbols from ./test...
    ​​(gdb) l
    ​​1       #include <stdio.h>
    ​​2
    ​​3       int main(int argc, char **argv)
    ​​4       {
    ​​5           printf("%s, %s!\n", argv[1], argv[2]);
    ​​6
    ​​7           return 0;
    ​​8       }
    ​​(gdb) b 7
    ​​Breakpoint 1 at 0x1186: file test.c, line 7.
    ​​(gdb) r Hello World
    ​​Starting program: /home/edward/workspace/linux_2022/gdb/test Hello World
    ​​Hello, World!
    ​​
    ​​Breakpoint 1, main (argc=3, argv=0x7fffffffe328) at test.c:7
    ​​7           return 0;
    ​​(gdb)
    
  • 第二種方法,啟動 gdb 時,直接給參數
    ​​$ gdb -q --args ./test Hello World!
    ​​Reading symbols from ./test...
    ​​(gdb) l
    ​​1       #include <stdio.h>
    ​​2
    ​​3       int main(int argc, char **argv)
    ​​4       {
    ​​5           printf("%s, %s!\n", argv[1], argv[2]);
    ​​6
    ​​7           return 0;
    ​​8       }
    ​​(gdb) b main
    ​​Breakpoint 1 at 0x1149: file test.c, line 4.
    ​​(gdb) r
    ​​Starting program: /home/edward/workspace/linux_2022/gdb/test Hello World\!
    ​​
    ​​Breakpoint 1, main (argc=0, argv=0x7fffffffe320) at test.c:4
    ​​4       {
    ​​(gdb) n
    ​​5           printf("%s, %s!\n", argv[1], argv[2]);
    ​​(gdb)
    ​​Hello, World!!
    ​​7           return 0;
    ​​(gdb)
    

breakpoint

設定 breakpoint

可以用 break 指令 (或簡寫為 b ) 加上 breakpoint 的位置來設定。
breakpoint 的位置可以是

  1. 檔案名稱
  2. 行號
  3. 原始程式碼檔名加上行號
break main         在 main() 函式的開頭設定 breakpoint
break 6            在目前這個檔案的第 6 行設定 breakpoint
break hello.c:6    在 hello.c 這個檔案的第 6 行設定 breakpoint
(gdb) l
1       #include <stdio.h>
2
3       int main()
4       {
5           int a = 1, b = 2;
6
7           a +=b;
8           printf("Hello World!\n");
9
10          return 0;
(gdb)
11      }
(gdb) b main
Breakpoint 1 at 0x1149: file hello.c, line 4.
(gdb) b 5
Breakpoint 2 at 0x1155: file hello.c, line 5.
(gdb) b hello.c:8
Breakpoint 3 at 0x1169: file hello.c, line 8.

列出所有的 breakpoint

可以用 i b 這個指令列出目前所有的 breakpoint 。

(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001149 in main at hello.c:4
2       breakpoint     keep y   0x0000000000001155 in main at hello.c:5
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8

開啟與關閉 breakpoint

可以用 i b 指令列出所有的 breakpoint 後,再用 disable 指令加上 breakpoint 的編號暫時讓指定的 breakpoint 失效。
事後可以再用 enable 指令加上 breakpoint 的編號來恢復 breakpoint 的功能。

(gdb) disable 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000001149 in main at hello.c:4
2       breakpoint     keep y   0x0000000000001155 in main at hello.c:5
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8
(gdb) enable 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001149 in main at hello.c:4
2       breakpoint     keep y   0x0000000000001155 in main at hello.c:5
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8

刪除 breakpoint

可以用 i b 指令列出所有的 breakpoint 後,再用 clear 指令加上 breakpoint 的位置來刪除指定的 breakpoint。

  1. 檔案名稱
  2. 行號
  3. 原始程式碼檔名加上行號
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001149 in main at hello.c:4
2       breakpoint     keep y   0x0000000000001155 in main at hello.c:5
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8
(gdb) clear main

(gdb) i b
Deleted breakpoint 1 Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000001155 in main at hello.c:5
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8
(gdb) clear 5

(gdb) i b
Deleted breakpoint 2 Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x0000000000001169 in main at hello.c:8
(gdb) clear hello.c:8

(gdb) i b
Deleted breakpoint 3 No breakpoints or watchpoints.

條件斷點

單步執行

  • nnext 指令以 statement 為單位,可以依序單步執行目前函式中的每一個 statement。
  • 當 statement 為一個函式時,sstep 指令可以進入此函式進行追蹤。
  • 使用單步執行時,一般可以搭配 i localsp 變數名稱 觀察變數的內容。
  • next xstep x 可以指定往下走 x 條 statement。
(gdb) l
1       int main()
2       {
3           int i = 1;
4           i = 2;
5           i = 3;
6           i = 4;
7           i = 5;
8           i = 6;
9           i = 7;
10          i = 8;
(gdb) l
11          i = 9;
12          i = 10;
13          i = 11;
14          i = 12;
15          i = 13;
16
17          return 0;
18      }
(gdb) b main
Breakpoint 1 at 0x1129: file test.c, line 2.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test

Breakpoint 1, main () at test.c:2
2       {
(gdb) n
3           int i = 1;
(gdb) n
4           i = 2;
(gdb) n 5
9           i = 7;
(gdb)

指定執行位置

advance 指令可以指定接下來要直接執行到程式的哪一個位置。有點像快轉。不需要一步步地做單步執行。

(gdb) l
1       #include <stdio.h>
2
3       void bar()
4       {
5           printf("Hello bar!\n");
6           return;
7       }
8
9       void foo()
10      {
(gdb)
11          bar();
12      }
13
14      int main()
15      {
16          foo();
17          return 0;
18      }
(gdb)
Line number 19 out of range; test.c has 18 lines.
(gdb) b main
Breakpoint 1 at 0x1175: file test.c, line 15.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test

Breakpoint 1, main () at test.c:15
15      {
(gdb) advance bar
bar () at test.c:4
4       {
(gdb) n
5           printf("Hello bar!\n");
(gdb) n
Hello bar!
6           return;
(gdb)

觀察變數內容

info locals 命令

info locals 命令可以列出目前所有區域變數的內容。

(gdb) l
1       int main()
2       {
3           int i = 0, j = 1, k = 2;
4
5           i += 1;
6           j += 2;
7           k += 3;
8
9           return 0;
10      }
(gdb) b 9
Breakpoint 1 at 0x1152: file test2.c, line 9.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test2

Breakpoint 1, main () at test2.c:9
9           return 0;
(gdb) info locals
i = 1
j = 3
k = 5
(gdb)

print 變數名稱 可以顯示指定變數的內容。

(gdb) l
1       int main()
2       {
3           int i = 0;
4
5           i += 1;
6           i += 2;
7
8           return 0;
9       }
(gdb) b main
Breakpoint 1 at 0x1129: file test2.c, line 2.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test2

Breakpoint 1, main () at test2.c:2
2       {
(gdb) n
3           int i = 0;
(gdb) print i
$1 = 0
(gdb) n
5           i += 1;
(gdb) print i
$2 = 0
(gdb) n
6           i += 2;
(gdb) print i
$3 = 1
(gdb) n
8           return 0;
(gdb) print i
$4 = 3
(gdb)

display 命令

除了 i localsp 變數名稱 外,還可以用 display 命令設定想要觀察的變數。
每做一次 next ,那些被 display 設定為要觀察的變數的內容就會被自動顯示出來。如下面這個範例中的變數 i。要注意的是,在 GDB 提示符號 (gdb) 後的 statement 是下一個要執行的 statement。

(gdb) l
1       int main()
2       {
3           int i = 1;
4           i = 2;
5           i = 3;
6           i = 4;
7           return 0;
8       }
(gdb) b main
Breakpoint 1 at 0x1129: file test.c, line 2.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test

Breakpoint 1, main () at test.c:2
2       {
(gdb) display i
1: i = 0
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  i
(gdb) n
3           int i = 1;
1: i = 0
(gdb) n
4           i = 2;
1: i = 1
(gdb) n
5           i = 3;
1: i = 2
(gdb) n
6           i = 4;
1: i = 3
(gdb) n
7           return 0;
1: i = 4
(gdb)

watch 命令

watch 命令可以設定想要觀測的變數。只要該變數的的內容有發生變化, GDB 就會把該變數改變前與改變後的內容印出來。

(gdb) l
1       int main()
2       {
3           int i = 0, j = 1;
4
5           j += 1;
6           j *= 2;
7           j -= 3;
8           i += j;
9           j += 4;
10          j += 5;
(gdb)
11
12          return 0;
13      }
(gdb) b main
Breakpoint 1 at 0x1129: file test2.c, line 2.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test2

Breakpoint 1, main () at test2.c:2
2       {
(gdb) watch i
Hardware watchpoint 2: i
(gdb) n
3           int i = 0, j = 1;
(gdb) n
5           j += 1;
(gdb) n
6           j *= 2;
(gdb) n
7           j -= 3;
(gdb) n
8           i += j;
(gdb) n

Hardware watchpoint 2: i

Old value = 0
New value = 1
main () at test2.c:9
9           j += 4;
(gdb) n
10          j += 5;
(gdb) n
12          return 0;
(gdb)

觀察資料結構 print {type} variable

假設追蹤下列程式碼。

struct test {
    int a;
    double b;
};

int main()
{
    struct test t;
    t.a = 123;
    t.b = 123.456;

    struct test *pt = &t;

    return 0;
}

編譯原始碼 (gcc test.c -g -o test) 後,啟動 gdb 進行追蹤。

$ gdb -q test
Reading symbols from test...
(gdb) l
1
2       struct test {
3           int a;
4           double b;
5       };
6
7       int main()
8       {
9           struct test t;
10          t.a = 123;
(gdb)
11          t.b = 123.456;
12
13          struct test *pt = &t;
14
15          return 0;
16      }
(gdb) b 15
Breakpoint 1 at 0x1180: file test.c, line 15.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test

Breakpoint 1, main () at test.c:15
15          return 0;
(gdb) p {struct test} pt
$1 = {a = 123, b = 123.456}
(gdb) p pt
$2 = (struct test *) 0x7fffffffe2c0
(gdb) p {struct test} 0x7fffffffe2c0
$3 = {a = 123, b = 123.456}

在追蹤過程中修改變數內容

print 指令修改變數內容

可以用 print 變數名稱 = 新內容 修改變數的內容。

(gdb) l
1       int main()
2       {
3           int i = 0;
4
5           i += 1;
6           i += 2;
7           i += 3;
8           i += 4;
9
10          return 0;
(gdb)
11      }
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test2

Breakpoint 1, main () at test2.c:2
2       {
(gdb) n
3           int i = 0;
(gdb) n
5           i += 1;
(gdb) n
6           i += 2;
(gdb) print i
$4 = 1
(gdb) print i = 100
$5 = 100
(gdb) n
7           i += 3;
(gdb) print i
$6 = 102
(gdb) n
8           i += 4;
(gdb) print i
$7 = 105
(gdb) n
10          return 0;
(gdb) print i
$8 = 109
(gdb)

set 指令修改變數內容

set 指令有兩種用法

  1. set variable 變數名稱 = 新內容
  2. set (變數名稱 = 新內容)
(gdb) l
1       int main()
2       {
3           int i = 0;
4
5           i += 1;
6           i += 2;
7           i += 3;
8           i += 4;
9
10          return 0;
(gdb)
11      }
(gdb) b main
Breakpoint 1 at 0x1129: file test2.c, line 2.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test2

Breakpoint 1, main () at test2.c:2
2       {
(gdb) n
3           int i = 0;
(gdb) n
5           i += 1;
(gdb) print i
$1 = 0
(gdb) n
6           i += 2;
(gdb) print i
$2 = 1
(gdb) set variable i = 100
(gdb) print i
$3 = 100
(gdb) n
7           i += 3;
(gdb) print i
$4 = 102
(gdb) set (i = 200)
(gdb) print i
$5 = 200
(gdb) n
8           i += 4;
(gdb) print i
$6 = 203
(gdb) n
10          return 0;
(gdb) print i
$7 = 207
(gdb)

Stack Manipulation

backtracebt 指令可以顯示目前函式呼叫堆疊的狀況。

(gdb) l
1
2       void bar()
3       {
4           return;
5       }
6
7       void foo()
8       {
9           bar();
10      }
(gdb)
11
12      int main()
13      {
14          foo();
15          return 0;
16      }
(gdb)
Line number 17 out of range; test.c has 16 lines.
(gdb) b 4
Breakpoint 1 at 0x1131: file test.c, line 4.
(gdb) r
Starting program: /home/edward/workspace/linux-2022/gdb/test

Breakpoint 1, bar () at test.c:4
4           return;
(gdb) bt
#0  bar () at test.c:4
#1  0x0000555555555146 in foo () at test.c:9
#2  0x000055555555515b in main () at test.c:14

其他關於堆疊的功能可用 help stack 得到說明

(gdb) help stack
Examining the stack.
The stack is made up of stack frames.  Gdb assigns numbers to stack frames
counting from zero for the innermost (currently executing) frame.

At any time gdb identifies one frame as the "selected" frame.
Variable lookups are done with respect to the selected frame.
When the program being debugged stops, gdb selects the innermost frame.
The commands below can be used to select other frames by number or address.

List of commands:

backtrace -- Print backtrace of all stack frames, or innermost COUNT frames.
bt -- Print backtrace of all stack frames, or innermost COUNT frames.
down -- Select and print stack frame called by this one.
faas -- Apply a command to all frames (ignoring errors and empty output).
frame -- Select and print a stack frame.
frame address -- Select and print a stack frame by stack address.
frame apply -- Apply a command to a number of frames.
frame apply all -- Apply a command to all frames.
frame apply level -- Apply a command to a list of frames.
frame function -- Select and print a stack frame by function name.
frame level -- Select and print a stack frame by level.
frame view -- View a stack frame that might be outside the current backtrace.
return -- Make selected stack frame return to its caller.
select-frame -- Select a stack frame without printing anything.
select-frame address -- Select a stack frame by stack address.
select-frame function -- Select a stack frame by function name.
select-frame level -- Select a stack frame by level.
select-frame view -- Select a stack frame that might be outside the current backtrace.
up -- Select and print stack frame that called this one.

Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Type "apropos -v word" for full documentation of commands related to "word".
Command name abbreviations are allowed if unambiguous.

參考資料