比GDB方便n倍的调试工具——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之后,终端窗口被评分为上下两部分:上面是代码窗口,下面是调试窗口。

77d8a5141a03ca54a28df47cc01d2f06.png

按下ESC键进入代码窗口,此时可以上下浏览代码,并且可以进行一系列的操作:

空格键:设置或者取消断点;

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

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

。。。

还有很多方便的快捷键:

-:缩小代码窗口;

+:扩大代码窗口;

gg: 光标移动到文件头部;

GG:光标移动到文件尾部;

ctrl + b:代码向上翻一页;

ctrl + u:代码向上翻半页;

ctrl + f:代码向下翻一页;

ctrl + d:代码向下翻半页;

按下i键回到调试窗口,进入调试模式,使用的调试指令与GDB几乎一样!

也就是说:可以在实时查看代码的情况下进行调试操作,大大提高了效率。

我们按照上面GDB的调试过程走一遍:

按下ESC键进入代码窗口,此时代码前面的行号如果是白色的,表示所在的当前行。

按下j键,向下移动高亮的当前行。当移动到25行时,如下:

e0be1341d1604aa6e262e9042f696333.png

按下空格键,表示在此行设置一个断点,此时行号变成红色的:

b512700a63f5a73a8ae2d28cca931c59.png

并且在调试窗口打印一行信息:

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

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

e12fd37db48f8532f59ea94fbf2652a9.png

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

(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()相关的代码,如下所示:

03190ff7db2868661a3ab9a0fd5f2704.png

继续单步,在执行赋值语句*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的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样!


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

既然看到这里了,如果觉得不错,请您随手点个【赞】和【在看】吧!

转自微信公众号:IOT物联网小镇

757bb83c181684ba8a0ebe7a606a9650.jpeg

4a4ff016eeb94f8eff6679588de64584.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux--切换ipython解释器到python3

Ipython修改为python3解释器&#xff1a; which ipython --得到路径 cat 路径--查看执行的解释器版本 sudo gedit 路径--修改解释器版本为python3保存即可&#xff0c;保存的时候提示异常&#xff0c;这个是正常现象&#xff0c;实际上已经保存成功。测试 which ipython 路径 c…

数据库中字段类型Number(n,m)大概说明

number可以存储浮点数&#xff0c;也可以存储整数。Number(n,m) int类型只能存放整数。 a、number(4,3)是表示这个数一共有4位是有效位,后面的3表示有3个是小数也就是这个数&#xff0c;只能是1.234,这样格式的最大只能是9.999,b、number&#xff08;3,4&#xff09; 表示这个…

WCF Security userName/Password

1. Transfer SecurityTransfer Security 主要包括三个方面&#xff1a; "消息完整性(Message Integrity)"、"消息机密性 (Message Confidentiality)" 和 "交互验证(Mutual Authentication)"。消息完整性必须确保消息在传输过程中没有被篡改&…

Sencha Touch2中数据的获取

根据Sencha Touch技术框架的特点&#xff0c;前台展示的绝大部分数据都是通过ajax方式获取&#xff0c;譬如列表数据的获取、表单数据的获取等等。列表数据的获取&#xff0c;一般是通过store组件和list组件进行结合&#xff1b;表单数据的获取通常使用Ext.request方式获取。列…

boost库中mutex、condition_variable与mutex::scoped_lock联合使用实现线程之间的通信

最近在公司负责一个线程池的模块&#xff0c;里面用到了boost库中的mutex、condition_variable与mutex::scoped_lock&#xff0c;在此总结下线程池在使用时的方式和要点&#xff0c;这里记录了线程之间的通信方式&#xff0c;希望对学习线程之间(同一个进程)通信的同志们有所帮…

caffe运行训练脚本时报错:Unknown bottom blob 'data' (layer 'conv1',bottom index 0)

报错的两种报错原因&#xff1a; 1.输入数的路径错误&#xff0c;需要将路径进行修改排查目录是否出错 2.训练原数据格式不对 3.train.prototxt文件中并未设置test层&#xff0c;而在solver层则设置了test的迭代等参数 两种解决方法 1.对错误原因1&#xff0c;则改为正确路径 2…

保护视力,我写的一个定时提醒的小玩意。

做程序员2年了&#xff0c;感觉视力越来越差。有时候常常工作到忘记休息。于是就想写一个能够定时提醒的小东西(公司不让从网络下载别的程序)。 功能: 1.能够每隔一段时间提醒我休息&#xff0c;做做眼保健操。 2.能够自己设定时间间隔. 运行环境&#xff1a;.ne…

Matrix Computations 1

matrix computation转载于:https://www.cnblogs.com/stoneresearch/archive/2012/06/05/4336290.html

Linux下修改只读文件

最近在linux Ubuntu下配置hadoop&#xff0c;遇到了一个只读文件core-site.xml&#xff0c;需要修改其中的内容&#xff0c;但是该文件是只读的。查了资料比较简单&#xff1a; chattr -i 文件 让只读文件可编辑 chattr i 文件 让文件只读 ubuntu下使用&#xff1a; sud…

动态dp模板题(树剖+dp+线段树)

动态最大带权独立集 &#xff08;还有一个是全局平衡二叉树的解法&#xff0c;还没学&#xff09; 1 #include"bits/stdc.h"2 3 using namespace std;4 const int inf 1e8;5 int n,m;6 int v[100005];7 const int nn 1e510;8 9 int link[nn<<1],son[nn<&l…

×××linux下vsftp服务器

一、编译安装vsftp [rootYYzs tmp]# tar -xvf vsftpd-2.2.0.tar.gz[rootYYzs tmp]# cd vsftpd-2.2.0[rootYYzs vsftpd-2.2.0]# make//vsftp默认配置中需要“nobody”用户&#xff0c;在系统中添加此用户[rootYYzs vsftpd-2.2.0]# useradd nobody//VSFTPD默认配置中需要“/usr/s…

WinCE程序的几种开发方法

文章允许转载,请注明出处和作者:luocq(akay_21cn_com)下面介绍的几种开发方法,还是倾向于Delphi的程序员,如果是熟练的VC程序员,当然VC是不二的选择.1、采用Delphi2007来进行WinCE .net程序开发http://spaces.msn.com/members/GordonLiWei/?partqsayear%3D2005%26amonth%3D12&…

几道Linux驱动相关面试题,你会几题?

1一、Linux基础1、任意3种网络操作的Linux命令,并说明他们的含义 1. ifconfig 命令ifconfig 用于查看和配置 Linux 系统的网络接口。 查看所有网络接口及其状态&#xff1a;ifconfig -a 。 使用 up 和 down 命令启动或停止某个接口&#xff1a;ifconfig eth0 up 和 ifconfig et…

解决Too many open files问题

转载&#xff1a;https://blog.csdn.net/zhuwinmin/article/details/72730288 当用linux做高并发服务器时&#xff0c;会遇到"Too many open files"的错误。 Linux是有文件句柄限制的&#xff08;open files&#xff09;&#xff0c;而且Linux默认不是很高&#xf…

RedHat Linux 5.5系统下配置yum包详细过程

1、挂载光盘 mount -t iso9660 /dev/dvd /mnt/cdrom2、建立文件夹 安装如下路径&#xff0c;建立对应的文件夹&#xff0c;其中pub文件夹需要创建4个。3、复制以下内容到指定文件夹 需要注意的是&#xff0c;如果按照第1步将光盘挂在到/mnt/cdrom 下面…

hadoop fs 基本命令

今天由于工作需要&#xff0c;需要使用到hadoop fs的一些命令&#xff0c;就简单的总结了下&#xff1a; 1&#xff0c;hadoop fs –fs [local | <file system URI>]&#xff1a;声明hadoop使用的文件系统&#xff0c;如果不声明的话&#xff0c;使用当前配置文件配置的…

树莓派的这十年

来源 | 新智元编辑 | 袁榭 好困刚刚过完10岁生日的树莓派&#xff0c;早已褪去了当年廉价电脑教具的外衣&#xff0c;一跃成为全球业界首屈一指的微型电脑品牌。为啥叫「Raspberry Pi」&#xff1f;从某种意义上讲&#xff0c;「树莓」这个命名方式其实很单纯&#xff0c;因为大…

存储过程入门与提高

什么是存储过程呢&#xff1f; 定义&#xff1a; 将常用的或很复杂的工作&#xff0c;预先用SQL语句写好并用一个指定的名称存储起来, 那么以后要叫数据库提供与已定义好的存储过程的功能相同的服务时,只需调用execute,即可自动完成命令。 讲到这里,可能有人要问&#xff1a;这…

C++ 11 nullptr关键字

C 11 nullptr关键字 转载&#xff1a;https://www.cnblogs.com/DswCnblog/p/5629073.html 熟悉C的童鞋都知道&#xff0c;为了避免“野指针”&#xff08;即指针在首次使用之前没有进行初始化&#xff09;的出现&#xff0c;我们声明一个指针后最好马上对其进行初始化操作。如…

Vue-watch选项

Vue ----watch 选项 用于 监听数据变化&#xff1a; 1 <!DOCTYPE html>2 <html lang"en">3 <head>4 <meta charset"UTF-8">5 <meta name"viewport" content"widthdevice-width, initial-scale1.0"…