Linux操作系統從入門到實戰(十五)詳細講解Linux調試器 gdb/cgdb使用
- 前言
- 一、gdb/cgdb是什么?
- 1. 程序的兩種發布模式(debug 和 release)
- 二、gdb/cgdb如何啟動?
- 1. 準備工作
- 2. 啟動 gdb/cgdb 調試器
- 2.1 啟動 gdb
- 2.2 啟動 cgdb(更友好的界面)
- 3. 核心操作(讓程序在指定位置停下)
- 1. 給函數打斷點(按函數名)
- 2. 給行打斷點(按文件名+行號)
- 3. 查看已設置的斷點
- 4. 運行程序 & 讓程序在啟動時停下
- 1. 運行程序:r 命令(run 的縮寫)
- 2. 讓程序“啟動時就停下”(剛進入 main 就暫停)
- 5. 刪除斷點(不需要的斷點可以移除)
- 1. 按斷點編號刪除:delete 斷點編號
- 2. 按位置刪除:clear 位置
- 三、gdb/cgdb的常見使用
- 1. 基礎操作:啟動與退出
- 2. 查看源代碼:調試時“看到”代碼
- 1. list 或 l:顯示代碼(默認每次10行)
- 2. list 函數名 或 l 函數名:查看指定函數的代碼
- 3. list 文件名:行號 或 l 文件名:行號:查看指定文件的指定行
- 3 控制程序執行
- 1. run 或 r:啟動程序(從頭開始執行)
- 2. next 或 n:單步執行(不進函數)
- 3. step 或 s:單步執行(進函數)
- 4. continue 或 c:從當前位置繼續運行
- 5. finish:執行到當前函數結束
- 6. until 行號 或 u 行號:快速執行到指定行
- 7. 斷點管理
- 8. 查看和修改變量
- 1. print 變量 或 p 變量:打印變量值
- 2. print 表達式 或 p 表達式:計算表達式結果
- 3. set var 變量=值:臨時修改變量值
- 4. display 變量:自動跟蹤變量值
- 5. undisplay 編號:取消跟蹤變量
- 9. 查看調用棧
- 1. backtrace 或 bt:查看函數調用棧
- 2. info locals或 i locals:查看當前函數的所有局部變量
- 常用命令速查表
前言
在之前的內容中,我們圍繞 Linux 環境,系統講解了 Git 版本控制系統的核心知識。
本篇博客將聚焦 調試工具領域,正式開啟 GDB/CGDB 調試器 的全方位使用指南。
我的個人主頁,歡迎來閱讀我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知識文章專欄
歡迎來閱讀指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
一、gdb/cgdb是什么?
- 簡單說,它們是調試工具。
- 寫程序時難免會出錯(比如程序突然崩潰、結果不對),這時候就需要工具幫我們找出問題在哪行代碼、為什么出錯——gdb 就是干這個
- cgdb 可以理解成 gdb 的“增強版”,帶了更友好的界面(比如能同時顯示代碼和調試信息),用起來更方便,但核心功能和 gdb 差不多。
1. 程序的兩種發布模式(debug 和 release)
你寫的代碼(比如 .c
或 .cpp
文件),需要通過 gcc/g++
編譯成能直接運行的程序(比如 Windows 里的 .exe
,Linux 里的可執行文件)。
編譯時可以選兩種模式:
-
debug 模式:
編譯出來的程序會帶很多“調試信息”(比如哪行代碼對應程序里的哪個位置),就像給程序加了“標記”,方便 gdb 這類工具定位錯誤。但因為帶了這些信息,程序會大一點,運行速度也稍慢。適合開發時用(寫代碼、找錯階段)。 -
release 模式:
編譯時會去掉調試信息,還會做一些優化(讓程序更小、運行更快)。適合最終發布給用戶用(比如你寫的工具、軟件,給別人用的時候就用這種模式)。 -
關鍵:用 gdb 調試必須加
-g
Linux 里用 gcc/g++
編譯時,默認是 release 模式(不加 -g
的話,編譯出來的程序沒有調試信息)。
- 這時候如果想用 gdb 調試,gdb 會“看不懂”程序(找不到代碼位置),沒法幫你找錯。
所以,如果我們想在開發階段用 gdb 調試,編譯時必須加 -g
選項(告訴編譯器:用 debug 模式,保留調試信息)。
比如:
gcc -g test.c -o test
(編譯 test.c,生成帶調試信息的程序 test,之后就能用 gdb 調試了)
二、gdb/cgdb如何啟動?
1. 準備工作
在開始調試前,必須用 -g
選項編譯程序(生成帶調試信息的版本)。
我們需要有個簡單的 C 程序 test.c
:
#include <stdio.h>// 加法函數
int add(int a, int b) {return a + b;
}int main() {int x = 5;int y = 3;int result = add(x, y);printf("結果是:%d\n", result);return 0;
}
編譯命令(必須加 -g
):
gcc -g test.c -o test # 生成可執行文件 test,帶調試信息
2. 啟動 gdb/cgdb 調試器
2.1 啟動 gdb
在終端輸入以下命令,啟動 gdb 并加載要調試的程序:
gdb ./test # ./test 是剛才編譯好的程序
啟動后會進入 gdb 的交互界面,顯示類似這樣的信息(最后一行是 (gdb)
提示符,等待輸入命令):
GNU gdb (GDB) Red Hat Enterprise Linux 10.2-6.el9
...
Reading symbols from ./test...
(gdb) # 這里可以輸入調試命令
2.2 啟動 cgdb(更友好的界面)
cgdb 是 gdb 的“增強版”,能同時顯示代碼和調試信息,操作命令和 gdb 完全一樣。
如果沒安裝,先在 CentOS 9 上安裝:
sudo dnf install cgdb # 安裝 cgdb
啟動 cgdb 的命令和 gdb 類似:
cgdb ./test # 啟動后界面會分成上下兩部分,上面顯示代碼,下面是命令行
3. 核心操作(讓程序在指定位置停下)
斷點是調試的核心——程序運行到斷點處會自動暫停,方便我們查看變量、一步步執行代碼。
1. 給函數打斷點(按函數名)
命令:b 函數名
(b 是 break 的縮寫)
例子:給 add
函數設置斷點
(gdb) b add # 給 add 函數設置斷點
Breakpoint 1 at 0x400526: file test.c, line 5. # 提示:斷點1已設置,在 test.c 第5行
2. 給行打斷點(按文件名+行號)
命令:b 文件名:行號
例子:給 main
函數里的第8行(int result = add(x, y);
)設置斷點
(gdb) b test.c:8 # 給 test.c 的第8行設置斷點
Breakpoint 2 at 0x400540: file test.c, line 8. # 提示:斷點2已設置
3. 查看已設置的斷點
命令:info breakpoints
(或簡寫 info b
)
(gdb) info b # 查看所有斷點
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400526 in add at test.c:5
2 breakpoint keep y 0x0000000000400540 in main at test.c:8
Num
是斷點編號(后面刪除斷點會用到);Enb
是“是否啟用”(y 表示啟用,n 表示禁用)。
4. 運行程序 & 讓程序在啟動時停下
1. 運行程序:r 命令(run 的縮寫)
在 gdb 中輸入 r
,程序會開始運行,直到遇到斷點才會暫停。
例子:我們已經設置了 add
函數和第8行的斷點,運行程序:
(gdb) r # 啟動程序運行
Starting program: /home/user/test Breakpoint 2, main () at test.c:8 # 程序在斷點2(第8行)停下了
8 int result = add(x, y);
(gdb) # 此時可以進行下一步操作(如查看變量、單步執行等)
2. 讓程序“啟動時就停下”(剛進入 main 就暫停)
如果想讓程序一啟動就停下(還沒執行任何邏輯),最常用的方法是給 main
函數設置斷點:
(gdb) b main # 給 main 函數設置斷點
Breakpoint 3 at 0x400535: file test.c, line 6.(gdb) r # 運行程序
Starting program: /home/user/test Breakpoint 3, main () at test.c:6 # 直接停在 main 函數的第一行代碼
6 int x = 5;
(gdb) # 此時可以從程序入口開始一步步調試
5. 刪除斷點(不需要的斷點可以移除)
1. 按斷點編號刪除:delete 斷點編號
例子:刪除編號為2的斷點(之前設置的第8行斷點)
(gdb) delete 2 # 刪除斷點2
(gdb) info b # 查看剩余斷點,會發現編號2的斷點已消失
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400526 in add at test.c:5
3 breakpoint keep y 0x0000000000400535 in main at test.c:6
2. 按位置刪除:clear 位置
例子:刪除 add
函數的斷點
(gdb) clear add # 刪除 add 函數的斷點
Deleted breakpoint 1
如果想刪除所有斷點,直接輸入 delete
(不加編號):
(gdb) delete # 刪除所有斷點
Delete all breakpoints? (y or n) y # 輸入 y 確認
三、gdb/cgdb的常見使用
1. 基礎操作:啟動與退出
這是調試的“開門”和“關門”操作,必須先掌握。
命令 | 作用 | 例子 |
---|---|---|
gdb 程序名 | 啟動 gdb 并加載程序 | gdb ./test |
cgdb 程序名 | 啟動 cgdb 并加載程序 | cgdb ./test |
quit 或 q | 退出調試器 | 輸入 q 后回車 |
ctrl + d | 快速退出(和 q 一樣) | 按住 ctrl 再按 d |
2. 查看源代碼:調試時“看到”代碼
調試時需要知道當前在執行哪行代碼,list
命令專門用來顯示源代碼。
1. list 或 l:顯示代碼(默認每次10行)
- 作用:從上次顯示的位置繼續往下列10行代碼(第一次用從程序開頭列)。
- 例子:
(gdb) l # 顯示前10行代碼(如果代碼短,會顯示全部) 1 #include <stdio.h> 2 3 // 加法函數 4 int add(int a, int b) { 5 return a + b; 6 } 7 8 int main() { 9 int x = 5; 10 int y = 3; (gdb) l # 再按一次,顯示接下來的10行 11 int result = add(x, y); 12 printf("結果是:%d\n", result); 13 return 0; 14 }
2. list 函數名 或 l 函數名:查看指定函數的代碼
- 作用:直接顯示某個函數的源代碼(方便定位函數邏輯)。
- 例子:查看
add
函數的代碼(gdb) l add # 顯示 add 函數的代碼 4 int add(int a, int b) { 5 return a + b; 6 } 7 8 int main() { 9 int x = 5; 10 int y = 3; 11 int result = add(x, y);
3. list 文件名:行號 或 l 文件名:行號:查看指定文件的指定行
- 作用:如果程序有多個文件(比如
test.c
、calc.c
),可以精確指定查看某文件的某行。 - 例子:查看
test.c
的第8行(main
函數開始處)(gdb) l test.c:8 # 顯示 test.c 第8行附近的代碼 3 // 加法函數 4 int add(int a, int b) { 5 return a + b; 6 } 7 8 int main() { 9 int x = 5; 10 int y = 3; 11 int result = add(x, y); 12 printf("結果是:%d\n", result);
3 控制程序執行
這是調試的核心——控制程序執行節奏,想讓它停就停,想讓它走就走。
1. run 或 r:啟動程序(從頭開始執行)
- 作用:讓程序從入口(如
main
函數)開始運行,直到遇到斷點才停下(沒斷點就直接跑完)。 - 例子:之前設置了
main
函數斷點,運行程序(gdb) r # 啟動程序 Starting program: /home/user/test Breakpoint 1, main () at test.c:9 # 停在 main 函數的第9行 9 int x = 5;
2. next 或 n:單步執行(不進函數)
- 作用:執行當前行代碼,然后停下;如果當前行是調用函數(比如
add(x,y)
),不進入函數內部,直接執行完整個函數再停下。 - 例子:當前停在
main
函數第9行,用n
執行下一步(gdb) n # 執行第9行(int x=5;),停到第10行 10 int y = 3; (gdb) n # 執行第10行(int y=3;),停到第11行 11 int result = add(x, y); (gdb) n # 執行第11行(調用 add 函數),不進入 add 內部,直接得到結果,停到第12行 12 printf("結果是:%d\n", result);
3. step 或 s:單步執行(進函數)
- 作用:和
next
類似,但如果當前行是調用函數(比如add(x,y)
),會進入函數內部,停在函數的第一行代碼。 - 例子:同樣停在第11行,用
s
執行(gdb) s # 執行第11行,進入 add 函數內部 add (a=5, b=3) at test.c:5 # 停在 add 函數的第5行 5 return a + b; (gdb) s # 執行 add 函數第5行,返回 main 函數 main () at test.c:12 # 回到 main 函數第12行 12 printf("結果是:%d\n", result);
4. continue 或 c:從當前位置繼續運行
- 作用:程序在斷點處停下后,用
c
讓它繼續往下跑,直到遇到下一個斷點(或程序結束)。 - 例子:在
main
第9行停下后,想直接跑到add
函數斷點(gdb) c # 從第9行繼續運行 Continuing. Breakpoint 2, add (a=5, b=3) at test.c:5 # 遇到 add 函數斷點停下 5 return a + b;
5. finish:執行到當前函數結束
- 作用:如果現在在某個函數內部(比如
add
函數),用finish
會執行完這個函數的所有代碼,然后回到調用它的地方停下。 - 例子:在
add
函數內部時(gdb) finish # 執行完 add 函數 Run till exit from #0 add (a=5, b=3) at test.c:5 main () at test.c:12 # 回到 main 函數調用 add 的下一行 12 printf("結果是:%d\n", result); Value returned is $1 = 8 # 還會顯示函數的返回值(8)
6. until 行號 或 u 行號:快速執行到指定行
- 作用:從當前位置直接跑到指定行停下(比一次次按
n
快,適合跳過中間無關代碼)。 - 例子:當前在
main
第9行,想直接跑到第12行(gdb) u 12 # 執行到第12行停下 main () at test.c:12 12 printf("結果是:%d\n", result);
7. 斷點管理
除了之前講的“設置斷點”和“刪除斷點”,還有兩個常用操作:暫時禁用(不用刪除,以后還能用)和重新啟用。
命令 | 作用 | 例子 |
---|---|---|
info break 或 info b | 查看所有斷點信息 | info b # 顯示斷點編號、位置等 |
disable 斷點編號 | 禁用指定斷點(暫時失效) | disable 1 # 禁用編號1的斷點 |
enable 斷點編號 | 啟用指定斷點(恢復生效) | enable 1 # 啟用編號1的斷點 |
disable breakpoints | 禁用所有斷點 | disable breakpoints |
enable breakpoints | 啟用所有斷點 | enable breakpoints |
例子:
(gdb) info b # 查看當前有2個斷點
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400526 in add at test.c:5
2 breakpoint keep y 0x0000000000400535 in main at test.c:9(gdb) disable 1 # 禁用斷點1(add函數的斷點)
(gdb) info b # 查看斷點1的Enb變成n(禁用)
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000000000400526 in add at test.c:5
2 breakpoint keep y 0x0000000000400535 in main at test.c:9(gdb) enable 1 # 重新啟用斷點1
(gdb) info b # 斷點1的Enb變回y(啟用)
8. 查看和修改變量
調試的核心目的之一是看變量的值對不對,甚至可以臨時改值測試。
1. print 變量 或 p 變量:打印變量值
- 作用:顯示當前作用域內某個變量的值(比如
x
、result
)。 - 例子:當前在
main
第10行,打印x
和y
的值(gdb) p x # 打印 x 的值 $1 = 5 (gdb) p y # 打印 y 的值 $2 = 3
2. print 表達式 或 p 表達式:計算表達式結果
- 作用:不僅能打印變量,還能計算表達式(比如
x+y
、result*2
)。 - 例子:
(gdb) p x + y # 計算 x+y 的結果 $3 = 8 (gdb) p result * 2 # 假設 result 是8,計算 8*2 $4 = 16
3. set var 變量=值:臨時修改變量值
- 作用:調試時發現變量值錯了,可以臨時修改,看程序后續執行是否正常(不用改代碼重新編譯)。
- 例子:把
y
的值從3改成10(gdb) p y # 原來 y 是3 $5 = 3 (gdb) set var y=10 # 臨時改成10 (gdb) p y # 確認修改成功 $6 = 10 (gdb) n # 繼續執行,此時 add(x,y) 會計算 5+10=15
4. display 變量:自動跟蹤變量值
- 作用:設置后,每次程序暫停(比如單步執行、遇到斷點),會自動顯示這個變量的值(不用每次手動
p
)。 - 例子:跟蹤
result
變量(gdb) display result # 開始跟蹤 result 1: result = 0 # 初始值(還沒賦值) (gdb) n # 執行到賦值 result 的行 11 int result = add(x, y); (gdb) n # 賦值后,自動顯示 result 的新值 12 printf("結果是:%d\n", result); 1: result = 15 # 自動顯示更新后的值
5. undisplay 編號:取消跟蹤變量
- 作用:
display
會給每個跟蹤的變量分配一個編號,用undisplay 編號
取消跟蹤。 - 例子:取消跟蹤上面的
result
(編號是1)(gdb) undisplay 1 # 取消編號1的跟蹤 (gdb) n # 再執行,不會自動顯示 result 了 13 return 0;
9. 查看調用棧
當程序調用多個函數時(比如 main
調用 add
),backtrace
能顯示“誰調用了誰”,方便定位當前執行位置。
1. backtrace 或 bt:查看函數調用棧
- 作用:顯示從程序啟動到當前位置的所有函數調用關系(最上面是當前執行的函數,往下是調用它的函數)。
- 例子:在
add
函數內部時(gdb) bt # 查看調用棧 #0 add (a=5, b=10) at test.c:5 # 當前在 add 函數 #1 0x0000000000400554 in main () at test.c:11 # add 是被 main 第11行調用的
2. info locals或 i locals:查看當前函數的所有局部變量
- 作用:一次性顯示當前所在函數(比如
add
或main
)的所有局部變量及其值,不用一個個print
。 - 例子:在
main
函數內部時(gdb) i locals # 顯示 main 函數的局部變量 x = 5 y = 10 result = 15
常用命令速查表
為了方便記憶,整理成表格(按使用頻率排序):
功能分類 | 命令 | 作用描述 |
---|---|---|
基礎操作 | gdb 程序名 | 啟動調試 |
q | 退出調試 | |
查看代碼 | l / l 函數名 | 顯示源代碼(默認10行/指定函數) |
執行控制 | r | 啟動程序 |
n | 單步執行(不進函數) | |
s | 單步執行(進函數) | |
c | 繼續運行到下一個斷點 | |
斷點管理 | b 行號/函數名 | 設置斷點 |
info b | 查看所有斷點 | |
delete 編號 | 刪除指定斷點 | |
變量操作 | p 變量 | 打印變量值 |
set var 變量=值 | 修改變量值 | |
display 變量 | 自動跟蹤變量值 | |
函數與棧 | finish | 執行到當前函數結束 |
bt | 查看函數調用棧 | |
i locals | 查看當前函數的局部變量 |
以上就是這篇博客的全部內容,下一篇我們將繼續探索Linux的更多精彩內容
我的個人主頁
歡迎來閱讀我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知識文章專欄
歡迎來閱讀指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482
非常感謝您的閱讀,喜歡的話記得三連哦 |