友情提醒:本文转自公众号“一口LInux”,可关注该公众号获取更多知识。本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!
引言程序调试时,你是否遇到过下面几种情况:
1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。
2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真的就是个BUG。要想确定,只能修改源码、重新编译、重新部署,然后重新运行验证。
3、已经找到了root cause,但不确定解决方案是否能正常工作,为了验证,不得不反复地修改代码、编译、部署。
对于大型项目,编译过程可能需要几十分钟,甚至几个小时,部署过程则更为复杂漫长!可想而知,如果调试过程中,不得不反复的修改源码,然后重新编译和部署,会是一项多么繁琐和浪费时间的事情!
那么,有没有一种更高效的调试手段,可以避免反复修改代码和编译呢?
当然有!本文将介绍一种GDB调试技巧,可以一边调试,一边修复Bug,可以在不修改代码、不重新编译的前提下即可修复BUG,验证我们的解决方案,大幅提高调试效率!
本文预期效果如下图,冒泡排序程序中,有三个BUG:
冒泡排序示例
图中已经把三个BUG都标注了出来。正常编译运行时,程序执行结果如下:
程序执行异常
不过是普通方式执行,还是在GDB中执行,程序都异常终止,无法得到正常结果。
但是,利用本文介绍的调试技巧,可以利用GDB给这个程序制作一个“热补丁”,在不修改代码、不重新编译的前提下,解决掉程序中的三个BUG,让程序正常执行,并得到预期结果!
最终效果,如下图所示:
打上“热补丁”后,程序正常执行
是不是很有趣呢?下面开始介绍!
GDB Breakpoint Command ListsGDB支持断点触发后,自动执行用户预设的一组调试命令。使用方法:
commands [bp_id...]
command-list
end
其中:
-
commands是GDB内置关键字
-
bp_id是断点的ID,也就是info命令显示出来的断点Num,可以指定多个,也可以不指定。当不指定时,默认只对最近一次设置的那个断点有效。
-
command-list是用户预设的一组命令,当bp_id指定的断点被触发时,GDB会自动执行这些命令。
-
end表示结束。
这个功能适用于各种类型的断点,如breakpoint、watchpoint、catchpoint等。
适用场景举例利用GDB breakpoint commands lists这个特性可以做很多有趣的事情,本文仅列举其中的几个。
1、随时随地printf,不需修改代码和重新编译看过我之前文章的朋友,应该还记得,我介绍过GDB的动态打印(Dynamic Printf)功能,可以用dprintf命令在代码的任意地方添加动态打印断点,并自动执行格式化打印操作,从而无需修改代码和重新编译就可以在代码中任意增加日志打印信息。
利用GDB breakpoint commands lists功能,可以实现一样的功能,而且除了打印之外,还可以做其它更多的操作,比如dump内存,dump寄存器等。
在GDB中可以做很多有趣的事情,比如修改变量、修改寄存器、调用函数等,结合breakpoint command list功能,可以在调试的同时,修改程序执行逻辑,给程序打上“热补丁”。从而可以在调试过程中,快速修复Bug,避免重新修改代码和重新编译,大大提高程序调试的效率!
这是本文重点讲解的场景,稍后会演示如何利用这个功能,在GDB调试的过程中修复掉上文冒泡排序程序中的三个Bug。
3、实现自动化调试,提高调试效率这个功能,结合GDB支持的脚本功能,以及自定义命令功能,可以实现调试自动化。
这涉及到GDB的很多其它知识,篇幅有限,不再展开讨论,以后更新专门文章讲解!感兴趣的童鞋,不妨关注一下!
给冒泡排序打上“热补丁”现在,我们利用GDB breakpoint command lists功能,给文中的冒泡排序程序打上“热补丁”,演示如何在不修改源码、不重新编译的前提下,解决掉程序中的3个BUG。
再看一下示例程序:
编译一下:
gcc -g bubble.c -o bubble
先用GDB加载运行一下:
程序运行异常,符合我们的预期。
下面我们依次解决冒泡排序程序中的3个BUG。
1、解决第一个BUG先解决第22行的BUG,也就是传递给了bubble_sort()错误的数组长度。
我们知道,在x64上,函数参数优先采用寄存器传递。那么,我们有这么几种方式可以选择:
-
把断点设置在bubble_sort()入口第一条指令,然后直接修改存放数组长度n的那个寄存器中的值。
-
把断点设置在bubble_sort()入口处(不必是第一条指令),在第7行for循环之前,把存放数组长度的变量n的值改掉。
-
把断点设置在main()函数第22行,也就是调用bubble_sort()的地方,然后以正确的参数手动调用bubble_sort()函数,并利用GDB的jump命令,跳过第22行代码的执行。
考虑到有些童鞋对x64 CPU不是非常了解,或者对GDB的jump命令不熟悉,我们采用第2种方式。而且,这种方式也更简单通用。
我们先给bubble_sort()函数设置断点,然后利用commands命令预设一条命令,把变量n的值修改为10。命令如下:
b bubble_sort
commands 1
set var n=10
end
设置完之后,用run命令开始运行程序。结果如下:
bubble_sort()处的断点被触发后,程序暂停,用print命令查看变量n的值,已经被修改成了正确的值:10。
可见,我们的设置是有效的。
断点触发后,让程序自动恢复执行
那么,在bubble_sort()处断点被触发,变量n的值被修改之后,如何让程序自动恢复执行呢?
很简单,只需要在预设的命令中添加一个continue命令就可以了。为了证明我们的设置确实是生效的,我们在修改变量n的前后,各添加一个格式化打印语句,把变量n的值打印出来:
b bubble_sort
commands 1
printf "The original value of n is %d\n",n
set var n=10
printf "Current value of n is %d\n",n
continue
end
结果如下图:
解决第一个BUG
从运行结果可以看出,断点被触发后,我们预设的语句被正确执行,变量n的值被修改为10,然后程序自动恢复执行。
到此,第一个BUG已经解决了。
2、解决第二个BUG下面,我们解决第7行代码中的数组访问越界错误:数组的元素个数是n,但是bubble_sort()中第一个for循环的终止条件是i
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?