前言
我们已经学习了
linux
下许多的工具,vim
、gcc
、make/makefile
等;已经能够在
linux
写代码,并且进行编译运行,让程序在linux
下跑起来。
但是,如果我们在写代码的时候遇见了错误;但是我们并不知道错误在哪,在windows
下,我们可以进行调试来查找代码错误的位置进行修改;我们在linux
就只能查看源代码,直接查找错误,这样很麻烦;
现在就来学习linux
如何调试程序。
调试程序 ——gdb/cgdb
Debug/Release
模式
在之前学习C语言
时,听说过Debug
和Release
,只知道Debug
时用来调试的,程序员写代码的版本;而Release
是发布版本。
现在我们来看一下这两个模式有什么区别
- 首先的区别就是
Debug
会生成程序的调试信息,而Release
不会生成程序的调试信息。
因为Debug
模式会生成调试信息,所以Debug
模式的程序就要比Release
模式的程序大小要大。
我们如何验证呢?
linux
中 gcc
编译默认生成的是Release
版本,我们要生成Debug
模式就要带-g
选项。
现在有这样一段代码test.c
文件
#include<stdio.h>
int func(int n)
{int ret = 0;for (int i = 1; i <= n; i++){ret += i;}return n;
}
int main()
{int n = 100;int sum = func(n);printf("sum = %d\n", sum);return 0;
}
我们在linux
下进行编译
这里有这样的提示,那是因为C98
不支持在for
循环中定义变量,要使用C99
,上面也有提示。
所以我们就要这样来编译
gcc test.c -o test -st=c99
这里写成makefile
方便操作。
这样生成的是Release
模式的程序,我们查看它文件属性
现在,我们使用-g
选项生成Debug
模式的程序
可以看到Debug
模式的要比Release
模式的程序要大一些。
gdb
/cgdb
的使用
这里,只有Debug
模式的程序才能被调试;Release
模式下不能被调试,因为缺少调试信息。
在使用之前可能需要进行安装
yum -install -y gdb
yum -install -y cgdb
1. 进入调试
gdb 可执行程序
这里无论是gdb
还是cgdb
,都是可执行程序,对可执行程序进行调试。
可以看到这样就进入gdb
调试了,但是gdb
调试现在看不到我们的源代码。
2. 退出调试
quit
现在来看一下cgdb
调试的界面
cgdb test
这样的界面看起来要比gdb
好用一些,所以这里就以cgdb
为例,来学习调试
cgdb
中,屏幕上半部分可以看到一部分代码;其中绿色箭头
指向的地方就是当前程序运行的位置。
退出调试仍然是quit
3. 查看代码
查看代码,l
;后可以什么的不跟,也可以跟行号或者函数名
l
:查看源代码,从上次位置开始,依次显示10
行代码l 文件名:行号
:列出指定文件的源代码l 函数名
:列出指定函数的源代码
l 文件名:行号
l:函数名
这里列出的可能有一些差别。
4. 运行代码
我们进入调试,但是代码并没有运行起来;在windows
下我们之间F5
就让代码运行起来了;而cgdb
中r
命令可以让代码运行起来
r/run
,执行代码:
逐步执行
有了断点,我们在
r
时程序就会停止在断点处,那我们该如何一行一行执行代码呢?在
windows
下,我们是按F10
和F11
来依次执行代码;在
linux
中cgdb
,我们使用n/next
和s/step
来依次执行代码
n/next
,相当于F10
,一行一行执行代码, 在遇到函数时,不进入函数内部;
s/step
,就相当于F11
,一行一行执行代码, 在遇到函数时,进入函数内部;
这里就不演示了。
执行到某处
在我们调试程序时,程序现在停止在一个断点处,我们不想一行一行执行代码,而是想要让程序直接运行到下一个代码;
只需要指向
c/contine
即可
c/continue
r
重新执行
如果现在程序正在执行,我们想要让程序重新执行,只需要
r
即可;这是会询问我们是否重新执行,
y
即可
finish
执行到当前函数结束,然后停止
until
执行到某一行
until 行号
程序执行到某一行然后停止。
现在执行until 11
,让程序执行到11
行。
5. 断点
增加/删除断点
我们指向
run/r
后,发现代码直接就执行结束了;但是在我们调试的时候,我们并不希望代码执行运行结束,我们需要通过断点让代码在指定位置停下来;
在windows
下,我们通过快捷键F9
或者鼠标点击来打断点和去掉断点;
在cgdb
中,我们通过命令**b/break
**来打断点,通过delete/d
来取消断点。
b
打断点
b 行号
:在指定行打断点b 函数名
:在函数开头打断点
可以看到,我们打断点之后并看不到任何断点信息,那如果我们想要看到已经存在的断点,可以使用命令
info b
来查看
info b
查看所有断点信息
d
删除断点
我们打断点可以通过
行号
,但是删除断点我们就不能使用行号了,而是使用Num
断点编号。
这里还要注意一个点,断点编号时不断递增的,不会随着我们删除断点而减小
什么意思呢,就是现在存在两个断点我们删除了其中一个断点,然后再次创建了一个断点,它的编号就是3
而不是2
。
这样有了断点,我们在执行
r
时就程序就会停止在断点处。
这里看一下上半代码部分,可以看到程序停在了
15
行,并且断点位置的行号颜色为红色。
启用/禁用断点
当我们在调试程序时,我们增加的断点并不一定所有的都能用的到,有一些断点我们不想让它在这次调试中起作用,这时就可以禁用这个断点
看到这里可能有疑惑,为什么不直接删除呢?
如果代码非常的多,删除了之后,接下来调试要用到,又要重新去找,非常浪费时间。
启用断点
enable 断点编号
禁用断点
disable 断点编号
这里断点默认是启用状态的。
那现在执行一下看是否真的禁用了呢?
可以看到第一个断点并未触发,而是直接触发第二个断点。
6. 监视
在windows
下我们通过监视窗口来查看一个变量的值;
而在linux
的cgdb
中,我们也可以通过指令来查看变量的值。
监视变量
p
p
用来查看一个变量当前的值
但是这样,我们在此执行代码会发现,执行过后就不在显示了;
这样我们每次查看就要去输入指令
p 变量名
,这样好麻烦,我们想要每一次执行过后,它都会显示出来变量的值
display
用来跟踪显示变量的值。
display
如上图所示,我们每一次执行代码,变量的值都会显示出来。
监视函数栈帧内局部变量
如果我们不是想要查看某一个变量,而是查看当前函数内所有的局部变量?
我们就要用到info/i locals
可以看到func
中所有的局部变量都显示出来了。
查看当前函数调用栈帧
如果我们想要查看当前的函数调用栈帧,直接使用
bt/backtrace
即可。
cgdb
常用小技巧
1. watch
watch
:用来监视一个变量的值是否发生变化,发生变化时会提示。
我们使用info b
查看断点中也可以看到watch
监视的变量。
2. set war
set war
:在调试过程中,修改变量的值。
可以看到,我们在调试过程中,使用set var
就可以修改一个变量的值。
3. 条件断点
添加条件断点
b 行号 if 条件
如上图所示,新添加的条件断点(当i==10
时触发)。
可以看到程序在i==0
时,断点触发,停止在第九行。
给已存在断点增加条件
当我们需要给已经存在的断点增加条件时,我们需要指令
condition 断点编号 条件
到这里本篇内容就结束了,希望对你有所帮助。
制作不易,感谢大佬的支持。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws