比 GDB 更方便的代码调试工具:CGDB
- 有 bug 的示例代码
- GDB 调试操作
- 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;
}
在编译之前,先看一下代码,你能发现其中的
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]
发现
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 {
下断点在
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;
在断点处停了下来,此时该赋值语句还没有执行,所以先单步执行一次:
(gdb) step
26 if (0 == get_data(user_data.data, &user_data.data_len))
此时,打印一下这个变量
user_data.flag
的值和地址:因为待会进入被调用函数,这个变量就不可见了,所以需要通过地址来打印。
(gdb) print &user_data.flag
$1 = (unsigned int *) 0x7fffffffdb62
(gdb) print/x user_data.flag
$2 = 0xa5
此时赋值是正确的,再接着往下执行,进入被调用函数
get_data()
了,(gdb) step
get_data (data=0x7fffffffdb40 "n\333\377\377\377\177", len=0x7fffffffdb60) at test.c:16
16 assert(data && len);
这个函数一共就
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
继续单步执行(因为不需要跟进
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
发现问题了:在执行
*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.
按下
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
执行这条赋值语句,然后打印一下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
此时,赋值语句正确执行,打印的值也是符合预期的。
再执行单步指令,进入函数
get_data()
内部:(gdb) step
get_data (data=0x7fffffffdb40 "n\333\377\377\377\177", len=0x7fffffffdb60) at test.c:16
此时,上面的代码窗口自动进入
get_data()
相关的代码,如下所示:继续单步,在执行赋值语句
*len = strlen(g_data);
之前打印一下变量user_data.flag
地址中的内容:(gdb) print/x *0x7fffffffdb62
$2 = 0xa5
正确!然后执行赋值语句之后,再次打印:
(gdb) next
(gdb) print/x *0x7fffffffdb62
$3 = 0x0
发现问题:在执行
*len = strlen(g_data)
语句之后,变量user_data.flag
地址中的内容就被改变了。小结:
CGDB
的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样!- EOF -
↓推荐关注↓
「CPP开发者」在 Github 维护着 9000+ star 的C/C++开发资源。日常分享 C语言 和 C++ 开发相关技术文章,每篇文章都经过精心筛选,一篇文章讲透一个知识点,让读者读有所获~
点赞和在看就是最大的支持❤️
阅读原文 关键词
函数
指令
文件
函数
变量
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。