1. 程式人生 > >從 0 開始學習 Linux 系列之「08.15 個 gdb 除錯基礎命令」

從 0 開始學習 Linux 系列之「08.15 個 gdb 除錯基礎命令」

gdb 簡介

gdbUNIXUNIX-like 下的除錯工具,在 Linux 下一般都直接在命令列中用 gdb 來除錯程式,相比 Windows 上的整合開發環境 IDE 提供的圖形介面除錯,一開始使用 gdb 除錯可能會讓你感到生無可戀,但是隻要熟悉了 gdb 除錯的常用命令,調試出程式會很有成就感。

一方面因為這些命令就類似圖形介面除錯按鈕背後的邏輯,另一方面用命令列來除錯程式,逼格瞬間就上了一個檔次,這次就跟大家分享 gdb 除錯的基本技術和 15 個常用除錯命令。在此之前,我們先來回顧下在 Windows 上使用 IDE 的圖形介面除錯過程。

IDE 的除錯步驟

在 Windows 的 IDE 下除錯程式,例如使用 VS,一般都有下面這幾個操作:
1. Debug 模式編譯並啟動程式
2. 程式執行出錯,打斷點

分析出錯的地方
3. 單步執行程式,包括:step over 單步執行;step into 跳入函式;step return 跳出函式
4. 還有全速執行,列印或者監視變數,凍結或解凍執行緒等除錯技術

在 IDE 中上面的這些步驟一般都有固定的按鈕提供給我們使用,非常的簡單方便,我們只要多練習練習,在圖形介面除錯程式不會很難,但是在 Linux 下用命令來除錯程式就比圖形介面要複雜很多了。

其實,你知道真正的除錯高手是什麼樣的嗎?就是 Ta 對計算機原理和程式本身的邏輯理解非常深刻在 Ta 的腦海中已經可以模擬程式的執行過程,並且知道可能出錯的地方,這樣連斷點都不用打了,而且 Bug 的命中率也不低

,或許這就是真正的大佬吧 :)

我們回到正題,來介紹在 Linux 使用命令列的除錯過程。

gdb 的除錯步驟

在 Linux 下既然是使用命令列來除錯,顧名思義就是手敲命令來除錯程式,大體分為下面幾個步驟,後面會詳細介紹:
1. 編譯可以除錯的程式
2. 執行程式,打斷點
3. 單步除錯,監控變數
4. 視覺化除錯
5. 其他除錯技術

可以看出,與 IDE 除錯過程差不多,但是實際操作起來可是千差萬別,可不是點按鈕了,而是自己敲除錯程式的命令,就相當於你正在學習那些除錯按鈕背後的原理,把這種方法學會,不用學就會使用 IDE 來除錯程式,不管你信不信,我反正是信了 :)

下面開始正式的除錯技術介紹。

15 個 gdb 除錯基礎命令

下面來正式介紹 gdb 常用的除錯技術,都是除錯命令,只看不做比較乏味,還是建議你跟我一起動手除錯下面的程式,這樣才能真正的學會,這是本次要除錯的 hello.c 程式,非常簡單:

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

int main() {
    int a = 1;
    int b = 2;
    printf("a = %d\n", a);
    printf("b = %d\n", b);

    int c = add(a, b);
    printf("%d + %d = %d\n", a, b, c);
    return 0;
}

1. 編譯可以除錯的程式

我們平常使用 gcc 編譯的程式如果不加 [-g] 選項:

gcc hello.c -o hello

gdb 會提示該可執行檔案沒有除錯符號,不能除錯:

gdb hello
# gdb 提示資訊
Reading symbols from a.out...(no debugging symbols found)...done.

如果需要讓程式可以除錯,就必須在編譯的時候加上 [-g] 引數

gcc -g hello.c -o hello

2. 載入要除錯的程式

我們在命令列下需要手動載入待除錯的程式,有 2 種方法:

方法一 - gdb 可執行檔案

使用如下的命令來載入可執行檔案 hello 到 gdb 中:

gdb hello

載入成功,gdb 會列印一段提示資訊,並且命令列字首變為 (gdb),下面是我的 Ubuntu 列印的資訊:

GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu1) 7.11.90.20161005-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from hello...done.
(gdb) q

注:q 退出 gdb

方法二 - 使用 gdb 提供的 file 命令

第二種方法是在 gdb 環境中使用 file 命令,我們需要先進入 gdb 環境下:

gdb

使用 file hello 載入待除錯程式:

...
(gdb) file hello
Reading symbols from hello...done.
(gdb) q

3. 檢視除錯程式

在 gdb 下檢視除錯程式使用命令 list 或簡寫 l,「回車」列出後面程式:

(gdb) list
1   #include <stdio.h>
2   
3   int add(int x, int y) {
4       return x + y;
5   }
6   
7   
8   int main() {
9       int a = 1;
10      int b = 2;
(gdb) 
11      printf("a = %d\n", a);
12      printf("b = %d\n", b);
13      
14      int c = add(a, b);
15      printf("%d + %d = %d\n", a, b, c);
16      return 0;
17  }

4. 新增斷點

在 gdb 下新增斷點使用命令 break 或簡寫 b,有下面幾個常見用法(這裡統一用 b):
1. b function_name
2. b row_num
3. b file_name:row_num
4. b row_num if condition

比如我們以第一個為例,在 main 函式上新增斷點:

(gdb) b main
Breakpoint 1 at 0x6e8: file hello.c, line 4.

列印的資訊告訴我們在 hello.c 檔案的第 4 行,地址 0x6e8 處添加了一個斷點,那如何檢視斷點呢?

5. 檢視斷點

在 gdb 下檢視斷點使用命令 info break 或簡寫 i b,比如檢視剛才打的斷點:

(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000006e8 in main at hello.c:4

可以看到打印出剛才新增的 main 函式的斷點資訊:編號,型別,顯示狀態,是否啟用,地址,其他資訊,那又如何刪除這個斷點呢?

6. 禁用斷點

在 gdb 下禁用斷點使用命令 disable Num,比如禁用剛才打的斷點:

(gdb) disable 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x00000000000006e8 in main at hello.c:4

可以看到欄位「Enb」已經變為 n,表示這個斷點已經被禁用了。

7. 刪除斷點

在 gdb 下刪除斷點使用命令 delete 斷點 Num 或簡寫 d Num,比如刪除剛才的 Num = 1 的斷點:

(gdb) d 1
(gdb) i b
No breakpoints or watchpoints.

刪除後再次檢視斷點,提示當前沒有斷點,說明刪除成功啦,下面來執行程式試試。

8. 執行程式

在 gdb 下使用命令 run 或簡寫 r 來運行當前載入的程式:

(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 
a = 1
b = 2
1 + 2 = 3
[Inferior 1 (process 10415) exited normally]

我這次沒有新增斷點,程式全速執行,然後正常退出了。

9. 單步執行下一步

在 gdb 下使用命令 next 或簡寫 n 來單步執行下一步,假設我們在 main 打了斷點:

(gdb) b main
Breakpoint 1 at 0x6e8: file hello.c, line 4.
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 

Breakpoint 1, main () at hello.c:4
4       int a = 1;
(gdb) n
5       printf("a = %d\n", a);

可以看到當前停在 int a = 1; 這一行,按 n 執行了下一句程式碼 printf("a = %d\n", a);

10. 跳入,跳出函式

在 gdb 下使用命令 step 或簡寫 s 來跳入一個函式,使用 finish 來跳出一個函式,我們在第 14 行 int c = add(a, b); 新增一個斷點:

(gdb) b 14
Breakpoint 1 at 0x6f6: file hello.c, line 14.
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 
a = 1
b = 2

Breakpoint 1, main () at hello.c:14
14      int c = add(a, b);
(gdb) s
add (x=1, y=2) at hello.c:4
4       return x + y;
(gdb) finish
Run till exit from #0  add (x=1, y=2) at hello.c:4
0x0000555555554705 in main () at hello.c:14
14      int c = add(a, b);
Value returned is $1 = 3
(gdb) n
15      printf("%d + %d = %d\n", a, b, c);

這個過程是這樣的:
1. 在 14 行 int c = add(a, b); 新增斷點
2. 程式執行並停到 int c = add(a, b); 這一行
3. s 跳入 add 函式
4. finish 跳出 add 函式,並輸出一些函式返回的資訊

11. 列印變數

在 gdb 中使用命令 print var 或簡寫 p var 來列印一個變數或者函式的返回值,我們在第 10 行 int b = 2; 新增一個斷點:

(gdb) b 10
Breakpoint 1 at 0x6c3: file hello.c, line 10.
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 

Breakpoint 1, main () at hello.c:10
10      int b = 2;
(gdb) p a
$1 = 1

我們打印出變數 a 的值為 1,在除錯中比較頻繁的操作是「監視變數」,在 gdb 中如何做呢?

12. 監控變數

在 gdb 中使用命令 watch var 來監控一個變數,使用 info watch 來檢視監控的變數,我們這裡來監控變數 c

(gdb) b 14
Breakpoint 1 at 0x6f6: file hello.c, line 14.
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 
a = 1
b = 2

Breakpoint 1, main () at hello.c:14
14      int c = add(a, b);
(gdb) watch c
Hardware watchpoint 2: c
(gdb) info watch
Num     Type           Disp Enb Address            What
2       hw watchpoint  keep y                      c

注意:程式必須要先執行才能監控

13. 檢視變數型別

在 gdb 下使用命令 whatis 檢視一個變數的型別:

(gdb) b 10
Breakpoint 1 at 0x6c3: file hello.c, line 10.
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello 

Breakpoint 1, main () at hello.c:10
10      int b = 2;
(gdb) whatis b
type = int

這裡變數 bint 型別。

14. 在 gdb 中進入 shell

在 gdb 下使用命令 shell 啟動 shell :

(gdb) shell
orange@ubuntu:~/Desktop/gdb$ exit
exit
(gdb) 

使用 exit 會再次退回到 gdb 中。

15. 在 gdb 中實現視覺化除錯

誰說 gdb 只能在命令列除錯呢?gdb 也支援「圖形介面」,不過這裡的圖形介面都是用字元顯示的,當然不如 VS 那種好看,不過使用視覺化相比直接看命令列更加直觀了。

在 gdb 下使用 wi 啟動視覺化除錯:

(gdb) wi

效果如下圖所示,上面是程式碼效果,下面是命令介面:

wi

有了圖形介面,就再對照著圖形介面將前面的命令再練習練習,看看自己手敲的命令背後到底做了些什麼,加深下影響。

結語

這篇部落格主要介紹了 gdb 基本的除錯技術,一篇文章不可能面面俱到,還有很多命令沒有介紹,如果你有興趣的話,這裡還有一份比較好的 gdb 快速學習指南 分享給你。感謝你的閱讀,我們下次再見 :)

本文原創釋出於微信公眾號「cdeveloper」,程式設計、職場,人生,關注並回復關鍵字「linux」、「機器學習」等獲取免費學習資料。