在之前学习中我们使用的通常是集各种功能于一体的编译器,例如VS stdio,但是一个程序在编辑后还要进行编译,然后才能产生一个二进制的可执行文件,编辑和翻译工作都可以使用不同的软件进行,例如记事本就是一款编辑软件,除了编辑和翻译,还有一种功能在我们的学习过程中常常会用到,那就是调试。
今天就带大家仔细学习如何在LInux(我使用的Linux版本是centos7.6。)下使用gdb对C程序进行调试。我会先进行讲解,然后会用截图进行演示,确保大家看到现象。
首先要知道自己是否下载调试工具gdb,我们可以通过这个指令进行查看。
gdb --version
这样就能看到我们该操作系统下是否下载gdb,也可以详细看到他的版本,如果没有下载的话只需要使用yum指令进行下载即可。
要注意,如果是普通用户需要进行提权才能下载,如果是root(超级用户)就不需要加上前边的sudo,因为我之前已经下载过了而且版本也是最新的,所以最下边告诉我什么都没有做。
准备工作已经结束,接下来进入正题
首先一个常识就是,只能在degug模式下进行调试,因为release版本并没有调试信息,但是Linux下默认是release版本,所以我们要在编译时加上-g选项。
我们随便写一点C代码。
如何证明加上-g选项产生的可执行文件是debug模式的呢?
可以在使用默认release模式进行编译产生一个新的文件exe。
通过大小我们可以明显看出a.out的大小是要大于exe文件的。还有一种方法就是直接使用gdb进行调试,如果能调试就一定是debug版本,如果不能就是release版本。
对比一下
首先,从gdb模式下退出是q指令。输入q键后直接敲回车就退出到命令行模式。
l指令
单单输入l会帮我们打印我们写的c程序的前十行代码,再输入l会自动向后打印,直至打印至末尾位置。
如图
l指令也可以直接加行号,从num行开始打印。
l num
l还可以直接在代码中查找某函数,然后直接打印出函数上下的几行代码。下图为C代码,我们可以看到有一个add函数和main函数。
我们在gdb模式下想直接锁定main函数,就可以直接使用l指令,空格加main函数即可。
如图:
如果单单只能看代码那就很low了,接下来我们对比VS stdio中调试时的操作,对gdb如何实现同样功能实现详细的讲解。
首先就是打断点操作。
对应vs中F9就是打断点和取消断点,在gdb中对应的操作就是b加行号。
命令行提示断点已经打好了,但是我们怎么知道断点是真的打好了呢?
info b指令
info b指令就是用来查看我们所打得断点的信息。
我们可以看到会给我们提供很多的断点的信息,前边序号和种类不必多说,Disp表示是否常显示,对于断点来说就是断点执行一次之后是否还有效,例如在循环中的断点,如果是keep状态下次就还会有效,如果继续调试还是会停在该断点的位置,如果状态为dis就为无效。
Enb就是该断点的是使能开关,如果是y表示该断点继续使用,如果为n就表示该断点不生效,就会直接跳过该断点。
后边Address表示该断点的地址,whta就是断点在哪个函数哪个位置第几行。
上边都是粗略的解释,下边我会给大家详细演示如何使用它们或者改变他们的属性。
知道了如何创建断点,那么如何删除断点呢?
d 指令
要注意的是,这里d后边跟的是断点的序号,而不是断点的行号。
我们来创建几个删除几个观察现象。
创建时不会按照断点的行号进行排序,第几个打得断点就是几号。如图
删除断点的操作
可见1号断点和3号断点成功被删除了。那么此时我们继续创建断点,序号是补上1和3呢,还是继续向后添加序号为5的断点呢?
一试便知。
不会对断点进行补齐,而是继续向后排列,不要记错咯。
当然我们前边添加和删除断点的操作是在该次调试下有效的,如果q退出调试后再次进入我们之前所打的断点就都不复存在了,再次调试后序号就会从1重新开始。
有关断点,我们还需要知道如何打开或关闭使能开关,但是就算现在说了还是不能好好演示效果,所以在此之前讲一讲gdb中对应vs中f5的操作。下边的指令都要稍微记忆一下,不然很容易忘掉。
点击F5调试后,会直接跳到第一个断点的位置,在gdb中就是r指令。
r:对应vs下F5,跳到下一断点处。
来使用一下,现在我们打了以下三个断点。
输入r进行调试,就可以直接运行到我们打断点的位置。此时再次查看断点信息就可以看到一号断点已经被命中了一次。
接下来介绍另一个操作,逐过程调试,即vs下的F10。
逐过程和逐语句的区别就是,逐语句会进入函数中,会走完程序的所有代码,而逐过程会把函数认定为一个过程,直接走完该函数。
逐过程对应的就是n(next),逐语句对应的就是s(step)。
我们来看现象
我们第一个缎带你打在第十行,可以看出下一步就是输入a的值,然后进行判断。
此时运行到if判断句,我们逐语句或者逐过程调试时,会自动打印出当前所在行。因为linux下没有图形化界面,所以只能打印出提示我们当前光标所在行。作用就和下图的小箭头一样。
因为n是逐过程,所以到达调用函数的语句只会执行一步,我们来观察现象。
我们直接在调用函数的语句前打上一个断点。
直接r跳转,因为前边有打印操作,直接忽略就好了。
可以看到这样一条语句
此时执行n命令。
可以看到,并没有跳转到该函数位置。
前边所说的s是逐语句运行,所以按理说会跳转到add函数的位置,实践检验真知。
同样将断点设置在进入函数时的语句,r直接跳转到断点处,然后使用s指令观看现象
上边return的语句就是我们在add函数中的语句。
现在已经知道基本的调试的操作了,但是在vs中还有一个十分关键的功能就是
在调试中,一定想要知道程序中变量的变化,所以就需要我们的监视功能。
gdb下当然也是有这个功能的,对应的就是p加上变量名,就可以查看当前程序中该变量的值。
为了验证这个功能,新写一段代码。(编译时记得-g选项哦)。
直接在main函数第一条语句前打上断点,然后单步运行进行观察。
可以看到,我们使用p指令时,下边就会给我们显式打印a的值。
未执行b的初始化时,b的值为0,单步运行后即为4,可见操作是正确的。
但是对比vs下是一个悬浮窗口,而且是常显示的,我们可以一直看到。
当然,gdb也可以设置常显示。
每次向下执行时都会打印两个变量的大小,这就是变量的常显示。
如何取消变量的常显示呢?
我们可以看到,每次打印常显示变量的时候,前边都有一个编号,我们就是使用undisplay加序号即可。
如何查看我们设置的常变量呢?
info display
将一号变量取消常显示
我们在display时,也可以同时常显示多个文件,将所有变量常显示全部关闭,然后同时将三个变量常显示。但是要求这几个变量是相同类型的即可。
还记得前边断点的描述吗?如何不使用某个断点呢?
我们可以关闭该断点的是能开关。
使用的指令就是disable加序号。要和前边的display要区分开哦!
此时这个断点就是未启用的。
如果想继续用这个断点呢?就要启用该断点,enable指令加序号就能使使能开关设置为y。
调试中还有其他的操作
- until n行号:直接跑到第n行。
-
finish进入某个函数时,如果想要直接走完这个函数就可以直接finish。
-
c:从这个断点直接到达下一个断点。
-
如果在运行过程中想要修改某个变量的值,就可以使用set var指令,修改一个变量的值。
可以看出,在修改后a的值就变成了1,程序在后边的运行中a的变量都会因为这次改变而改变,从而影响程序运行的结果。
gdb下基本的指令操作到这里就全部讲解完毕了,如果有什么错误还请指正,欢迎大家讨论。