比GDB更方便的代码调试工具:CGDB

开发 前端
这篇文章就来分享一下CGDB的最基本使用方法,如果是第一次听说,强烈建议您体验一下,一定会爱上它的!

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";

/*
功能:加载一段数据
参数1:  data[OUT]: 数据被加载的缓冲区
参数2:  len [OUT]:实际被加载的数据的长度
返回值: 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[])
{
    // 创建结构体变量
    struct USER_DATA user_data;
    user_data.flag = 0xA5;
    
    // 往结构体变量中加载数据
    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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.

在编译之前,先看一下代码,你能发现其中的bug吗?

当然了,在编译的时候,编译器以Warning的方式给出了风险提示。因为示例代码很简单,所以很容易发现。

但是在一个项目中,如果不喜欢消除编译Warning警告的话,这个bug还是比较隐蔽的。

编译测试代码:gcc -g test.c -o test

因为要使用GDB调试,所以别忘了加上-g选项。

GDB 调试操作

$ gdb ./test
(gdb) r   // 直接全速执行一次
(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]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

发现user_data.flag的值不对,决定在调用get_data之前的那行下一个断点,然后从头开始执行:

查看代码行号:

(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    {
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

下断点在25行:

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

开始运行:

(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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

在断点处停了下来,此时该赋值语句还没有执行,所以先单步执行一次:

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

此时,打印一下这个变量user_data.flag的值和地址:

因为待会进入被调用函数,这个变量就不可见了,所以需要通过地址来打印。

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

此时赋值是正确的,再接着往下执行,进入被调用函数get_data()了,

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

这个函数一共就4行代码,我们每单步执行一句,就打印一下user_data.flag变量的内容。

单步执行下一行memcpy处,并且看一下user_data.flag变量地址处的内容是否仍然为:0xa5:

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

继续单步执行(因为不需要跟进memcpy、strlen的内部,所以使用next命令),并打印:

(gdb) next
18    *len = strlen(g_data);     // 这一句即将被执行
(gdb) print/x *0x7fffffffdb62
$4 = 0xa5
(gdb) next
19    return 0;
(gdb) print/x *0x7fffffffdb62
$5 = 0x0
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

发现问题了:在执行*len = strlen(g_data)语句之后,变量user_data.flag地址中的内容就被改变了。

再仔细检查一下代码,就可以诊断出是数据类型使用错了。

解决bug: get_data()函数的最后一个参数,应该是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.
  • 1.
  • 2.

按下i键回到调试操作窗口,然后输入运行指令r,会在第25行停下来的,如下绿色的箭头所示:

图片

当然了,调试窗口也会打印出相关信息:

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

Breakpoint 1, main (argc=1, argv=0x7fffffffdc58) at test.c:25
  • 1.
  • 2.
  • 3.
  • 4.

单步step执行这条赋值语句,然后打印一下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
  • 1.
  • 2.
  • 3.
  • 4.

此时,赋值语句正确执行,打印的值也是符合预期的。

再执行单步指令,进入函数get_data()内部:

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

此时,上面的代码窗口自动进入get_data()相关的代码,如下所示:

图片

继续单步,在执行赋值语句*len = strlen(g_data);之前打印一下变量user_data.flag地址中的内容:

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

正确!然后执行赋值语句之后,再次打印:

(gdb) next
(gdb) print/x *0x7fffffffdb62
$3 = 0x0
  • 1.
  • 2.
  • 3.

发现问题:在执行*len = strlen(g_data)语句之后,变量user_data.flag地址中的内容就被改变了。

小结:

CGDB的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样!

责任编辑:武晓燕 来源: IOT物联网小镇
相关推荐

2015-08-14 09:21:09

gdb工具调试 Go

2016-03-29 10:32:34

2021-03-15 06:23:40

GDB调试代码编程语言

2022-08-28 10:36:53

调试工具通用

2024-01-24 13:22:40

Python调试工具技巧

2024-07-09 08:31:26

2021-07-28 08:53:53

GoGDB调试

2012-02-24 09:25:20

JavaScript

2016-12-02 20:23:51

AndroidADB

2020-02-25 14:38:05

代码开发工具

2010-06-04 17:48:20

Linux编程工具

2011-08-15 17:38:48

iPhone开发调试工具

2023-03-29 08:18:16

Go调试工具

2009-10-22 13:02:47

SQL SERVER

2018-11-27 11:35:32

systemtapMySQL调试工具

2020-05-21 15:53:59

远程调试工具

2022-08-21 14:05:54

调试工具CDP

2024-02-23 10:00:27

Linux工具

2020-02-12 13:00:09

网速度测试工具开源工具

2015-04-22 12:10:28

在线编译调试工具
点赞
收藏

51CTO技术栈公众号