比gdb更方便的代码调试工具:cgdb(代码片段)

IOT物联网小镇 IOT物联网小镇     2023-02-03     372

关键词:

文章目录

别人的经验,我们的阶梯!

CGDBGDB前端,在终端窗口中意图形化的形式来调试代码(基于ncurse),非常方便。相对于GDB来说,可以很大的提高效率

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

有 bug 的示例代码

下面的测试代码中,有一个严重的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的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样!


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

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

如果转载本文,文末务必注明:“转自微信公众号:IOT物联网小镇”。



推荐阅读

【1】C语言指针-从底层原理到花式技巧,用图文和代码讲解透彻

【2】GCC 链接过程中的【重定位】过程分析

【3】Linux 动态链接过程中的【重定位】底层原理

【4】原来gdb的底层调试原理这么简单

【5】gcc编译时,链接器安排的【虚拟地址】是如何计算出来的?

【6】Linux中对【库函数】的调用进行跟踪的3种【插桩】技巧

比gdb更方便的代码调试工具:cgdb(代码片段)

...窗口中意图形化的形式来调试代码(基于ncurse),非常方便。相对于GDB来说,可以很大的提高效率。这篇文章就来分享一下CGDB的最基本使用方法,如果是第一次听说,强烈建议您体验一下,一定会爱上它的!... 查看详情

gdb调试汇编分析

GDB调试汇编分析代码本次实践我参照了许多先做了的同学的博客,有卢肖明,高其,张梓靖同学。代码借用的是卢肖明同学的代码进行调试运行。GCC编译使用gcc-ggdbtest.c-ogdbtest-m32命令在64位的机器上产生32位汇编代码在使用gdb进... 查看详情

go调试工具:gdbvsdlv(代码片段)

...也希望能有更直接的程序跟踪及定位工具能够帮助我们更方便快捷的追踪、定位问题,最直观的感觉还是使用调试器。Linux平台下,原生的C/C++程序,我们往往使用gdb进行程序调试,切换到Golang,我们同... 查看详情

代码调试工具gdb(代码片段)

代码调试工具GDB入门什么是调试?关于GDB将代码载入GDB附录:源码编译cpp调试模式的编译golang调试模式的编译什么是调试?让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方ÿ... 查看详情

linuxgdb调试工具应用详解(代码片段)

文章目录gdb调试工具gdb介绍生成调试信息启动gdb显示源代码设置断点简单断点—当前文件多文件设置断点---其他文件查询所有断点条件断点维护断点调试代码查看变量的值查看运行时变量的值自动显示变量的值查看修改变量的值... 查看详情

linuxgdb调试工具应用详解(代码片段)

文章目录gdb调试工具gdb介绍生成调试信息启动gdb显示源代码设置断点简单断点—当前文件多文件设置断点---其他文件查询所有断点条件断点维护断点调试代码查看变量的值查看运行时变量的值自动显示变量的值查看修改变量的值... 查看详情

rustlldb调试入门指北(代码片段)

...令用于调试,其相比原生的 gdb 和 lldb 添加了一些方便调试的脚本下面来初步的了解 rust-lldb 的使用,rustup 会安装 查看详情

用gdb调试程序(代码片段)

说明从CSDN的网站上找到的GDB使用说明。原文标题:用GDB调试程序作者:haoel(QQ:753640,MSN:haoel@hotmail.com)关键字:gdb调试cc++gun这篇文章非常好,所以转载了下来,作为收藏。GDB... 查看详情

使用gdb+gdbserver调试应用程序(代码片段)

目录一、gdb基本使用1.启动gdb2.gdb交互式命令二、gdb+gdbserver实现远程调试一、gdb基本使用?GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。对于一名Linux下工作的c++程序员,gdb是必... 查看详情

使用gnu/gdb调试linuxc/c++可执行程序查看出错源代码、设置断点

...优化,使得程序在代码大小和运行速度上都是最优的,以方便用户使用。用gcc/g++编译时,要加上-g选项生成debug版本的可执行程序,否则就无法使用gdb调试了。r表示开始run,如果在运行的过程中发生了错误,比如segmentationfault,可... 查看详情

gdb工具使用方法和常用指令介绍(代码片段)

1、gdb工具介绍1.1、gdb和gdbserver的区别(1)gdbserver运行在设备上,运行要调试的程序并监听是否有客户端要连接;gdb运行在虚拟机或者编译服务器上,通过向gdbserver发送命令,完成调试;(2)gdb直接运行在调试机上&... 查看详情

gdb调试工具

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓... 查看详情

gdb调试工具(代码片段)

...继续(gdb)continue直接run,可以找出段错误的位置。list1打印代码【或者l1】rrun【参数列表】sstepnnextuntilpvar【查看变量】ptypevar【查看变量类型】con 查看详情

linux环境开发工具gdb调试工具+makefile自动化构建工具(代码片段)

Linux环境开发工具(2)gdb调试工具+Makefile自动化构建工具Linux编译器-gcc/g++使用程序编译过程重要概念:函数库静态库与动态库gcc选项gdb使用具体命令Makefile工具使用过程项目清理关于项目清理Linux小程序---理... 查看详情

gdb调试技巧gdb入门(代码片段)

转自网友,我就不重复造轮子了,高阶部分我来补。一、gdb简介GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。一般来说,GDB主要帮助你完成下面四个方面的功能ÿ... 查看详情

linuxlinux调试器--gdb详解(代码片段)

...理5.特殊符号三.使用git命令行1.安装git2.在gitee创建项目3.代码上传三部曲一.Linux调试器-gdb使用1.背景·程序的发布方式有两种,debug模 查看详情

[linux高并发服务器]gdb调试(代码片段)

...么预先准备基本命令例子进入和退出gdb获取帮助查看文件代码查看和设置显示行数打断点!!!运行GDB程序GDB是什么GDB是由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境GDB可以帮助完成下... 查看详情

gdb程序调试常用命令(代码片段)

调试之前若要在GDB中调试程序在编译时需要加上调试信息在GCC中添加的方法GCC-ga.c-oa.exe或下面提供更符合GDB的调试信息GCC-ggdba.c-oa.exe运行流程命令作用-start开始执行程序,在main函数第一句处停止-continue、-c从当前位置继续执... 查看详情