?
CGDB 是GDB的前端,在終端窗口中意圖形化的形式來調(diào)試代碼(基于ncurse),非常方便。相對于GDB來說,可以很大的提高效率。
這篇文章就來分享一下CGDB的最基本使用方法,如果是第一次聽說,強烈建議您體驗一下,一定會愛上它的!
有 bug 的示例代碼
?
#include#include #include #include #include typedef struct USER_DATA{ char data[32]; unsigned short data_len; unsigned int flag; }__attribute((packed))__; const unsigned char *g_data = "hello"; /* 功能:加載一段數(shù)據(jù) 參數(shù)1: data[OUT]: 數(shù)據(jù)被加載的緩沖區(qū) 參數(shù)2: len [OUT]:實際被加載的數(shù)據(jù)的長度 返回值: 0-成功,else-失敗 */ static int get_data(unsigned char *data, unsigned int *len) { assert(data && len); memcpy((void *)data, (void *)g_data, strlen(g_data)); *len = strlen(g_data); return 0; } int main(int argc, char *argv[]) { // 創(chuàng)建結(jié)構(gòu)體變量 struct USER_DATA user_data; user_data.flag = 0xA5; // 往結(jié)構(gòu)體變量中加載數(shù)據(jù) if (0 == get_data(user_data.data, &user_data.data_len)) { printf("get_data ok! "); printf("data_len = %d, data = %s ", user_data.data_len, user_data.data); printf("user_data.flag = 0x%x ", user_data.flag); // 期望值:0xA5 } else { printf("get_data failed! "); } return 0; }
?
在編譯之前,先看一下代碼,你能發(fā)現(xiàn)其中的bug嗎?
當(dāng)然了,在編譯的時候,編譯器以Warning的方式給出了風(fēng)險提示。因為示例代碼很簡單,所以很容易發(fā)現(xiàn)。
但是在一個項目中,如果不喜歡消除編譯Warning警告的話,這個bug還是比較隱蔽的。
編譯測試代碼:gcc -g test.c -o test
因為要使用GDB調(diào)試,所以別忘了加上-g選項。
GDB 調(diào)試操作
?
$ gdb ./test (gdb) r // 直接全速執(zhí)行一次 (gdb) r Starting program: /home/captain/demos_2022/cgdb/test test start... get_data ok! data_len = 5, data = hello user_data.flag = 0x0 [Inferior 1 (process 9933) exited normally]
?
發(fā)現(xiàn)user_data.flag的值不對,決定在調(diào)用get_data之前的那行下一個斷點,然后從頭開始執(zhí)行:
查看代碼行號:
?
(gdb) l main 18*len = strlen(g_data); 19return 0; 20} 21 22int main(int argc, char *argv[]) 23{ 24struct USER_DATA user_data; 25user_data.flag = 0xA5; 26if (0 == get_data(user_data.data, &user_data.data_len)) 27{
?
下斷點在25行:
?
(gdb) b 25 Breakpoint 1 at 0x400771: file test.c, line 25.
?
開始運行:
?
(gdb) r Starting program: /home/captain/demos_2022/cgdb/test Breakpoint 1, main (argc=1, argv=0x7fffffffdc58) at test.c:25 25user_data.flag = 0xA5;
?
在斷點處停了下來,此時該賦值語句還沒有執(zhí)行,所以先單步執(zhí)行一次:
?
(gdb) step 26if (0 == get_data(user_data.data, &user_data.data_len))
?
此時,打印一下這個變量user_data.flag的值和地址:
因為待會進(jìn)入被調(diào)用函數(shù),這個變量就不可見了,所以需要通過地址來打印。
?
(gdb) print &user_data.flag $1 = (unsigned int *) 0x7fffffffdb62 (gdb) print/x user_data.flag $2 = 0xa5
?
此時賦值是正確的,再接著往下執(zhí)行,進(jìn)入被調(diào)用函數(shù)get_data()了,
?
(gdb) step get_data (data=0x7fffffffdb40 "n333377377377177", len=0x7fffffffdb60) at test.c:16 16assert(data && len);
?
這個函數(shù)一共就4行代碼,我們每單步執(zhí)行一句,就打印一下user_data.flag變量的內(nèi)容。
單步執(zhí)行下一行memcpy處,并且看一下user_data.flag變量地址處的內(nèi)容是否仍然為:0xa5:
?
(gdb) step 17memcpy((void *)data, (void *)g_data, strlen(g_data)); (gdb) print/x *0x7fffffffdb62 $3 = 0xa5
?
繼續(xù)單步執(zhí)行(因為不需要跟進(jìn)memcpy、strlen的內(nèi)部,所以使用next命令),并打印:
?
(gdb) next 18*len = strlen(g_data); // 這一句即將被執(zhí)行 (gdb) print/x *0x7fffffffdb62 $4 = 0xa5 (gdb) next 19return 0; (gdb) print/x *0x7fffffffdb62 $5 = 0x0
?
發(fā)現(xiàn)問題了:在執(zhí)行*len = strlen(g_data)語句之后,變量user_data.flag地址中的內(nèi)容就被改變了。
再仔細(xì)檢查一下代碼,就可以診斷出是數(shù)據(jù)類型使用錯了。
解決bug: get_data()函數(shù)的最后一個參數(shù),應(yīng)該是unsigned short型指針才正確。
問題是解決了,但是回過頭來看一下gdb的調(diào)試過程,還是比較繁瑣的:調(diào)試指令和代碼顯示夾雜在一起,需要敲很多指令。
CGDB 調(diào)試操作
啟動CGDB之后,終端窗口被評分為上下兩部分:上面是代碼窗口,下面是調(diào)試窗口。
按下ESC鍵進(jìn)入代碼窗口,此時可以上下瀏覽代碼,并且可以進(jìn)行一系列的操作:
空格鍵:設(shè)置或者取消斷點;
o:查看代碼所在的文件;
/ 或者 ?:在代碼中搜索字符串;
。。。
還有很多方便的快捷鍵:
-:縮小代碼窗口;
+:擴(kuò)大代碼窗口;
gg: 光標(biāo)移動到文件頭部;
GG:光標(biāo)移動到文件尾部;
ctrl + b:代碼向上翻一頁;
ctrl + u:代碼向上翻半頁;
ctrl + f:代碼向下翻一頁;
ctrl + d:代碼向下翻半頁;
按下i鍵回到調(diào)試窗口,進(jìn)入調(diào)試模式,使用的調(diào)試指令與GDB幾乎一樣!
也就是說:可以在實時查看代碼的情況下進(jìn)行調(diào)試操作,大大提高了效率。
我們按照上面GDB的調(diào)試過程走一遍:
按下ESC鍵進(jìn)入代碼窗口,此時代碼前面的行號如果是白色的,表示所在的當(dāng)前行。
按下j鍵,向下移動高亮的當(dāng)前行。當(dāng)移動到25行時,如下:
按下空格鍵,表示在此行設(shè)置一個斷點,此時行號變成紅色的:
并且在調(diào)試窗口打印一行信息:
?
(gdb) Breakpoint 1 at 0x400771: file test.c, line 25.
?
按下i鍵回到調(diào)試操作窗口,然后輸入運行指令r,會在第25行停下來的,如下綠色的箭頭所示:
當(dāng)然了,調(diào)試窗口也會打印出相關(guān)信息:
?
(gdb) r Starting program: /home/captain/demos_2022/cgdb/test Breakpoint 1, main (argc=1, argv=0x7fffffffdc58) at test.c:25
?
單步step執(zhí)行這條賦值語句,然后打印一下user_data.flag的值和地址:
?
(gdb) print/x user_data.flag 1: /x user_data.flag = 0xa5 (gdb) print &user_data.flag 2: &user_data.flag = (unsigned int *) 0x7fffffffdb62
?
此時,賦值語句正確執(zhí)行,打印的值也是符合預(yù)期的。
再執(zhí)行單步指令,進(jìn)入函數(shù)get_data()內(nèi)部:
?
(gdb) step get_data (data=0x7fffffffdb40 "n333377377377177", len=0x7fffffffdb60) at test.c:16
?
此時,上面的代碼窗口自動進(jìn)入get_data()相關(guān)的代碼,如下所示:
繼續(xù)單步,在執(zhí)行賦值語句*len = strlen(g_data);之前打印一下變量user_data.flag地址中的內(nèi)容:
?
(gdb) print/x *0x7fffffffdb62 $2 = 0xa5
?
正確!然后執(zhí)行賦值語句之后,再次打印:
?
(gdb) next (gdb) print/x *0x7fffffffdb62 $3 = 0x0
?
發(fā)現(xiàn)問題:在執(zhí)行*len = strlen(g_data)語句之后,變量user_data.flag地址中的內(nèi)容就被改變了。
小結(jié):
CGDB的操作過程,雖然我寫的比較啰嗦,但是實際使用起來,真的是非常的絲滑,就像巧克力一樣!
評論