<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方便n倍的調試工具——CGDB

          共 4375字,需瀏覽 9分鐘

           ·

          2022-11-01 18:25

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

          這篇文章就來分享一下CGDB的最基本使用方法,如果是第一次聽說,強烈建議您體驗一下,一定會愛上它的!

          有 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ù)據(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)建結構體變量
          struct USER_DATA user_data;
          user_data.flag = 0xA5;

          // 往結構體變量中加載數(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嗎?

          當然了,在編譯的時候,編譯器以Warning的方式給出了風險提示。因為示例代碼很簡單,所以很容易發(fā)現(xiàn)。

          但是在一個項目中,如果不喜歡消除編譯Warning警告的話,這個bug還是比較隱蔽的。

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

          因為要使用GDB調試,所以別忘了加上-g選項。

          GDB 調試操作

          $ 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的值不對,決定在調用get_data之前的那行下一個斷點,然后從頭開始執(zhí)行:

          查看代碼行號:

          (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 {

          下斷點在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
          25 user_data.flag = 0xA5;

          在斷點處停了下來,此時該賦值語句還沒有執(zhí)行,所以先單步執(zhí)行一次:

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

          此時,打印一下這個變量user_data.flag的值和地址

          因為待會進入被調用函數(shù),這個變量就不可見了,所以需要通過地址來打印。

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

          此時賦值是正確的,再接著往下執(zhí)行,進入被調用函數(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);

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

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

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

          繼續(xù)單步執(zhí)行(因為不需要跟進memcpy、strlen的內部,所以使用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)問題了:在執(zhí)行*len = strlen(g_data)語句之后,變量user_data.flag地址中的內容就被改變了。

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

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

          問題是解決了,但是回過頭來看一下gdb的調試過程,還是比較繁瑣的:調試指令和代碼顯示夾雜在一起,需要敲很多指令。

          CGDB 調試操作

          啟動CGDB之后,終端窗口被評分為上下兩部分:上面是代碼窗口,下面是調試窗口。

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

          空格鍵:設置或者取消斷點;

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

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

          。。。

          還有很多方便的快捷鍵

          -:縮小代碼窗口;

          +:擴大代碼窗口;

          gg: 光標移動到文件頭部;

          GG:光標移動到文件尾部;

          ctrl + b:代碼向上翻一頁;

          ctrl + u:代碼向上翻半頁;

          ctrl + f:代碼向下翻一頁;

          ctrl + d:代碼向下翻半頁;

          按下i鍵回到調試窗口,進入調試模式,使用的調試指令與GDB幾乎一樣!

          也就是說:可以在實時查看代碼的情況下進行調試操作,大大提高了效率。

          我們按照上面GDB的調試過程走一遍:

          按下ESC鍵進入代碼窗口,此時代碼前面的行號如果是白色的,表示所在的當前行。

          按下j鍵,向下移動高亮的當前行。當移動到25行時,如下:

          按下空格鍵,表示在此行設置一個斷點,此時行號變成紅色的:

          并且在調試窗口打印一行信息:

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

          按下i鍵回到調試操作窗口,然后輸入運行指令r,會在第25行停下來的,如下綠色的箭頭所示:

          當然了,調試窗口也會打印出相關信息:

          (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í)行,打印的值也是符合預期的。

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

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

          此時,上面的代碼窗口自動進入get_data()相關的代碼,如下所示:

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

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

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

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

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

          小結:

          CGDB的操作過程,雖然我寫的比較啰嗦,但是實際使用起來,真的是非常的絲滑,就像巧克力一樣!


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

          既然看到這里了,如果覺得不錯,請您隨手點個【贊】和【在看】吧!

          轉自微信公眾號:IOT物聯(lián)網(wǎng)小鎮(zhèn)





          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美日韩一区视频 | 久久性爱成人 | 放荡少妇三p黄色毛片 | 91精品国产乱码久久久福利 | 东京热成人小说 |