<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          比GDB更方便的代碼調(diào)試工具:CGDB

          共 4740字,需瀏覽 10分鐘

           ·

          2022-07-25 11:32

          作  者:道哥,10+年嵌入式開(kāi)發(fā)老兵,專(zhuān)注于:C/C++、嵌入式、Linux。

          關(guān)注下方公眾號(hào),回復(fù)【書(shū)籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書(shū)籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。

          目錄

          • 有 bug 的示例代碼

          • GDB 調(diào)試操作

          • CGDB 調(diào)試操作

          別人的經(jīng)驗(yàn),我們的階梯!

          CGDB 是GDB前端,在終端窗口中意圖形化的形式來(lái)調(diào)試代碼(基于ncurse),非常方便。相對(duì)于GDB來(lái)說(shuō),可以很大的提高效率。

          這篇文章就來(lái)分享一下CGDB的最基本使用方法,如果是第一次聽(tīng)說(shuō),強(qiáng)烈建議您體驗(yàn)一下,一定會(huì)愛(ài)上它的!

          有 bug 的示例代碼

          #include <unistd.h>
          #include <stdlib.h>
          #include <stdio.h>
          #include <string.h>
          #include <assert.h>

          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í)際被加載的數(shù)據(jù)的長(zhǎng)度
          返回值: 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! \n");
          printf("data_len = %d, data = %s \n", user_data.data_len, user_data.data);
          printf("user_data.flag = 0x%x \n", user_data.flag); // 期望值:0xA5
          }
          else
          {
          printf("get_data failed! \n");
          }
          return 0;
          }

          在編譯之前,先看一下代碼,你能發(fā)現(xiàn)其中的bug嗎?

          當(dāng)然了,在編譯的時(shí)候,編譯器以Warning的方式給出了風(fēng)險(xiǎn)提示。因?yàn)槭纠a很簡(jiǎn)單,所以很容易發(fā)現(xiàn)。

          但是在一個(gè)項(xiàng)目中,如果不喜歡消除編譯Warning警告的話(huà),這個(gè)bug還是比較隱蔽的。

          編譯測(cè)試代碼:gcc -g test.c -o test

          因?yàn)橐褂?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">GDB調(diào)試,所以別忘了加上-g選項(xiàng)。

          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的值不對(duì),決定在調(diào)用get_data之前的那行下一個(gè)斷點(diǎn),然后從頭開(kāi)始執(zhí)行:

          查看代碼行號(hào):

          (gdb) l main
          18 *len = strlen(g_data);
          19 return 0;
          20 }
          21
          22 int main(int argc, char *argv[])
          23 {
          24 struct USER_DATA user_data;
          25 user_data.flag = 0xA5;
          26 if (0 == get_data(user_data.data, &user_data.data_len))
          27 {

          下斷點(diǎn)在25行:

          (gdb) b 25
          Breakpoint 1 at 0x400771: file test.c, line 25.

          開(kāi)始運(yùn)行:

          (gdb) r
          Starting program: /home/captain/demos_2022/cgdb/test

          Breakpoint 1, main (argc=1, argv=0x7fffffffdc58) at test.c:25
          25 user_data.flag = 0xA5;

          在斷點(diǎn)處停了下來(lái),此時(shí)該賦值語(yǔ)句還沒(méi)有執(zhí)行,所以先單步執(zhí)行一次:

          (gdb) step
          26 if (0 == get_data(user_data.data, &user_data.data_len))

          此時(shí),打印一下這個(gè)變量user_data.flag的值和地址

          因?yàn)榇龝?huì)進(jìn)入被調(diào)用函數(shù),這個(gè)變量就不可見(jiàn)了,所以需要通過(guò)地址來(lái)打印。

          (gdb) print &user_data.flag
          $1 = (unsigned int *) 0x7fffffffdb62
          (gdb) print/x user_data.flag
          $2 = 0xa5

          此時(shí)賦值是正確的,再接著往下執(zhí)行,進(jìn)入被調(diào)用函數(shù)get_data()了,

          (gdb) step
          get_data (data=0x7fffffffdb40 "n\333\377\377\377\177", len=0x7fffffffdb60) at test.c:16
          16 assert(data && len);

          這個(gè)函數(shù)一共就4行代碼,我們每單步執(zhí)行一句,就打印一下user_data.flag變量的內(nèi)容。

          單步執(zhí)行下一行memcpy處,并且看一下user_data.flag變量地址處的內(nèi)容是否仍然為:0xa5:

          (gdb) step
          17 memcpy((void *)data, (void *)g_data, strlen(g_data));
          (gdb) print/x *0x7fffffffdb62
          $3 = 0xa5

          繼續(xù)單步執(zhí)行(因?yàn)椴恍枰M(jìn)memcpy、strlen的內(nèi)部,所以使用next命令),并打?。?/p>

          (gdb) next
          18 *len = strlen(g_data); // 這一句即將被執(zhí)行
          (gdb) print/x *0x7fffffffdb62
          $4 = 0xa5
          (gdb) next
          19 return 0;
          (gdb) print/x *0x7fffffffdb62
          $5 = 0x0

          發(fā)現(xiàn)問(wèn)題了:在執(zhí)行*len = strlen(g_data)語(yǔ)句之后,變量user_data.flag地址中的內(nèi)容就被改變了。

          再仔細(xì)檢查一下代碼,就可以診斷出是數(shù)據(jù)類(lèi)型使用錯(cuò)了。

          解決bug: get_data()函數(shù)的最后一個(gè)參數(shù),應(yīng)該是unsigned short型指針才正確。

          問(wèn)題是解決了,但是回過(guò)頭來(lái)看一下gdb的調(diào)試過(guò)程,還是比較繁瑣的:調(diào)試指令和代碼顯示夾雜在一起,需要敲很多指令。

          CGDB 調(diào)試操作

          啟動(dòng)CGDB之后,終端窗口被評(píng)分為上下兩部分:上面是代碼窗口,下面是調(diào)試窗口。

          按下ESC鍵進(jìn)入代碼窗口,此時(shí)可以上下瀏覽代碼,并且可以進(jìn)行一系列的操作:

          空格鍵:設(shè)置或者取消斷點(diǎn);

          o:查看代碼所在的文件;

          / 或者 ?:在代碼中搜索字符串;

          。。。

          還有很多方便的快捷鍵

          -:縮小代碼窗口;

          +:擴(kuò)大代碼窗口;

          gg: 光標(biāo)移動(dòng)到文件頭部;

          GG:光標(biāo)移動(dòng)到文件尾部;

          ctrl + b:代碼向上翻一頁(yè);

          ctrl + u:代碼向上翻半頁(yè);

          ctrl + f:代碼向下翻一頁(yè);

          ctrl + d:代碼向下翻半頁(yè);

          按下i鍵回到調(diào)試窗口,進(jìn)入調(diào)試模式,使用的調(diào)試指令與GDB幾乎一樣!

          也就是說(shuō):可以在實(shí)時(shí)查看代碼的情況下進(jìn)行調(diào)試操作,大大提高了效率。

          我們按照上面GDB的調(diào)試過(guò)程走一遍:

          按下ESC鍵進(jìn)入代碼窗口,此時(shí)代碼前面的行號(hào)如果是白色的,表示所在的當(dāng)前行。

          按下j鍵,向下移動(dòng)高亮的當(dāng)前行。當(dāng)移動(dòng)到25行時(shí),如下:

          按下空格鍵,表示在此行設(shè)置一個(gè)斷點(diǎn),此時(shí)行號(hào)變成紅色的:

          并且在調(diào)試窗口打印一行信息:

          (gdb) 
          Breakpoint 1 at 0x400771: file test.c, line 25.

          按下i鍵回到調(diào)試操作窗口,然后輸入運(yùn)行指令r,會(huì)在第25行停下來(lái)的,如下綠色的箭頭所示:

          當(dāng)然了,調(diào)試窗口也會(huì)打印出相關(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í)行這條賦值語(yǔ)句,然后打印一下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

          此時(shí),賦值語(yǔ)句正確執(zhí)行,打印的值也是符合預(yù)期的。

          再執(zhí)行單步指令,進(jìn)入函數(shù)get_data()內(nèi)部:

          (gdb) step
          get_data (data=0x7fffffffdb40 "n\333\377\377\377\177", len=0x7fffffffdb60) at test.c:16

          此時(shí),上面的代碼窗口自動(dòng)進(jìn)入get_data()相關(guān)的代碼,如下所示:

          繼續(xù)單步,在執(zhí)行賦值語(yǔ)句*len = strlen(g_data);之前打印一下變量user_data.flag地址中的內(nèi)容:

          (gdb) print/x *0x7fffffffdb62
          $2 = 0xa5

          正確!然后執(zhí)行賦值語(yǔ)句之后,再次打?。?/p>

          (gdb) next
          (gdb) print/x *0x7fffffffdb62
          $3 = 0x0

          發(fā)現(xiàn)問(wèn)題:在執(zhí)行*len = strlen(g_data)語(yǔ)句之后,變量user_data.flag地址中的內(nèi)容就被改變了。

          小結(jié):

          CGDB的操作過(guò)程,雖然我寫(xiě)的比較啰嗦,但是實(shí)際使用起來(lái),真的是非常的絲滑,就像巧克力一樣!


          ------ End ------

          既然看到這里了,如果覺(jué)得不錯(cuò),請(qǐng)您隨手點(diǎn)個(gè)【贊】和【在看】吧!

          如果轉(zhuǎn)載本文,文末務(wù)必注明:“轉(zhuǎn)自微信公眾號(hào):IOT物聯(lián)網(wǎng)小鎮(zhèn)”。



          推薦閱讀

          【1】C語(yǔ)言指針-從底層原理到花式技巧,用圖文和代碼講解透徹

          【2】GCC 鏈接過(guò)程中的【重定位】過(guò)程分析

          【3】Linux 動(dòng)態(tài)鏈接過(guò)程中的【重定位】底層原理

          【4】原來(lái)gdb的底層調(diào)試原理這么簡(jiǎn)單

          【5】gcc編譯時(shí),鏈接器安排的【虛擬地址】是如何計(jì)算出來(lái)的?

          【6】Linux中對(duì)【庫(kù)函數(shù)】的調(diào)用進(jìn)行跟蹤的3種【插樁】技巧


          星標(biāo)公眾號(hào),第一時(shí)間看文章!


          瀏覽 73
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草超碰在线 | 青青草天天免费在线 | 日韩AⅤ无码一区二区三区 | 中国黄色一级毛骗 | 三级视频网 |