較詳細的gdb入門教程


前言

本文選自 較詳細的gdb入門教程 - Zesty_Fox

本篇教程適用於 Windows,macOS 及 Linux,但由於 Windows 的自帶終端很難用,所以體驗可能不太好。Windows 10 建議安裝 Windows Terminal 以取得最佳體驗。

你是否為 C/C++ 下的調試而苦惱?你是否苦於 Dev-C++ 調試煩人的問題(如調不了 STL、結構體數組要一層一層展開)?那么,gdb 很可能是你的最佳選擇。

gdb 是一個命令行下的、功能強大的調試器。看到命令行下,是不是有點害怕?沒關系,本文最后會介紹一些圖形前端,但建議先學習一些基礎命令。

示例代碼:(example.cpp,以下調試命令均以此代碼為准)

博主太懶了,只寫了個求階乘

#include <iostream>
#include <cstdio>
using namespace std;
int f(int x){
    int ans=1;
    for(int i=1;i<=x;i++) ans*=i;
    return ans;
}

int main(){
    int a;
    scanf("%d",&a);
    printf("%d\n",f(a));
    return 0;
}

系統環境:Linux Mint 19.3 64 位。

調試

啟動 gdb,載入文件,打印源代碼,退出gdb

首先,在編譯選項里加上 -g ,以生成調試用的符號表。建議不要同時開 -O2 等優化選項,否則可能會有奇奇怪怪的問題。

打開終端,輸入 gdb [可執行文件名] ,載入程序(注意,是可執行文件名(比如 1.exe),不是你的源文件名)。比如這樣:

> g++ example.cpp -o example -g
[編譯,無提示]
> gdb ./example

然后,你可能會見到如下的界面:

GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 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 ./example...done.
(gdb) 

這樣,你就進入 gdb 的命令行環境里了。

其中,第一行是版本信息,倒數第二行表示正載入符號表,最后一行 (gdb) 則是** gdb 的提示符**。

請注意,若你倒數第二行有 (no debugging symbols found) 字樣,請確保在編譯選項里加上 -g 選項。

當然,如果你直接輸入 gdb 啟動,不加文件名,也可以。只是,你要使用 file 命令手動載入可執行文件。

以后出現的所有命令,都是在 gdb 的環境,而非系統 shell 的環境執行的。

命令:file(簡寫fil)
格式:file 可執行文件名
作用:載入當前目錄下的對應名稱的可執行文件。

例子:

(gdb) file example
Load new symbol table from "example"? (y or n) y
Reading symbols from example...done.

命令:list(簡寫為l)
格式:list [行號]
作用:打印給定行號周圍 \(10\) 行的源代碼。若不提供行號,則接續打印上次的源代碼。

這里提到了一個簡寫的概念。什么是簡寫呢?簡寫是為了簡化命令的。比如,打一個 list 還是有些麻煩的。這時,我們可以輸入它的簡寫 l 。你可以認為一個命令與它的簡寫是完全等價的。以后若提到簡寫,不再解釋。

例子:

(gdb) l 7
2	#include <cstdio>
3	using namespace std;
4	int f(int x){
5	    int ans=1;
6	    for(int i=1;i<=x;i++) ans*=i;
7	    return ans;
8	}
9	
10	int main(){
11	    int a;
(gdb) l
12	    scanf("%d",&a);
13	    printf("%d\n",f(a));
14	    return 0;
15	}
(gdb) 

最后,用 quit 命令退出 gdb。

命令:quit(簡寫為q)
格式:quit(無參數)
作用:退出gdb。

設置斷點

要調試程序,我們必須讓它在某個地方停下來。否則,讓它一直執行下去,那和普通的執行程序有什么區別呢?

因此,我們需要用 break 來設置斷點。此后,程序將會在設定的斷點處停下來。

命令:break(簡寫為b)
格式:break 函數名|行號
作用:在給定函數名或行數處設置斷點。

例子:

(gdb) break main //main函數處設置斷點
Breakpoint 1 at 0x874: file example.cpp, line 10.
(gdb) break 11 //在第11行處設置斷點
Breakpoint 2 at 0x883: file example.cpp, line 11.

當然可以用它的簡寫:

(gdb) b main
Breakpoint 1 at 0x874: file example.cpp, line 10.
(gdb) b 11
Breakpoint 2 at 0x883: file example.cpp, line 11.

運行

命令:run(簡寫為r)
格式:run(無參數)
作用:從頭運行程序。

命令:continue(簡寫為c)
格式:continue(無參數)
作用:從當前位置繼續運行程序,直到遇到下一個斷點或程序運行完畢。

命令:until(簡寫為u)
格式:until 行號
作用:從當前位置繼續運行程序,直到指定行號處才停下來。

當然,有時候,你可能發現運行上述命令后 gdb 會停住。這有兩種情況:

  1. 你的程序用了標准輸入,gdb 在等待輸入。
  2. 數據規模太大或程序效率太低,以至於運行到斷點的時間較長。

例子:

(gdb) r
Starting program: /home/acceptedzhs/example 

Breakpoint 1, main () at example.cpp:10
10	int main(){
(gdb) c
Continuing.

Breakpoint 2, main () at example.cpp:12
12	    scanf("%d",&a); 
(gdb) 

單步執行

很多時候,我們要一步一步地執行程序。無疑,反復地 breakcontinue 十分麻煩。gdb有兩個命令 nextstep,可實現單步執行。

命令:next(簡寫為n)
格式:next(無參數)
作用:單步執行。若當前行有函數調用,則把這個函數作為一個整體執行(即不進入函數內部)。

命令:step(簡寫為s)
格式:step(無參數)
作用:單步執行。若當前行有函數調用,則進入該函數內部

但是,又有人想偷懶了。反復敲 ns 依然很麻煩。怎么辦呢?

gdb有個特性:若什么都不輸,直接按回車,則會執行上一次執行的命令

所以,只要開始敲個 ns,然后一直敲回車就行了。

舉個例子好了:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example 
10

Breakpoint 1, main () at example.cpp:13
13	    printf("%d\n",f(a));
(gdb) n
3628800
14	    return 0;
(gdb) [回車] //看到沒,執行了上次的命令,即next
15	}
(gdb) 
Starting program: /home/acceptedzhs/example 
10

Breakpoint 1, main () at example.cpp:13
13	    printf("%d\n",f(a));
(gdb) s
f (x=10) at example.cpp:5 //step命令,進入了f函數內部
5	    int ans=1;
(gdb) 

輸出變量/函數值

有時,我們想要打印某些變量或函數的值,看它是否符合期望。這又怎么辦呢?

命令:print(簡寫為p)
格式:print 變量名
作用:打印一次變量名/函數調用對應的值。

命令:display(簡寫為disp)
格式:display 變量名
作用:設置在每一次停下來時(如到斷點時,單步執行等)都打印該變量名/函數調用對應的值。

例子:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example 
9

Breakpoint 1, main () at example.cpp:13
13	    printf("%d\n",f(a));
(gdb) p a
$1 = 9
(gdb) n
362880 //輸出9!
14	    return 0; //這只會顯示一次,下一步就不會再打印該變量值了
(gdb) p f(2) //當然,調用函數也可以
$2 = 2
(gdb) 
(gdb) b f
Breakpoint 1 at 0x841: file example.cpp, line 5.
(gdb) r
Starting program: /home/acceptedzhs/example 
9

Breakpoint 1, f (x=9) at example.cpp:5
5	    int ans=1;
(gdb) disp ans
1: ans = 0
(gdb) n
6	    for(int i=1;i<=x;i++) ans*=i;
1: ans = 1
(gdb) 
7	    return ans;
1: ans = 362880 //每次停下來時,該變量都會顯示
(gdb) 
8	}
1: ans = 362880
(gdb)

有時,你可能會見到 `` 的提示。此時,請檢查編譯時是否開了優化(如 -O2 )。

查看某些信息

命令:info(簡寫為i)
格式:info 類型
作用:打印對應類型的信息。

其中,類型可以是 breakpoints(斷點,簡寫b)、locals(局部變量,簡寫lo)、display(被設為總是顯示的變量,簡寫 disp)等。具體可以通過 help info 查看。

比如:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example 
10 //程序的標准輸入

Breakpoint 1, main () at example.cpp:13
13	    printf("%d\n",f(a));
(gdb) i lo
a = 10
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555489b in main() at example.cpp:13
	breakpoint already hit 1 time
(gdb) disp a
1: a = 10
(gdb) i display 
Auto-display expressions now in effect:
Num Enb Expression
1:   y  a
(gdb) 

我們發現輸出了很多信息。其中 Num 是編號。編號有什么用呢?我們待會兒就要見到。

刪除/禁用/啟用某些東西

命令:disable(簡寫為dis)
格式:disable 類型 [編號]
作用:臨時禁用某些類型的對應編號的東西,待會兒講。

命令:delete(簡寫為d)
格式:delete 類型 [編號]
作用:刪除某些類型的對應編號的東西。

命令:enable(簡寫為en)
格式:enable 類型 [編號]
作用:啟用某些類型的對應編號的東西。

其中,類型就是講述 info 命令時中的類型,編號就是 info 命令輸出的一堆東西中的 Num 那一欄。

注意:delete 可能用不了類型的簡寫。

舉個例子:

(gdb) b 12
Breakpoint 1 at 0x883: file example.cpp, line 12.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000555555554883 in main() at example.cpp:12
(gdb) dis b 1 //禁用1號斷點
(gdb) r
Starting program: /home/acceptedzhs/example 
10
3628800 //不經過該斷點了
[Inferior 1 (process 2695) exited normally]
(gdb) en b 1 //啟用該斷點
(gdb) r
Starting program: /home/acceptedzhs/example 

Breakpoint 1, main () at example.cpp:12
12	    scanf("%d",&a); //又經過該斷點了
(gdb) d breakpoints 1 //刪除
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/acceptedzhs/example 
10
3628800 //又不經過斷點了
[Inferior 1 (process 3516) exited normally] 
(gdb) 

獲取幫助

有時,我們可能忘記某個命令的用法。這該怎么辦呢?

命令:help(簡寫為h)
格式:help 待查詢的命令(待查詢的命令可以用簡寫)
作用:顯示待查詢的命令的幫助。

例子:

(gdb) h b
Set breakpoint at specified location.
break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]
PROBE_MODIFIER shall be present if the command is to be placed in a
probe point.  Accepted values are `-probe' (for a generic, automatically
guessed probe type), `-probe-stap' (for a SystemTap probe) or 
`-probe-dtrace' (for a DTrace probe).
LOCATION may be a linespec, address, or explicit location as described
below.

With no LOCATION, uses current execution address of the selected
stack frame.  This is useful for breaking on return to a stack frame.
...(省略若干行)...

命令一覽表

命令 簡寫 作用
file fil 載入可執行文件
list l 打印源代碼
quit q 退出gdb
break b 設置斷點
run r 從頭運行程序
continue c 從當前位置繼續運行程序
until u 從當前位置繼續運行,直到指定行號
next n 單步執行
step s 單步執行
print p 打印一次值
display disp 設置某個變量/函數總是顯示
info i 打印相關類型的信息
disable dis 臨時禁用某些東西
delete d 刪除某些東西
enable en 啟用某些東西
help h 獲取幫助

圖形界面?

gdb 作為一個命令行調試器,對於某些人來說可能望而生畏。

所以,很多人為其開發了圖形前端,以方便大家使用。

這里,我推薦 nemiver、ddd、gdbgui。(貌似不支持 windows)

如果你是個 Vim 愛好者,vim-vebugger 也不錯。

對上面不滿意?可以試試 gdb 自帶的偽圖形界面,只要啟動gdb時加上 -tui 選項即可。


免責聲明!

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



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