CGDB的最基本使用方法

cgdb 是gdb的前端,在终端窗口中意图形化的形式来调试代码(基于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;/*功能:加载一段数据参数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! );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;}  
在编译之前,先看一下代码,你能发现其中的bug吗?
当然了,在编译的时候,编译器以warning的方式给出了风险提示。因为示例代码很简单,所以很容易发现。
但是在一个项目中,如果不喜欢消除编译warning警告的话,这个bug还是比较隐蔽的。
编译测试代码:gcc -g test.c -o test
因为要使用gdb调试,所以别忘了加上-g选项。
gdb 调试操作
$ gdb ./test(gdb) r // 直接全速执行一次(gdb) rstarting 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 main18*len = strlen(g_data);19return 0;20}2122int 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 25breakpoint 1 at 0x400771: file test.c, line 25.  
开始运行:
(gdb) rstarting program: /home/captain/demos_2022/cgdb/test breakpoint 1, main (argc=1, argv=0x7fffffffdc58) at test.c:2525user_data.flag = 0xa5;  
在断点处停了下来,此时该赋值语句还没有执行,所以先单步执行一次:
(gdb) step26if (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) stepget_data (data=0x7fffffffdb40 n333377377377177, len=0x7fffffffdb60) at test.c:1616assert(data && len);  
这个函数一共就4行代码,我们每单步执行一句,就打印一下user_data.flag变量的内容。
单步执行下一行memcpy处,并且看一下user_data.flag变量地址处的内容是否仍然为:0xa5:
(gdb) step17memcpy((void *)data, (void *)g_data, strlen(g_data));(gdb) print/x *0x7fffffffdb62$3 = 0xa5  
继续单步执行(因为不需要跟进memcpy、strlen的内部,所以使用next命令),并打印:
(gdb) next18*len = strlen(g_data); // 这一句即将被执行(gdb) print/x *0x7fffffffdb62$4 = 0xa5(gdb) next19return 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) rstarting 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.flag1: /x user_data.flag = 0xa5(gdb) print &user_data.flag2: &user_data.flag = (unsigned int *) 0x7fffffffdb62  
此时,赋值语句正确执行,打印的值也是符合预期的。
再执行单步指令,进入函数get_data()内部:
(gdb) stepget_data (data=0x7fffffffdb40 n333377377377177, 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的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样!


基于NXP i.MX RT1050主控板的恒温恒湿控制系统
管理下一代开放标准车辆电子架构
超低温冰箱BDW-86L770-Y产品特点的介绍
云母电容有什么特点_云母电容的应用领域
扼流圈是什么东西呀?作用呢?
CGDB的最基本使用方法
日本发布地铁专用机器人 精通中日韩英四种语言
新手如何看懂电路图?有哪些必要的知识点?
植入式医疗“起搏器”,或缓解老年痴呆病情
继手机应用之后,显通科技推出面向可穿戴设备和大型显示屏的超声波触控解决方案
区块链私有链的优点介绍
小LCD屏幕应用日渐普及,安森美半导体推出两款新的图像信号处理器
【XR806开发板试用】基于FreeRTOS的SoftAp配网实现
360F5手机上架官网 配置曝光 开售时间计日可期
用555制作的声控延时电路
一种用于光盘伺服控制系统的通用滤波器的设计
安捷伦将高性能示波器带入低价位
亚马逊Cable类产品USB Type c单品店铺数据分析
MATLAB机械臂的两种路径规划
一文看懂电动机控制与变频调速原理