版權(quán)聲明:本文為 cdeveloper 原創(chuàng)文章焙矛,可以隨意轉(zhuǎn)載放闺,但必須在明確位置注明出處凉夯!
gdb 簡(jiǎn)介
gdb
是 UNIX
及 UNIX-like
下的調(diào)試工具鱼辙,在 Linux 下一般都直接在命令行中用 gdb 來(lái)調(diào)試程序峰档,相比 Windows 上的集成開(kāi)發(fā)環(huán)境 IDE
提供的圖形界面調(diào)試间校,一開(kāi)始使用 gdb 調(diào)試可能會(huì)讓你感到生無(wú)可戀肛跌,但是只要熟悉了 gdb 調(diào)試的常用命令刊棕,調(diào)試出程序會(huì)很有成就感个曙,一方面因?yàn)檫@些命令就類似圖形界面調(diào)試按鈕背后的邏輯锈嫩,另一方面用命令行來(lái)調(diào)試程序,逼格瞬間就上了一個(gè)檔次垦搬,這次就跟大家分享 gdb 調(diào)試的基本技術(shù)和 15 個(gè)常用調(diào)試命令祠挫。
在此之前,我們先來(lái)回顧下在 Windows 上使用 IDE 的圖形界面調(diào)試過(guò)程悼沿。
IDE 的調(diào)試步驟
在 Windows 的 IDE 下調(diào)試程序等舔,例如使用 VS,一般都有下面這幾個(gè)操作:
- Debug 模式編譯并啟動(dòng)程序
- 程序運(yùn)行出錯(cuò)糟趾,打斷點(diǎn)分析出錯(cuò)的地方
- 單步運(yùn)行程序慌植,包括:
step over
單步執(zhí)行甚牲;step into
跳入函數(shù);step return
跳出函數(shù) - 還有全速運(yùn)行蝶柿,打印或者監(jiān)視變量丈钙,凍結(jié)或解凍線程等調(diào)試技術(shù)
在 IDE 中上面的這些步驟一般都有固定的按鈕提供給我們使用,非常的簡(jiǎn)單方便交汤,我們只要多練習(xí)練習(xí)雏赦,在圖形界面調(diào)試程序不會(huì)很難,但是在 Linux 下用命令來(lái)調(diào)試程序就比圖形界面要復(fù)雜很多了芙扎。
其實(shí)星岗,你知道真正的調(diào)試高手是什么樣的嗎?就是 Ta 對(duì)計(jì)算機(jī)原理和程序本身的邏輯理解非常深刻戒洼,在 Ta 的腦海中已經(jīng)可以模擬程序的運(yùn)行過(guò)程俏橘,并且知道可能出錯(cuò)的地方,這樣連斷點(diǎn)都不用打了圈浇,而且 Bug 的命中率也不低寥掐,或許這就是真正的大佬吧 :)
我們回到正題,來(lái)介紹在 Linux 使用命令行的調(diào)試過(guò)程磷蜀。
gdb 的調(diào)試步驟
在 Linux 下既然是使用命令行來(lái)調(diào)試召耘,顧名思義就是手敲命令來(lái)調(diào)試程序,大體分為下面幾個(gè)步驟褐隆,后面會(huì)詳細(xì)介紹:
- 編譯可以調(diào)試的程序
- 運(yùn)行程序污它,打斷點(diǎn)
- 單步調(diào)試,監(jiān)控變量
- 可視化調(diào)試
- 其他調(diào)試技術(shù)
可以看出妓灌,與 IDE 調(diào)試過(guò)程差不多,但是實(shí)際操作起來(lái)可是千差萬(wàn)別蜜宪,可不是點(diǎn)按鈕了虫埂,而是自己敲調(diào)試程序的命令,就相當(dāng)于你正在學(xué)習(xí)那些調(diào)試按鈕背后的原理圃验,把這種方法學(xué)會(huì)掉伏,不用學(xué)就會(huì)使用 IDE 來(lái)調(diào)試程序,不管你信不信澳窑,我反正是信了 :)
下面開(kāi)始正式的調(diào)試技術(shù)介紹斧散。
15 個(gè) gdb 調(diào)試基礎(chǔ)命令
下面來(lái)正式介紹 gdb 常用的調(diào)試技術(shù),都是調(diào)試命令摊聋,只看不做比較乏味鸡捐,還是建議你跟我一起動(dòng)手調(diào)試下面的程序,這樣才能真正的學(xué)會(huì)麻裁,這是本次要調(diào)試的 hello.c
程序箍镜,非常簡(jiǎn)單:
#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. 編譯可以調(diào)試的程序
我們平常使用 gcc 編譯的程序如果不加 [-g]
選項(xiàng):
gcc hello.c -o hello
gdb 會(huì)提示該可執(zhí)行文件沒(méi)有調(diào)試符號(hào)源祈,不能調(diào)試:
gdb hello
# gdb 提示信息
Reading symbols from a.out...(no debugging symbols found)...done.
如果需要讓程序可以調(diào)試,就必須在編譯的時(shí)候加上 [-g]
參數(shù):
gcc -g hello.c -o hello
2. 載入要調(diào)試的程序
我們?cè)诿钚邢滦枰謩?dòng)載入待調(diào)試的程序色迂,有 2 種方法:
方法一 - gdb 可執(zhí)行文件
使用如下的命令來(lái)載入可執(zhí)行文件 hello
到 gdb 中:
gdb hello
載入成功香缺,gdb 會(huì)打印一段提示信息,并且命令行前綴變?yōu)?(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 環(huán)境中使用 file
命令图张,我們需要先進(jìn)入 gdb 環(huán)境下:
gdb
使用 file hello
載入待調(diào)試程序:
...
(gdb) file hello
Reading symbols from hello...done.
(gdb) q
3. 查看調(diào)試程序
在 gdb 下查看調(diào)試程序使用命令 list
或簡(jiǎn)寫 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. 添加斷點(diǎn)
在 gdb 下添加斷點(diǎn)使用命令 break
或簡(jiǎn)寫 b
诈悍,有下面幾個(gè)常見(jiàn)用法(這里統(tǒng)一用 b
):
- b function_name
- b row_num
- b file_name:row_num
- b row_num if condition
比如我們以第一個(gè)為例祸轮,在 main
函數(shù)上添加斷點(diǎn):
(gdb) b main
Breakpoint 1 at 0x6e8: file hello.c, line 4.
打印的信息告訴我們?cè)?hello.c
文件的第 4 行,地址 0x6e8
處添加了一個(gè)斷點(diǎn)写隶,那如何查看斷點(diǎn)呢倔撞?
5. 查看斷點(diǎn)
在 gdb 下查看斷點(diǎn)使用命令 info break
或簡(jiǎn)寫 i b
,比如查看剛才打的斷點(diǎn):
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000006e8 in main at hello.c:4
可以看到打印出剛才添加的 main
函數(shù)的斷點(diǎn)信息:編號(hào)慕趴,類型痪蝇,顯示狀態(tài),是否啟用冕房,地址躏啰,其他信息,那又如何刪除這個(gè)斷點(diǎn)呢耙册?
6. 禁用斷點(diǎn)
在 gdb 下禁用斷點(diǎn)使用命令 disable Num
给僵,比如禁用剛才打的斷點(diǎn):
(gdb) disable 1
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep n 0x00000000000006e8 in main at hello.c:4
可以看到字段「Enb」已經(jīng)變?yōu)?n
,表示這個(gè)斷點(diǎn)已經(jīng)被禁用了详拙。
7. 刪除斷點(diǎn)
在 gdb 下刪除斷點(diǎn)使用命令 delete 斷點(diǎn) Num
或簡(jiǎn)寫 d Num
帝际,比如刪除剛才的 Num = 1
的斷點(diǎn):
(gdb) d 1
(gdb) i b
No breakpoints or watchpoints.
刪除后再次查看斷點(diǎn),提示當(dāng)前沒(méi)有斷點(diǎn)饶辙,說(shuō)明刪除成功啦蹲诀,下面來(lái)運(yùn)行程序試試。
8. 運(yùn)行程序
在 gdb 下使用命令 run
或簡(jiǎn)寫 r
來(lái)運(yùn)行當(dāng)前載入的程序:
(gdb) r
Starting program: /home/orange/Desktop/gdb/hello
a = 1
b = 2
1 + 2 = 3
[Inferior 1 (process 10415) exited normally]
我這次沒(méi)有添加斷點(diǎn)弃揽,程序全速運(yùn)行脯爪,然后正常退出了。
9. 單步執(zhí)行下一步
在 gdb 下使用命令 next
或簡(jiǎn)寫 n
來(lái)單步執(zhí)行下一步矿微,假設(shè)我們?cè)?main
打了斷點(diǎn):
(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);
可以看到當(dāng)前停在 int a = 1;
這一行痕慢,按 n
執(zhí)行了下一句代碼 printf("a = %d\n", a);
10. 跳入,跳出函數(shù)
在 gdb 下使用命令 step
或簡(jiǎn)寫 s
來(lái)跳入一個(gè)函數(shù)涌矢,使用 finish
來(lái)跳出一個(gè)函數(shù)掖举,我們?cè)诘?14 行 int c = add(a, b);
添加一個(gè)斷點(diǎn):
(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);
這個(gè)過(guò)程是這樣的:
- 在 14 行
int c = add(a, b);
添加斷點(diǎn) - 程序運(yùn)行并停到
int c = add(a, b);
這一行 -
s
跳入 add 函數(shù) -
finish
跳出 add 函數(shù),并輸出一些函數(shù)返回的信息
11. 打印變量
在 gdb 中使用命令 print var
或簡(jiǎn)寫 p var
來(lái)打印一個(gè)變量或者函數(shù)的返回值娜庇,我們?cè)诘?10 行 int b = 2;
添加一個(gè)斷點(diǎn):
(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拇泛,在調(diào)試中比較頻繁的操作是「監(jiān)視變量」滨巴,在 gdb 中如何做呢?
12. 監(jiān)控變量
在 gdb 中使用命令 watch var
來(lái)監(jiān)控一個(gè)變量俺叭,使用 info watch
來(lái)查看監(jiān)控的變量恭取,我們這里來(lái)監(jiān)控變量 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
注意:程序必須要先運(yùn)行才能監(jiān)控。
13. 查看變量類型
在 gdb 下使用命令 whatis
查看一個(gè)變量的類型:
(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
這里變量 b
是 int
類型熄守。
14. 在 gdb 中進(jìn)入 shell
在 gdb 下使用命令 shell
啟動(dòng) shell :
(gdb) shell
orange@ubuntu:~/Desktop/gdb$ exit
exit
(gdb)
使用 exit
會(huì)再次退回到 gdb 中蜈垮。
15. 在 gdb 中實(shí)現(xiàn)可視化調(diào)試
誰(shuí)說(shuō) gdb 只能在命令行調(diào)試呢?gdb 也支持「圖形界面」裕照,不過(guò)這里的圖形界面都是用字符顯示的攒发,當(dāng)然不如 VS 那種好看,不過(guò)使用可視化相比直接看命令行更加直觀了晋南。
在 gdb 下使用 wi
啟動(dòng)可視化調(diào)試:
(gdb) wi
效果如下圖所示惠猿,上面是代碼效果,下面是命令界面:
有了圖形界面负间,就再對(duì)照著圖形界面將前面的命令再練習(xí)練習(xí)偶妖,看看自己手敲的命令背后到底做了些什么,加深下影響政溃。
結(jié)語(yǔ)
這篇博客主要介紹了 gdb 基本的調(diào)試技術(shù)趾访,一篇文章不可能面面俱到,還有很多命令沒(méi)有介紹董虱,如果你有興趣的話扼鞋,這里還有一份比較好的 gdb 快速學(xué)習(xí)指南 送給愛(ài)學(xué)習(xí)的你,不用客氣愤诱,叫我雷鋒就好云头。
最后,感謝你的閱讀淫半,我們下次再見(jiàn) :)