1. 什麼是GDB?
GDB主要用來調試C/C++程序,它允許我們在執行程序時查看程序的行爲和內存信息,以及幫助我們在程序崩潰時查看它之前進行了什麼操作。
GDB的命令行調試要遠比IDE的功能高級的多,如果只是通常的設置斷點,監測變量什麼的可能IDE比較方便,但是一旦上升到使用那些高級點的調試技巧,反而GDB在命令行的模式下更爲的方便和高效,起碼啓動速度和響應速度會高很多。
2. 調試必備知識
2.1 什麼是core文件?
core 文件是大多數UNIX 系統實現的一種特性,當進程崩潰時,操作系統會將進程當前的內存映像和一部分相關的調試信息寫入core 文件,方便開發者後面對問題進行定位。
2.2 開啓或關閉core文件的生成
使用ulimit -c
命令可以查看當前的內核轉儲功能是否有效。
-c
選項表示內核轉儲文件的大小限制,如果執行完後它的大小爲0,表示內核轉儲不會生效,即使程序出現段錯誤,也不會有core文件生成。
開啓內核轉儲:
ulimit -c unlimited
這個命令的意思是不限制內核轉儲文件的大小。設爲無限制之後,發生問題時進程的內存就可以全部轉儲到內核轉儲文件中。
2.3 設置/查看Core文件的存儲路徑和命名規則
cat /proc/sys/kernel/core_pattern
執行該命令便可知道core文件存儲路徑和它的命名規則。假設執行完該命令,輸出內容如下所示:
/mnt/sdcard/core-%e-%p-%t
該內容說明core文件生成後將被存放到SD卡目錄下,並且還說明了命名格式,其中格式的具體含義如下:
%e:可執行文件名
%p:被轉儲的進程/線程ID
%t:轉儲時刻
2.4 調試信息與調試原理
一般要調試某個程序,爲了能清晰地看到調試的每一行代碼、調用的堆棧信息、變量名和函數名等信息,需要調試程序含有調試符號信息。
使用gcc編譯程序時,如果加上-g選項即可在編譯後的程序中保留調試符號信息。舉個例子,以下命令將生成一個帶調試信息的程序 hello_server。
gcc -g -o hello_server hello_server.c
2.5 編譯器優化對調試的影響
編譯器的程序優化選項一般有五個級別,從 O0~O4 ( 注意第一個O0 ,是字母O加上數字0), O0表示不優化,從 O1~O4 優化級別越來越高,O4最高。
如果在編譯代碼時開啓編譯器優化選項,那麼在實際調試過程中可能和實際代碼存在差異,例如:在代碼中定義了某個全局變量,但是它在任何地方都沒有被使用,編譯器就會將它從代碼中優化掉,那麼在使用GDB調試時,就無法查看該變量的任何信息。
相機中的代碼被編譯時開啓了編譯器優化選項-O2
,因此編譯器在編譯時會對代碼進行優化,在使用GDB調試時,可能存在有些變量的值等於 <optimized out>
,說明它已經被優化掉了。
3. 啓動GDB調試
3.1 使用GDB調試程序的3種方式
按常用方式排列如下:
-
gdb filename corefile
調試core文件。使用場景:程序發生段錯誤,通過core文件定位異常情況所在位置。
-
gdb -p pid|tid
調試正在運行中的進程或者線程。使用場景:1. mwareserver進程正在運行,用GDB掛載到進程中查看某些全局變量的值;2. mwareserver中某個線程阻塞了,用gdb可以查看它阻塞的位置來分析原因。
-
gdb filename
直接調試目標程序。使用場景:1. 學習開源代碼時通過GDB調試更快地理解程序的架構和執行邏輯;2. 調試自己代碼Demo中的bug。
3.2 GDB實用功能設置
- 日誌功能
有時候輸出信息太多,直接顯示不方便看,則可以啓用GDB的日誌功能。
(gdb) set logging on
,默認會將日誌輸出到gdb.txt文件中。
輸出文件也可以進行指定,(gdb) set logging file <file name>
,然後再執行set logging on
開啓日誌功能。
- 結構體格式化輸出
默認情況下,gdb以一種“緊湊的方式打印結構體。set print pretty on
,打開這個選項,GDB顯示結構體時會比較漂亮,當結構體較大時這個功能非常有幫助。
3.3 GDB常用命令
命令名稱 | 命令縮寫 | 命令說明 |
---|---|---|
backtrace | bt | 查看當前線程的調用堆棧 |
p | 打印變量或寄存器值 | |
examine | x | 查看內存地址中的值 |
frame | f | 切換到當前調用線程的指定堆棧,具體堆棧通過堆棧序號指定 |
up/down | / | 向上/向下切換當前調用線程的堆棧 |
info | i | 查看斷點 / 線程等信息 |
dump | / | 將指定的一段地址中的內存數據寫入文件中 |
1. backtrace
bt,查看當前線程的調用堆棧,即函數間的調用關係。
2. print
p,查看變量的值。
例1:
typedef struct rect
{
ULONG ulWidth;
ULONG ulHeight;
}Rect;
Rect stRect = {1920, 1080};
Rect *pstRect = &stRect;
- 查看結構體stRect的內容
(gdb) p stRect
- 查看結構體stRect中ulWidth的值
(gdb) p stRect.ulWidth
或者 p pstRect->ulWidth
- 查看結構體stRect的地址
(gdb) p &stRect
例2:
#define MAX_SIZE 10
ULONG i = 0;
ULONG aulArray[MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
ULONG *pulArrayCopy = malloc(MAX_SIZE);
for (i = 0; i < MAX_SIZE; i++)
{
pulArrayCopy[i] = aulArray[i];
}
free(pulArrayCopy);
-
查看數組aulArray中第5個元素的值
(gdb) p aulArray[4]
-
查看數組aulArray全部的元素
(gdb) p aulArray
-
查看動態數組pulArrayCopy全部的元素
(gdb) p *pulArrayCopy@MAX_SIZE
print輸出格式:
print默認是根據表達式的類型自動顯示的,在大多數情況下都可以工作的很好,但是在print中可以指定輸出格式得到個性化得顯示結果。
-
p/x i
,以十六進制打印變量i的值。 p/d|u i
,以有符號|無符號十進制整數打印。p/c cEnd
,以十進制和字符的方式顯示。p/f fPos
,以浮點數的方式顯示。p/s pHelloStr
,以字符串的方式顯示pHelloStr。
3. examine
x,查看內存地址中的值。它與print作用類似,但適用性更廣。
x/nfu addr
,x命令有三個可選參數。
n:顯示數目。
f:顯示格式,與print格式類似,默認以十六進制顯示。
u:顯示單位字節大小,b(bytes,1字節)、h(半字,2字節)、w(字,4字節)、g(gaint字,8字節)。
-
查看數組aulArray中第5個元素的值
(gdb) x/u aulArray[4]
等價於(gdb) x/1uw aulArray[4]
-
查看數組aulArray全部的元素
(gdb) x/10u aulArray
或者(gdb) x/10uw aulArray
-
查看字符串pHelloStr內容
char acHelloStr[10] = "Hello gdb!";
(gdb) x/10c acHelloStr
或者(gdb) x/s acHelloStr
。
examine命令相較於print命令適用性更廣的地方在於它能夠直接查看內存地址中的內容。
假設已知數組aulArray的地址爲0x7fffffffde30,數組大小爲10,若想查看數組元素的值,那麼就可以這樣查看:
x/10uw 0x7fffffffde30
或者p *0x7fffffffde30@10
。
4. frame
查看指定的棧幀,用法如:f n
,查看編號爲n的棧幀。
5. up/down
基於當前的棧幀向上或者向下移動棧幀。
6. info
info locals
,查看當前棧幀當中全部的局部變量的數值。info threads
,查看當前進程中運行的所有線程的信息,若需要查看某個線程的棧幀,則執行thread n
切換到編號爲n的線程,然後執行bt命令即可查看棧幀。info args
,查看當前函數的參數值。
7. dump
將指定的一段地址(從start_addr 到end_addr)中的內存數據寫入名爲filename 的文件中。
dump memory filename start_addr end_addr
4. 參考資料
- GNU GDB調試手冊
- Linux GDB調試指南
- 100個gdb小技巧
- 《Debug.Hacks中文版_深入調試的技術和工具》