百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

GDB动态打印:让你随时随地printf,不需修改代码,不需重新编译

bigegpt 2024-11-30 18:50 4 浏览

本系列专题,旨在介绍一些非常实用,却不为大多数人所知的调试技巧。灵活运用这些调试技巧,能够轻松解决一些我们经常遇到并为之困惑的问题,大大提高程序调试的效率。

感兴趣的朋友,欢迎右上角关注!

引言 - 程序调试的痛

关于程序调试,有人喜欢各种调试工具,有人喜欢用简单直接的log打印。两种方法各有各的优势和不足,大多时候是可以互补的。

在Linux环境下,GDB是各种调试工具中的佼佼者,而printf则是各种日志打印方法中的典型代表。

调试问题时候,你遇到过下面的情况吗?

代码添加打印信息进行调试,突然发现添加打印的位置不对,或者别的地方也需要添加打印信息。

于是,重新修改源码,重新添加打印,重新编译,重新部署,重新运行,重新调试,重新分析。

当我们费了九牛二虎之力把这些都弄好之后,很不幸地又发现了新的问题,然后不得不反复进行这些过程。

而且,当问题定位出来之后,我们之前花费很大力气添加的调试信息,还必须从程序中删除掉。

对于简单的程序,这些尚可接受。但是,在大型项目中,单是编译构建过程可能就要几十分钟,甚至数个小时,而部署过程则更为复杂。

你能想象得出,在这样的项目中一直重复这些过程,是一件多么痛苦的事情吗?

那么,有没有一种方法,既不需要修改源码,又能随时在程序中任何地方任意添加打印信息呢?

当然有!GDB的动态打印功能正式为此而生的!

GDB Dynamic Printf

GDB提供了Dynamic Printf功能,下文我们称之为动态打印。利用这个功能,我们可以在不修改程序源码的情况下,随时在程序的任何地方添加格式化打印。

如此一来,当然也就不需要重新编译和部署的过程了。

我们先看一个简单的示例,然后再详细介绍它的实现原理,和相关命令的用法。

示例

一个简单示例,如下图所示:

先编译一下:

gcc -g test.c -o test

然后用GDB进行调试:

和期望的一样,程序没有任何打印输出。

现在,我们在第6行、第11行、第14行分别添加一个动态打印断点,用下面的命令:

dprintf 6,"Hello, World!\n"
dprintf 11,"i = %d, a = %d, b = %d\n",i,a,b
dprintf 14,"Leaving! Bye bye!\n"

如下图:

稍微解释一下:

  • 第6行的语句会打印一句“Hello, World!”
  • 第11行会把i、a、b的值分别打印出来
  • 第14行打印“Leaving! Bye bye!”

设置好之后,查看一下断点的信息:

已经设置成功了。然后,重新运行:

看到了吧,尽管我们并没有对源码做任何修改,且没有重新编译,但程序仍然按照我们的设置,打印出了我们想要的信息!

是不是很神奇呢?GDB的动态打印功能究竟是如何工作的呢?

GDB 动态打印实现原理

在上面的示例中,在设置好动态打印的信息之后,我们可以用info break命令查看所设置的信息。

可见,GDB的动态打印,本质上也是一种特殊的断点。但是,它与一般的断点又有所区别。

一般的断点被触发后,会中断程序执行,然后等待用户操作,并且用户必须输入continue命令让程序恢复执行。

而动态打印断点被触发后,程序也会暂时中断执行,但是不需要等待用户响应,而是直接执行用户预设的格式化打印语句,并自动恢复程序的执行

GDB动态打印的使用方法

设置动态打印的命令是dprintf,格式如下:

dprintf location,format string,arg1,arg2,...

dprintf命令和C语言中的printf的用法很相似,支持格式化打印。

相比printf函数,dprintf命令多了一个location参数,用于指定动态打印被触发的位置。

和break命令设置断点时一样,location可以是文件名:行号、函数名、或者具体的地址等。

除了location外,剩余的几个参数,就和printf()函数一致了。format指定字符串打印的格式,后面几个参数指定打印的数据来源。

以上面示例中的命令为例:

dprintf 6,"Hello, World!\n"
dprintf 11,"i = %d, a = %d, b = %d\n",i,a,b
dprintf 14,"Leaving! Bye bye!\n"

在功能上等价于下图中右侧的代码:

到这里,GDB动态打印的最基本功能就介绍完了。

断点信息丢失怎么办?

在实际项目的调试过程中,难免会由于各种原因而必须要反复的调试才能定位出问题的原因,或者彻底理解程序的代码逻辑。

然而,dprintf本质上也是一种断点,因此,当调试结束后,本次调试时设置的断点信息就全部丢失了。如果要再次调试的话,就不得不重新设置一遍。

如果每次调试过程中,只需要设置一两个动态打印的话,那倒也简单。

可是,如果需要设置十几个甚至几十个动态打印呢?难道每次调试都要全部重新设置一遍吗?想想都是一件比较麻烦的事情,对吧?

其实,GDB也有对应的处理方案,很简单就可以解决!

保存和加载GDB断点信息

为了解决上面提到的问题,GDB很贴心地提供了对断点信息保存和加载的功能。

GDB中,可以把当前所设置的各种类型的断点信息全部保存在一个脚本文件中。这其中当然也包括dprintf设置的动态打印信息。

只需要执行下面的命令即可:

save breakpoints file_name

这条命令会把当前所有的断点信息都保存在file_name指定的文件中。

等下次进行调试时,可以把file_name文件中的断点信息重新加载起来。有两种方法:

  1. 启动GDB时使用“-x file_name”参数。
  2. 在GDB中执行source file_name命令。

下面分别演示一下。

保存断点信息

我们用GDB重新启动上面示例中的test程序:

  1. 用dprintf设置好断点。
  2. 用info break命令查看一下断点信息。
  3. 执行“save breakpoints test.bp”命令,把断点信息保存在test.bp文件中。

我们看下一下test.bp中究竟保存了什么内容:

原来,就是我们之前执行的三条dprintf命令,并且是以文本形式存在test.bp中的。

接下来,我们用两种方法分别加载test.bp中的断点信息。

用-x参数加载断点信息

可见,指定-x参数后,GDB在开始调试程序之前,会从指定的文件中把断点信息加载进来,并重新设置在程序中。因此,执行run命令后,程序能够按照我们的预期正常执行动态打印功能。

source命令加载断点信息

GDB把test加载起来之后,info break并没有显示出任何断点信息。然后,我们执行source test.bp命令,GDB会把断点信息从test.bp加载进来,并重新设置在test程序中。

结语

由于篇幅所限,本文只是介绍了GDB动态打印的基本功能和使用方法。其实,它还有很多高阶的用法和技巧,以后会再更新文章进行讲解。

程序调试是每个程序员必须要熟练掌握的基本技能,在整个计算机知识体系结构中,它占据着非常重要的地位。

应一些朋友的要求,我近期会更新一系列程序调试相关的系列专题文章。会涉及到调试器的实现原理、常用工具的高阶技巧、以及常见问题的定位方法和思路等内容。

本文是程序调试系列专题的第三篇,感兴趣的朋友,欢迎阅读其他已更新的内容:

C语言:GDB调试时遇到宏定义怎么办?一个小技巧帮你一秒钟搞定

C语言:当GDB遇到复杂数据结构,两分钟带你掌握四个高效调试技巧

对编译、链接、内核等技术感兴趣的朋友,欢迎阅读另外一个正在更新中的专题:

你真的理解"Hello world"吗? 从编译链接到OS内核系列专题(已更新三篇)


看完之后,如果觉得有点收获,不是完全浪费时间的话,别忘了点赞!把知识分享给更多志同道合的人!谢谢!

对本系列专题有什么建议,或者对哪些技术感兴趣的话,也欢迎留言讨论!

对编译器、OS内核、虚拟化、性能优化、程序调试等技术感兴趣的童鞋,欢迎右上角关注!

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...