轉(zhuǎn)載自:[深入淺出GDB調(diào)試器] (深入淺出GDB調(diào)試器 (qq.com))
前言
GDB全稱GNU symbolic debugger,它是誕生于GNU開(kāi)源組織的(同時(shí)誕生的還有 GCC、Emacs 等)UNIX及UNIX-like下的調(diào)試工具,是Linux下最常用的程序調(diào)試器,GDB 支持調(diào)試多種編程語(yǔ)言編寫(xiě)的程序,包括C、C++、Go、Objective-C、OpenCL、Ada 等。但是在實(shí)際應(yīng)用中,GDB 更常用來(lái)調(diào)試C和C++程序。雖然說(shuō)在Linux系統(tǒng)下我們可以借助諸多集成開(kāi)發(fā)工具來(lái)完成程序的編寫(xiě)和調(diào)試,但實(shí)際上,調(diào)試C/C++程序一定是直接或者間接使用GDB完成的。所以說(shuō)GDB調(diào)試幾乎可以說(shuō)是Linux程序員必備的基本技能。本文將手把手教你使用GDB調(diào)試程序,并帶你深入了解什么是GDB調(diào)試器。

文章目錄
前言
一、什么是GDB
(1)查看GDB版本
(2)安裝GDB調(diào)試器
(3)卸載GDB
- 為什么要有GDB
- 下載安裝GDB
二、GDB的啟動(dòng)與調(diào)試程序的上下文設(shè)置
(1)gdb工作目錄
(2)程序運(yùn)行參數(shù)
(3)查看及修改運(yùn)行環(huán)境
(4)輸入輸出重定向
(1)測(cè)試程序中的main函數(shù)參數(shù)解析argc與argv[]
(2)gcc編譯時(shí) ==-g== 選項(xiàng)幫我們做了什么?
(3)啟動(dòng)GDB與指定目標(biāo)調(diào)試程序的方式 - 準(zhǔn)備知識(shí)
- 程序上下文
三、GDB實(shí)戰(zhàn)講解
(1)創(chuàng)建一個(gè)多線程測(cè)試文件
(2)undefined reference to `pthread_create' 錯(cuò)誤
(3)多線程調(diào)試
(1)什么是 core dump 核心轉(zhuǎn)儲(chǔ)
(2)產(chǎn)生 core dump 的原因
(3)core 文件的相關(guān)配置與 shell 資源限制
(4)通過(guò)core文件調(diào)試當(dāng)?shù)舻某绦?br> (1)調(diào)試非運(yùn)行狀態(tài)的可執(zhí)行程序
(2)調(diào)試一個(gè)正在運(yùn)行的程序
(1)r(run)運(yùn)行與start運(yùn)行程序
(2)q(quit)退出調(diào)試
(3)help
(4)l(lsit)查看代碼
(5)set 傳入?yún)?shù)
(6)n(next)執(zhí)行下一條語(yǔ)句,不進(jìn)入函數(shù)內(nèi)部
(7)s(step)執(zhí)行下一條語(yǔ)句,且進(jìn)入函數(shù)內(nèi)部
(8)u(until)
(9)b(break)設(shè)置斷點(diǎn)以及打斷點(diǎn)的六種方式
(10)tbreak
(11)rbreak
(12)disable 與 enable
(13)watch
(14)rwatch
(15)awatch
(16)catch
(17)c(continue)執(zhí)行到下一個(gè)斷點(diǎn)處
(18)info 查看
(19)del(delete)刪除
(20)clear
(21)ignore
(22)p (print)
(23)ptype 查看類型
(24)display 跟蹤變化
(25)undisplay 取消跟蹤
(26)bt (backtrace)查看棧信息
(27)x 查看內(nèi)存
(28)disas 反匯編
(29)finish
(30)return
(31)call
(32)edit
(33)search - GDB命令詳解
- GDB跟蹤可以正常編譯運(yùn)行的源文件
- GDB跟蹤core(調(diào)試掛掉的程序)
- GDB調(diào)試多線程
總結(jié)
一、什么是GDB
- 為什么要有GDB
我們?cè)陂_(kāi)發(fā)程序的過(guò)程中,應(yīng)該很少會(huì)有一次就編譯通過(guò)的吧,有時(shí)候即便是寫(xiě)了短短幾十行的代碼,都難免會(huì)有一些小的疏忽,更何況是幾千上萬(wàn)甚至更大的代碼,反正我在開(kāi)發(fā)中幾乎每次寫(xiě)完程序都會(huì)經(jīng)過(guò)反復(fù)的調(diào)試,鍵盤(pán)的F11鍵經(jīng)常會(huì)壞掉。在程序中,出現(xiàn)的錯(cuò)誤主要分為 2大 類,即語(yǔ)法錯(cuò)誤和邏輯錯(cuò)誤:
語(yǔ)法錯(cuò)誤,顧名思義就是不符合編程語(yǔ)言語(yǔ)法的錯(cuò)誤,這類錯(cuò)誤一般都可以由編譯器診斷出來(lái),GCC編譯器的編譯階段會(huì)進(jìn)行語(yǔ)法檢查(這方面內(nèi)容我在GCC編譯器那篇文章中已經(jīng)詳細(xì)介紹過(guò)了);
邏輯錯(cuò)誤,這部分錯(cuò)誤是指我們?cè)诔绦蛟O(shè)計(jì)的邏輯上的錯(cuò)誤,程序編譯通過(guò),但是執(zhí)行結(jié)果并不符合我們的預(yù)期,這類錯(cuò)誤就沒(méi)有辦法依靠GCC編譯器去檢查了,需要我們自己調(diào)試分析;
程序出現(xiàn)語(yǔ)法錯(cuò)誤,可以依靠GCC檢查出來(lái),而邏輯錯(cuò)誤就要我們今天的主角GDB登場(chǎng)解決了。所謂調(diào)試(Debug),就是單步執(zhí)行代碼,或通過(guò)斷點(diǎn)讓程序執(zhí)行到某個(gè)位置,以此來(lái)逐步鎖定程序出現(xiàn)問(wèn)題的范圍。在單步調(diào)試的過(guò)程中,我們可以監(jiān)控程序執(zhí)行的每一個(gè)行為,包括變量值的變化、函數(shù)的調(diào)用、內(nèi)存中數(shù)據(jù)的變化、線程的調(diào)度等等,以此來(lái)修復(fù)BUG或者優(yōu)化代碼。
我們?cè)赪indows下開(kāi)發(fā)最常用的Visual Studio,它自帶的調(diào)試器是Remote Debugger,調(diào)試器與整個(gè)IDE無(wú)縫銜接,使用非常方便。在Linux下C/C++必備的調(diào)試器就是GDB了,下面講解如何查看GDB版本及安裝GDB。 - 下載安裝GDB
(1)查看GDB版本
gdb -v
gdb --version
如果你的執(zhí)行結(jié)果如下,說(shuō)明已經(jīng)安裝好了gdb,版本號(hào)如下,一般我們裝好Linux后可以通過(guò)這個(gè)命令來(lái)測(cè)試是否已經(jīng)安裝gdb調(diào)試器。

如果你的運(yùn)行結(jié)果顯示 not found ,說(shuō)明未安裝gdb調(diào)試器,安裝gdb的方法主要有兩個(gè),下面一節(jié)介紹安裝方法。
bash: gdb: command not found
(2)安裝GDB調(diào)試器
安裝gdb主要有兩種方法:
① 直接安裝。通常我們安裝好Linux之后,操作系統(tǒng)內(nèi)會(huì)附帶有g(shù)db的安裝包,我們可以直接使用操作系統(tǒng)內(nèi)已有的gdb安裝包,使用包管理器進(jìn)行安裝。這種方法簡(jiǎn)單有效,只需要一條命令就可以安裝成功(以CentOS為例)
yum -y install gdb
安裝好后,可以通過(guò) gdb -v 查看版本,一般來(lái)說(shuō)通過(guò)這種方式安裝的gdb都不是最新版本,并且無(wú)法自己選擇版本。
② 通過(guò)源碼安裝。源碼安裝是指首先去網(wǎng)上下載源碼壓縮包,然后在本地解壓安裝,我們可以選擇自己需要的版本進(jìn)行安裝,可以直接點(diǎn)擊源碼包的鏈接gdb源碼去下載。

里面有很多版本和格式,我們可以選擇一個(gè)自己需要的版本 .tar.gz 格式下載,下載后進(jìn)行下面的操作.
解壓文件
找到下載好的壓縮包并解壓
tar -zxvf gdb-11.2.tar.gz
如果你是在Windows下下載好的壓縮包,要傳到Linux下,可以借助SecureCRT的rz命令.

后面就只說(shuō)命令了。
解壓后進(jìn)入解壓出來(lái)的目錄
運(yùn)行 configure 文件配置環(huán)境,這時(shí)候會(huì)創(chuàng)建一個(gè)Makefile文件
make 編譯源碼文件
安裝 make install
gdb -v 查看
tar -zxvf gdb-11.2.tar.gz
cd gdb-11.2
./configure
make
make install
gdb -v
(3)卸載GDB
gdb調(diào)試器的卸載命令
yum remove gdb
二、GDB的啟動(dòng)與調(diào)試程序的上下文設(shè)置
- 準(zhǔn)備知識(shí)
(1)測(cè)試程序中的main函數(shù)參數(shù)解析argc與argv[]
首先我們創(chuàng)建一個(gè)C文件gdb_test.c,以用于后面舉例使用,程序如下
#include <stdio.h>
#include <stdlib.h>
struct st
{
int a;
int b;
};
void print_array(char* array, int len)
{
int i = 0;
for(i = 0; i < len; i++)
{
printf("array[%d]: %c\n", i, array[i]);
}
}
int main(int argc, char* argv[])
{
struct st st_temp;
int i = 0;
char array[5];
st_temp.a = 10;
st_temp.b = 11;
for(i = 0; i < 5; i++)
{
array[i] = i + '0';
}
print_array(array, 5);
for(i = 0; i < argc; i++)
{
printf("hello...argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
在這個(gè)測(cè)試程序中,main函數(shù)貌似有點(diǎn)不同尋常啊
int main(int argc, char* argv[])
多了兩個(gè)東西,argc和argv,其實(shí)在main函數(shù)中本就應(yīng)該有這兩個(gè)參數(shù),只不過(guò)在我們平常的大部分學(xué)習(xí)中,都弱化了這兩個(gè)參數(shù)的作用,估計(jì)大部分人在學(xué)習(xí)編程時(shí)都從來(lái)沒(méi)有寫(xiě)過(guò)這兩個(gè)參數(shù)。第一個(gè)參數(shù)argc用來(lái)統(tǒng)計(jì)程序運(yùn)行時(shí)傳遞給main函數(shù)的命令行參數(shù)的個(gè)數(shù),這個(gè)不需要我們?cè)O(shè)置;argv是一個(gè)字符串?dāng)?shù)組,用來(lái)存放我們傳入的參數(shù),其中argv[0]默認(rèn)就是程序運(yùn)行的路徑名。說(shuō)起來(lái)不好理解,我們舉個(gè)例子,就用上面給出的gdb_test.c文件,我們編譯好運(yùn)行一下,并傳遞參數(shù)
gcc gdb_test.c -o g3
./g3 111111

首先可以看到argc的值是2,argv的第一個(gè)參數(shù)是 ./g3 表示當(dāng)前目錄,第二個(gè)參數(shù)是我們傳入的111111。如果我們不傳任何參數(shù),argc就是1,argv只有一個(gè)字符串就是當(dāng)前路徑。

(2)gcc編譯時(shí) -g 選項(xiàng)幫我們做了什么?
gdb主要的作用是跟蹤程序的執(zhí)行過(guò)程,所以要想用gdb調(diào)試程序,首先要把源程序編譯為可執(zhí)行文件。但是,我們正常使用gcc命令編譯出來(lái)的可執(zhí)行文件是無(wú)法通過(guò)gdb調(diào)試的,因?yàn)檫@樣編譯出來(lái)的可執(zhí)行文件缺少gdb調(diào)試所需要的調(diào)試信息(比如每一行代碼的行號(hào)、包含程序中所有符號(hào)的符號(hào)表等信息)。要想生成帶有g(shù)db調(diào)試信息的可執(zhí)行文件,就要在gcc編譯的時(shí)候添加==-g== 選項(xiàng)。
你可能通過(guò)嘗試后會(huì)說(shuō),不加gcc的 -g 選項(xiàng)也能進(jìn)入gdb調(diào)試,確實(shí)是這樣,但是進(jìn)入gdb并不代表就可以調(diào)試,比如下面

我們不加 -g 編譯一個(gè)源文件,并啟動(dòng)gdb

進(jìn)入gdb后我們發(fā)現(xiàn),使用 r 命令執(zhí)行可以,但是通過(guò) list 查看源代碼卻不行。這是因?yàn)?,我們不?-g 編譯出來(lái)的可執(zhí)行文件不包含行號(hào)和符號(hào)表等調(diào)試所需要的信息,所以你想查看源碼、添加斷點(diǎn)都是無(wú)法實(shí)現(xiàn)的。而這就是 -g 選項(xiàng)的作用,我們可以對(duì)比一下加與不加 -g 選項(xiàng)生成的可執(zhí)行文件大小
能夠看得出,加了 -g 選項(xiàng)后編譯出來(lái)的可執(zhí)行文件占據(jù)了更多個(gè)空間,這是因?yàn)?,它包含了調(diào)試信息。
有時(shí)候,我們?cè)诰幾g時(shí)會(huì)組合 -g 和 -O 來(lái)使用,通常用 -Og 來(lái)實(shí)現(xiàn)在保證快速編譯和更好的調(diào)試前提下,進(jìn)行一定的優(yōu)化。
(3)啟動(dòng)GDB與指定目標(biāo)調(diào)試程序的方式
啟動(dòng)gdb調(diào)試器分為四種情況:
① 調(diào)試非運(yùn)行狀態(tài)且編譯通過(guò)可運(yùn)行的可執(zhí)行文件
gdb exe(可執(zhí)行文件名)
gdb ./exe(可執(zhí)行文件名)
② 調(diào)試正在運(yùn)行的可執(zhí)行文件
gdb -p pid(進(jìn)程號(hào))
③ 調(diào)試core
gdb exe(可執(zhí)行文件名) core.19761(core文件名)
gdb ./exe(可執(zhí)行文件名) core.19761(core文件名)
上面這三種情況會(huì)在后面對(duì)應(yīng)的章節(jié)詳細(xì)介紹。
④ 假如直接使用 gdb 命令進(jìn)入gdb調(diào)試器,gdb自己是無(wú)法確定要調(diào)試哪個(gè)可執(zhí)行文件的,即使當(dāng)前目錄只有一個(gè)可執(zhí)行文件也無(wú)法自動(dòng)識(shí)別,這時(shí)我們可以手動(dòng)指定目標(biāo)調(diào)試文件。

提示信息中已經(jīng)告訴我們使用哪個(gè)命令來(lái)指定待調(diào)試程序了,那就是 file 命令,使用方法是 file 直接加可執(zhí)行文件所在目錄以及可執(zhí)行文件名,如果可執(zhí)行文件就在gdb當(dāng)前工作目錄下,可以不加目錄,這樣我們就可以使用gdb調(diào)試 file 命令指定的可執(zhí)行文件了

不管哪種情況,我們進(jìn)入gdb時(shí),總會(huì)打印一堆聲明

要想去掉這些聲明,可以在gdb后面加 –silent 或 -q 或 –quiet 選項(xiàng)。

只要最下面有一個(gè) (gdb) 就說(shuō)明進(jìn)入成功。
-
程序上下文
(1)gdb工作目錄
默認(rèn)情況下,GDB調(diào)試器會(huì)把啟動(dòng)時(shí)所在的目錄作為工作目錄,但有時(shí)候我們可能需要根據(jù)情況去改變gdb的工作目錄,查看gdb當(dāng)前工作目錄和改變工作目錄的命令和 shell 下一樣。
① 查看當(dāng)前gdb工作目錄
pwd 命令可以查看當(dāng)前gdb工作目錄
image.png
② 改變gdb工作目錄
使用shell下的 cd 命令,可以改變gdb工作目錄,用法與shell下一樣
image.png
另外提示一下,gdb調(diào)試時(shí),也可以使用 tab 鍵命令補(bǔ)全、上下鍵查看歷史命令等。
(2)程序運(yùn)行參數(shù)
傳遞運(yùn)行參數(shù)的方式有三種:
① 啟動(dòng)gdb時(shí)指定(exe表示可執(zhí)行文件名,paras表示參數(shù))
gdb --args exe paras
我們用前面的gdb_test.c編譯為g3,并傳入?yún)?shù)111111111

② set命令
gdb調(diào)試器啟動(dòng)后,在運(yùn)行過(guò)程中,可以借助 set 命令指定目標(biāo)調(diào)試程序啟動(dòng)所需要的運(yùn)行參數(shù)
set args paras
我們?cè)诤瘮?shù)print_array()處設(shè)置一個(gè)斷點(diǎn),并執(zhí)行到斷點(diǎn)處,然后把函數(shù)參數(shù)len設(shè)置為2,也就是只打印兩個(gè)數(shù)據(jù)(array總共5個(gè)數(shù)據(jù),可以看前面的圖中打印結(jié)果)

可以看到 set 在運(yùn)行的過(guò)程中改變了參數(shù)len的值。
③ 運(yùn)行時(shí)指定
gdb調(diào)試器啟動(dòng)后,在運(yùn)行時(shí)可以通過(guò)run 和 start 來(lái)指定參數(shù)
run paras
start paras

(3)查看及修改運(yùn)行環(huán)境
① 查看程序的運(yùn)行路徑
show paths
② 設(shè)置程序的運(yùn)行路徑
path /xxx/xxx/
③ 查看環(huán)境變量
show environment
④ 設(shè)置環(huán)境變量
set environment PARA=para
(4)輸入輸出重定向
① 輸入輸出重定向
默認(rèn)情況下,程序中的輸出都是打印在終端上的,通過(guò)重定向可以把結(jié)果打印到指定位置。比如,我們可以把程序中的打印結(jié)果都打印到某個(gè)文件中

可以看到,運(yùn)行程序后,屏幕上沒(méi)有任何輸出,我們退出gdb查看1.txt文件

程序運(yùn)行結(jié)果都被打印到了該文件中。
② 選擇終端
使用終端tty1,命令如下
tty /dev/tty1
三、GDB實(shí)戰(zhàn)講解
- GDB命令詳解
在下面所有的命令標(biāo)題中,括號(hào)內(nèi)為命令全寫(xiě),括號(hào)外為命令縮寫(xiě),使用效果一樣,例如運(yùn)行命令 r(run),下面兩種用法效果一致
(gdb)r
(gdb)run
下面的例子都是用前面編譯好的文件 gdb_test.c 及可執(zhí)行文件 g3。
(1)r(run)運(yùn)行與start運(yùn)行程序
run 運(yùn)行程序,如果有斷點(diǎn)則停在斷點(diǎn)處,如果沒(méi)有斷點(diǎn)會(huì)一直執(zhí)行到程序結(jié)束。start 會(huì)執(zhí)行到main函數(shù)的起始位置,相當(dāng)于在main()加一個(gè)斷點(diǎn),然后使用 run 執(zhí)行。如果在程序調(diào)試或者執(zhí)行中使用 run 或 start 都代表從頭開(kāi)始重新執(zhí)行程序。
在 r 或 start 命令后面加參數(shù)可以把參數(shù)傳入并執(zhí)行(前面已經(jīng)介紹過(guò)了)
(gdb)r para
傳入?yún)?shù)para并執(zhí)行。

start 會(huì)執(zhí)行到 mian 處。
(2)q(quit)退出調(diào)試
退出 gdb 調(diào)試,回到 shell。
(3)help
查看幫助手冊(cè),按 q 退出幫助手冊(cè)。

(4)l(lsit)查看代碼
① 一次顯示10行


② 指定一個(gè)行號(hào)n,查看 n-5 到 n+4 行(共10行)

③ 查看第 n1 到 n2 行代碼 list n1,n2

④ 查看其他文件代碼,用于包含多個(gè)源文件的情況,比如可執(zhí)行文件 test 由 test1.c 和 test2.c 編譯而成,可以通過(guò)指定文件名來(lái)查看 test1.c 或 test2.c 的源代碼。
查看 test1.c 的代碼1到10行
(gdb)list test1.c:1,10
(5)set 傳入?yún)?shù)
① set 可以傳入?yún)?shù)或者修改變量的值

② 變量名與gdb命令名沖突
比如你在源代碼中有一個(gè)變量名叫 width ,如果你要用 set 設(shè)置這個(gè)變量的值會(huì)產(chǎn)生沖突,因?yàn)?set width 是gdb的命令,這時(shí)可以通過(guò) set var 告訴gdb該變量是用戶變量。建議自己寫(xiě)代碼時(shí)要避免和系統(tǒng)函數(shù)、編譯調(diào)試等命令重名的函數(shù)或變量,以避免不必要的麻煩。
(gdb)set var width=10
③ 設(shè)置命令
比如說(shuō)我們?cè)诖蛴〗Y(jié)構(gòu)體的時(shí)候,使用 p 命令默認(rèn)就是普通的打印,可能不是很美觀,我們可以通過(guò)命令使打印出來(lái)的結(jié)構(gòu)體更符合我們觀看的習(xí)慣
(gdb)set print pretty

(6)n(next)執(zhí)行下一條語(yǔ)句,不進(jìn)入函數(shù)內(nèi)部
單步執(zhí)行代碼,一條語(yǔ)句一條語(yǔ)句的執(zhí)行,如果遇到函數(shù)不會(huì)進(jìn)入函數(shù)內(nèi)部,可以理解為VS的 F10 調(diào)試鍵。也可以在后面加數(shù)字表示執(zhí)行多少行
(gdb)n num

(7)s(step)執(zhí)行下一條語(yǔ)句,且進(jìn)入函數(shù)內(nèi)部
用法基本與 next 相同,區(qū)別在于 step 在遇到函數(shù)的時(shí)候會(huì)進(jìn)入函數(shù)內(nèi)部(像 printf 等這種庫(kù)函數(shù)不會(huì)進(jìn)入),可以理解為VS的 F11 調(diào)試鍵。

可以看到,當(dāng)執(zhí)行到我們自己的函數(shù) print_array() 的時(shí)候,按 step 會(huì)進(jìn)入這個(gè)函數(shù)的內(nèi)部,停在這個(gè)函數(shù)內(nèi)部語(yǔ)句的第一行。同樣,step 也可以在后面加數(shù)字表示一次執(zhí)行多少行。
(8)u(until)
① 跳出循環(huán)體
在遇到循環(huán)體時(shí),如果在循環(huán)體尾部(最后一行代碼)按 until 調(diào)試鍵,會(huì)直接執(zhí)行完整個(gè)循環(huán)體,并停在循環(huán)體外。

② 跳轉(zhuǎn)至某一行
(gdb)until num
直接跳至第 num 行執(zhí)行并停在這一行。
③ 在其他時(shí)候,功能和 next 一樣,都是單步執(zhí)行
(9)b(break)設(shè)置斷點(diǎn)以及打斷點(diǎn)的六種方式
斷點(diǎn)(BreakPoint),可以讓程序執(zhí)行到斷點(diǎn)處并停在這里,加斷點(diǎn)應(yīng)該是調(diào)試的時(shí)候最常用的一種方法,就像VS中的 F9 鍵。加斷點(diǎn)的方式有很多種,下面將逐一介紹:
① b function (直接加函數(shù)名)在某個(gè)函數(shù) function 處添加斷點(diǎn)
在函數(shù) print_array() 處加斷點(diǎn)并執(zhí)行,會(huì)停在該函數(shù)內(nèi)部的第一行

② b num (直接加行號(hào))在第num行添加斷點(diǎn)

這里有一點(diǎn)要注意,因?yàn)槌绦蛞呀?jīng)啟動(dòng)了,如果我們要想執(zhí)行到斷點(diǎn)處,應(yīng)該使用命令 c ,如果使用 run 或 start 會(huì)重新運(yùn)行程序。
③ b file.c:num 在 file.c 文件的第 num 行加斷點(diǎn),如果不加文件名 file.c 則默認(rèn)是在含main函數(shù)的那個(gè)文件第 num 行加斷點(diǎn)。
④ b file.c:function 在 file.c 文件中名為 function 的函數(shù)處加斷點(diǎn)。
⑤ b ±num 通過(guò)偏移地址設(shè)置斷點(diǎn),+ 表示從當(dāng)前程序運(yùn)行行開(kāi)始,往下數(shù) num 行并設(shè)置斷點(diǎn);- 表示當(dāng)前程序運(yùn)行行開(kāi)始,往上數(shù) num 行并設(shè)置斷點(diǎn)。
舉例,當(dāng)前程序在第34行,通過(guò) b +12可以把程序打在 34+12=16 行處。

⑥ b (上面五種方式指定斷點(diǎn)位置) if expression 當(dāng)滿足表達(dá)式 expression 的時(shí)候打斷點(diǎn),也就是說(shuō)只有當(dāng)這個(gè)表達(dá)式為真的時(shí)候,這個(gè)斷點(diǎn)才會(huì)生效。
使用舉例:
(gdb)b 12 if i==2 當(dāng)i==2的時(shí)候在第12行加斷點(diǎn)
(gdb)b func if i>3 當(dāng)i>3的時(shí)候在函數(shù)func處加斷點(diǎn)
(10)tbreak
命令的格式與用法與 break 相同,但是設(shè)置的斷點(diǎn)只生效一次,該斷點(diǎn)使用一次后自動(dòng)去除。
(11)rbreak
該命令用于給函數(shù)加斷點(diǎn), rbreak regex 給所有滿足表達(dá)式 REGEX 的函數(shù)加斷點(diǎn),設(shè)置的斷點(diǎn)和 break 設(shè)置的斷點(diǎn)一樣。這個(gè)命令在C++調(diào)試的時(shí)候,用于給所有重載函數(shù)加斷點(diǎn)非常方便。也可以加文件名來(lái)限制為哪個(gè)文件中的所有滿足表達(dá)式的函數(shù)加斷點(diǎn) rbreak file.c:regex 。
(12)disable 與 enable
用于禁用和激活斷點(diǎn)(普通斷點(diǎn)、捕捉點(diǎn)、觀察點(diǎn)、display的變量),通過(guò)斷點(diǎn)號(hào)來(lái)指定要禁用或激活的斷點(diǎn)(通過(guò) info 查看斷點(diǎn)號(hào)),可以通過(guò) help 手冊(cè)查看用法,被 disable 禁用的斷點(diǎn)將會(huì)暫時(shí)失效,使用 enable 激活后會(huì)再度恢復(fù)正常使用。

enable 可以激活多個(gè)斷點(diǎn),并且可以指點(diǎn)被激活的斷點(diǎn)起作用的次數(shù)。

舉個(gè)小例子

可以看到,Enb 那一欄從 yes 變成了 no。
(13)watch
設(shè)置觀察點(diǎn),如果在執(zhí)行過(guò)程中變量發(fā)生變化,就把他打印出來(lái),并停止運(yùn)行。

這里要注意,如果你用指針(或地址)來(lái)設(shè)置觀察點(diǎn),一定要解引用,* 指針才是對(duì)指針?biāo)赶虻淖兞窟M(jìn)行觀察如果不解引用,那就是對(duì)指針變量本身(地址)進(jìn)行觀察。另外,如果你觀察一個(gè)臨時(shí)變量或表達(dá)式,當(dāng)它的生命周期結(jié)束的時(shí)候,對(duì)應(yīng)的觀察點(diǎn)也就失效了。
觀察點(diǎn)有軟件觀察點(diǎn)和硬件觀察點(diǎn),這里不再詳細(xì)介紹。
(14)rwatch
只要程序中出現(xiàn)讀取目標(biāo)變量或表達(dá)式的值的操作,程序就會(huì)停止運(yùn)行。(讀)
(15)awatch
只要程序中出現(xiàn)讀取目標(biāo)變量或表達(dá)式的值或者改變值的操作,程序就會(huì)停止運(yùn)行。(讀寫(xiě))
(16)catch
(gdb)catch event 監(jiān)控某一事件 event 的發(fā)生,當(dāng)事件發(fā)生時(shí),程序停止
這個(gè) event 可以是下面的情況:
① C++中 throw 拋出的異?;?catch 捕捉到的異常;
② load 命令或 unload 命令,在動(dòng)態(tài)庫(kù)加載或卸載時(shí)程序停止執(zhí)行;
③ fork、vfork、exec 系統(tǒng)調(diào)用時(shí),程序停止運(yùn)行;
舉個(gè)例子測(cè)試一下,先準(zhǔn)備一個(gè)C++源文件,并編譯生成帶調(diào)試信息的可執(zhí)行文件 test。

進(jìn)入調(diào)試,設(shè)置捕捉點(diǎn),捕捉 string 類型的異常

(17)c(continue)執(zhí)行到下一個(gè)斷點(diǎn)處
繼續(xù)執(zhí)行程序,一直執(zhí)行到下一個(gè)斷點(diǎn)處。
(18)info 查看
① info breakpoints 查看所有斷點(diǎn)的信息

Num:斷點(diǎn)編號(hào)
Type:斷點(diǎn)類型
Enb:激活狀態(tài),y表示已激活,n表示未激活
② info breakpoints num 查詢 num 號(hào)斷點(diǎn)的信息

③ info variables 查詢當(dāng)前全局變量信息

④ info watchpoints 查看觀察點(diǎn)信息
⑤ 查看寄存器

⑥ 查看當(dāng)前函數(shù)內(nèi)部臨時(shí)變量的值

⑦ 查看當(dāng)前函數(shù)參數(shù)的值

⑧ 更多用法,請(qǐng)查看幫助手冊(cè)

(19)del(delete)刪除
如果我們使用 quit 退出調(diào)試,然后再次啟動(dòng) gdb 的話,之前設(shè)置的所有類型的斷點(diǎn)(包括觀察點(diǎn)、捕捉點(diǎn))都會(huì)消失。通過(guò) delete 可以在當(dāng)前調(diào)試中刪除斷點(diǎn)。在使用 delete 刪除斷點(diǎn)的時(shí)候,要先用 info 命令查看斷點(diǎn)信息,在顯示信息的第一列會(huì)有斷點(diǎn)的編號(hào),然后再根據(jù)編號(hào)刪除斷點(diǎn)即可。(刪除觀察點(diǎn)、捕捉點(diǎn)方法與刪除斷點(diǎn)一致)

如果直接使用 delete 命令,不加斷點(diǎn)號(hào)的話,會(huì)刪除當(dāng)前所有斷點(diǎn)。
(20)clear
刪除斷點(diǎn),后面加行號(hào)或函數(shù)名,(delete是按照斷點(diǎn)號(hào)刪除)
(gdb)clear func 刪除函數(shù) func 處的斷點(diǎn)
(gdb)clear num 刪除第 num 行的斷點(diǎn)
(21)ignore
忽視斷點(diǎn)
(gdb)ignore num count 忽視編號(hào)為 num 的斷點(diǎn) count 次
(22)p (print)
① 打印變量的值
(gdb)p val 打印變量 val 的值
(gdb)p &val 打印變量 val 的地址

array 類型為 char ,地址每次+1增長(zhǎng)1個(gè)字節(jié)。
② 指定打印變量值的進(jìn)制,比如 /x 表示按16進(jìn)制打印

進(jìn)制表如下:
命令 進(jìn)制
/t 二進(jìn)制
/d 十進(jìn)制有符號(hào)
/u 十進(jìn)制無(wú)符號(hào)數(shù)
/x 十六進(jìn)制
/o 八進(jìn)制
/f 浮點(diǎn)型
/c 字符型
其實(shí)和我們?cè)贑語(yǔ)言中的語(yǔ)法是一樣的。
③ 打印表達(dá)式結(jié)果

④ 修改變量的值

(23)ptype 查看類型
查看一個(gè)變量的數(shù)據(jù)類型

(24)display 跟蹤變化
查看某個(gè)變量或表達(dá)式的值,和 p 命令類似,但是 display 會(huì)一直跟蹤這個(gè)變量或表達(dá)式值得變化,每執(zhí)行一條語(yǔ)句都會(huì)打印一次變量或表達(dá)式的值。

display 也可以按格式打印,語(yǔ)法和 print 一樣,請(qǐng)參照上表(print)。
display 跟蹤得變量或表達(dá)式也會(huì)放入一張表中,使用 info 命令可以查看信息

同樣,Num表示編號(hào),Enb表示是否激活,Expression表示被跟蹤的表達(dá)式。
(25)undisplay 取消跟蹤
后面加 Num 編號(hào),刪除取消跟蹤。其實(shí)也可以使用 del 刪除。

(26)bt (backtrace)查看棧信息
在一個(gè)程序的執(zhí)行過(guò)程中,如果遇到函數(shù)調(diào)用,會(huì)產(chǎn)生一系列一些與函數(shù)上下文相關(guān)的信息:比如函數(shù)調(diào)用的位置、函數(shù)參數(shù)、函數(shù)內(nèi)部的臨時(shí)變量等。這些信息會(huì)被存放在一塊稱為棧幀的內(nèi)存空間中,并且每一個(gè)函數(shù)調(diào)用都對(duì)應(yīng)一個(gè)棧幀(main 函數(shù)也有自己的棧幀,稱為初始幀)。這些所有的棧幀都存放在內(nèi)存中的棧區(qū)。通過(guò)命令 info frame 可以查看當(dāng)前使用的棧幀所存儲(chǔ)的信息,這里面包含了棧幀編號(hào)、棧幀地址、調(diào)用者、源碼編程語(yǔ)言等信息。通過(guò)命令 frame num 、up 、down 可以選的改變棧幀。

查看當(dāng)前所有棧幀 bt

(27)x 查看內(nèi)存

同樣可以指定按什么格式查看。
(28)disas 反匯編
查看函數(shù) print_array() 的反匯編代碼,使用命令 q 退出。

(29)finish
跳出當(dāng)前所在的函數(shù)。
(30)return
忽略后面的語(yǔ)句,立即返回,可以指定返回值 return -1 。
(31)call
調(diào)用某個(gè)函數(shù),call func() 調(diào)用 func() 函數(shù)。
(32)edit
進(jìn)入編輯模式

(33)search
search 搜索,reverse-search 反向搜索。

-
GDB跟蹤可以正常編譯運(yùn)行的源文件
(1)調(diào)試非運(yùn)行狀態(tài)的可執(zhí)行程序
這個(gè)很簡(jiǎn)單,我們前面介紹命令時(shí),所舉的例子,都是在這種情況下進(jìn)行的。也就是對(duì)編譯好的可執(zhí)行文件進(jìn)行調(diào)試。
image.png
進(jìn)入gdb調(diào)試,然后用上面介紹的命令進(jìn)行調(diào)試即可。
(2)調(diào)試一個(gè)正在運(yùn)行的程序
有時(shí)候我們運(yùn)行一個(gè)一直執(zhí)行的程序時(shí),希望能夠調(diào)試這個(gè)程序。比如某個(gè)帶有無(wú)限循環(huán)打印某些信息的程序。
image.png
我們可以這么做,首先編譯生成可執(zhí)行文件,然后在運(yùn)行時(shí)加 & 讓進(jìn)程轉(zhuǎn)為后臺(tái)執(zhí)行,或者通過(guò) SecureCRT 克隆會(huì)話來(lái)新打開(kāi)一個(gè)會(huì)話進(jìn)行調(diào)試。
① 首先通過(guò) ps 命令查看進(jìn)程號(hào),找到 loop 進(jìn)程的進(jìn)程信息
image.png
② 通過(guò)gdb的 -p 參數(shù),指定進(jìn)程進(jìn)入調(diào)試
image.png
③ 正在運(yùn)行的程序會(huì)暫停,可以正常調(diào)試了
-
GDB跟蹤core(調(diào)試掛掉的程序)
(1)什么是 core dump 核心轉(zhuǎn)儲(chǔ)
core是指core memory,dump即堆放。core dump就是核心轉(zhuǎn)儲(chǔ)的意思。在Unix系統(tǒng)中,經(jīng)常會(huì)將主內(nèi)存 main memory 稱為核心 core,而核心映像 core image 是指進(jìn)程執(zhí)行時(shí)的內(nèi)存狀態(tài)。當(dāng)程序發(fā)生錯(cuò)誤或者異?;蛘呤盏侥承┬盘?hào)而終止執(zhí)行的時(shí)候,操作系統(tǒng)會(huì)把核心映像寫(xiě)入一個(gè)文件(core 文件)來(lái)作為調(diào)試依據(jù),這就是核心轉(zhuǎn)儲(chǔ) core dump。
換句話說(shuō),當(dāng)我們寫(xiě)的程序在運(yùn)行時(shí)發(fā)生異常而退出的時(shí)候,由操作系統(tǒng)把程序當(dāng)前的內(nèi)存狀況存儲(chǔ)在一個(gè)core文件中,這就叫core dump。也就是說(shuō),所謂core dump核心轉(zhuǎn)儲(chǔ),就是當(dāng)我們寫(xiě)的程序當(dāng)?shù)簦ó惓M顺觯r(shí),把程序當(dāng)前的內(nèi)存狀況存儲(chǔ)起來(lái),以作為調(diào)試的參考的這么一種技術(shù)。
(2)產(chǎn)生 core dump 的原因
主要原因可以分為三大類:
① 訪問(wèn)越界
包括數(shù)組下標(biāo)越界,C語(yǔ)言字符串無(wú)結(jié)束符引起的越界,使用非法指針(空指針NULL、野指針、未初始化的指針、越界指針)等。
② 多線程
多線程訪問(wèn)全局變量未加同步機(jī)制(鎖機(jī)制等),或使用了線程不安全的函數(shù)。
③ 堆棧溢出
使用了太大的局部變量或無(wú)限嵌套、遞歸調(diào)用函數(shù),可能會(huì)造成棧溢出。
(3)core 文件的相關(guān)配置與 shell 資源限制
我們先準(zhǔn)備一個(gè)有問(wèn)題的程序
image.png
編譯并運(yùn)行這個(gè)程序,程序發(fā)生 core dump,但是我們并沒(méi)有找到 core 文件
image.png
這是因?yàn)椋J(rèn)情況下 core 文件被 shell 限制大小為0了,所以我們看不到 core 文件,可以通過(guò) ulimit 命令查看限制
image.png
實(shí)際上,ulimit 是 shell 的一個(gè)命令,通過(guò)這個(gè)命令可以查看 shell 對(duì)各種資源的限制,比如 -a 選項(xiàng)可以查看所有限制
image.png
第一條就是 core 文件的限制,大小被限制為0。我們可以去改變它的大小限制,最簡(jiǎn)單的方法就是改為無(wú)限制,無(wú)限制就相當(dāng)于可以是任意大小。
ulimit -c unlimited
再次查看 shell 的限制就能看到,現(xiàn)在 core 的限制變?yōu)?unlimited 了

我們現(xiàn)在再一次運(yùn)行剛才的 err 可執(zhí)行文件,就可以看到生成了一個(gè) core 文件

作為一個(gè)優(yōu)秀的程序員,我們可能決定還不夠好,這名字是啥呀 core.9546,怪怪的,我們希望他有一個(gè)符合我們心意的名字,這也可以實(shí)現(xiàn),我們可以修改 core 的配置文件 /proc/sys/kernel/core_pattern ,那你改吧,你發(fā)現(xiàn)改完保存不了。

因?yàn)檫@個(gè)文件是不能寫(xiě)入的,我們可以借助重定向來(lái)修改這個(gè)文件
echo "core-%e-%t" > /proc/sys/kernel/core_pattern

關(guān)于里面的參數(shù),列表如下
參數(shù) 含義
%p 添加 pid
%u 添加 uid
%g 添加 gid
%s 添加導(dǎo)致 core dump 的信號(hào)
%t 添加 core 生成的時(shí)間
%h 添加主機(jī)名
%e 添加命令名
注意,core 文件是執(zhí)行可執(zhí)行文件時(shí),產(chǎn)生 core dump 后才會(huì)產(chǎn)生的一種文件,所以要先執(zhí)行可執(zhí)行文件,產(chǎn)生 core dump,這樣才能得到 core 文件。
(4)通過(guò)core文件調(diào)試當(dāng)?shù)舻某绦?br> 使用 gdb 可執(zhí)行文件名 core文件名 進(jìn)入gdb調(diào)試

where 命令查看出錯(cuò)的位置

- GDB調(diào)試多線程
(1)創(chuàng)建一個(gè)多線程測(cè)試文件
創(chuàng)建一個(gè)測(cè)試文件,代碼如下,本人 Linux 專題系列有線程專題與進(jìn)程專題,本文只做一個(gè)簡(jiǎn)單的線程創(chuàng)建。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void* thread1()
{
printf("this is thread1...\n");
for(;;)
{
sleep(1);
}
}
void* thread2()
{
printf("this is thread2...\n");
for(;;)
{
sleep(1);
}
}
int main(int argc, char* argv[])
{
pthread_t tid1;
pthread_t tid2;
printf("this is main...");
pthread_create(&tid1, NULL, thread1, NULL); /*創(chuàng)建線程1*/
pthread_create(&tid2, NULL, thread2, NULL); /*創(chuàng)建線程2*/
/*等待線程結(jié)束*/
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
(2)undefined reference to pthread_create’ 錯(cuò)誤 上面的文件創(chuàng)建好之后,如果直接編譯,會(huì)報(bào)錯(cuò)undefined reference topthread_create’

這是因?yàn)椋?lt;pthread.h> 并非 Linux 系統(tǒng)的默認(rèn)庫(kù),而是POSIX線程庫(kù)。在Linux中將 <pthread.h> 作為一個(gè)庫(kù)來(lái)使用的話,要加上 -l pthread 來(lái)顯式鏈接該庫(kù)。

這樣編譯就通過(guò)了。
(3)多線程調(diào)試
① 首先,運(yùn)行 ttt 可執(zhí)行文件,這里也會(huì)顯示主進(jìn)程 ID

② 然后用 SecureCRT 克隆會(huì)話或在 Linux 下直接打開(kāi)一個(gè)新的終端,在另一個(gè)會(huì)話中查看進(jìn)程 ID

查看主線程的線程樹(shù) pstree ,可以看到兩個(gè)子線程的線程 ID

③ 查看線程棧信息,pstack

④ 進(jìn)入 gdb 調(diào)試

查看線程

切換線程,根據(jù) info 查看到的編號(hào)來(lái)切換,我們可以通過(guò)線程 ID 來(lái)判斷是否切換

⑤ 打斷點(diǎn)等等指令與之前講的無(wú)異,這里講一些用于線程的命令
(gdb)thread apply num n 讓線程 num 繼續(xù)執(zhí)行,num 是線程的編號(hào),用info查看
(gdb)set scheduler-locking on 只執(zhí)行當(dāng)前線程,輸入 n 繼續(xù)執(zhí)行
(gdb)set scheduler-locking off 所有線程并發(fā)執(zhí)行
thread apply 線程id gdb_cmd : 指定某線程執(zhí)行某gdb命令
thread apply all gdb_cmd :全部的線程執(zhí)行某gdb命令
break xxx thread ID :為某個(gè)線程設(shè)置斷點(diǎn)
set scheduler-locking step: 當(dāng)單步執(zhí)行某一線程時(shí),其它線程不會(huì)執(zhí)行,同時(shí)保證在調(diào)試過(guò)程中當(dāng)前線程不會(huì)發(fā)生改變。
但如果該模式下執(zhí)行 continue、until、finish 命令,則其它線程也會(huì)執(zhí)行,并且如果某一線程執(zhí)行過(guò)程遇到斷點(diǎn),則 GDB 調(diào)試器會(huì)將該線程作為當(dāng)前線程。
NOTE:只有線程創(chuàng)建之后這個(gè)命令的暫停效果才有效,比如set scheduler-locking on開(kāi)啟之后,但pthread_create語(yǔ)句還沒(méi)執(zhí)行,這時(shí)執(zhí)行continue后邊創(chuàng)造的線程不會(huì)被暫停,必須等線程創(chuàng)造后再執(zhí)行coneinue。
總結(jié)
熟練掌握 gdb 調(diào)試是一個(gè)高水平程序員的基本技能,其實(shí)我們用習(xí)慣了 IDE 中的調(diào)試器之后,反而越來(lái)越忽視 gdb 這種命令行的調(diào)試。但是實(shí)際上,熟練掌握 gdb 會(huì)對(duì)調(diào)試程序本身產(chǎn)生更深刻的理解,可以大大提高程序調(diào)試水平。如果這篇文章大家覺(jué)得有幫助,可以關(guān)注我的 Linux 專欄,里面有更多 Linux 相關(guān)的優(yōu)質(zhì)文章。“紙上得來(lái)終覺(jué)淺,絕知此事要躬行”,學(xué)習(xí) Linux 知識(shí)的同時(shí),一定要?jiǎng)邮志毩?xí),親自去調(diào)試一些程序,只能理解這只指令是怎么執(zhí)行的。









