🔥博客主页: 小羊失眠啦.
🎥系列专栏:《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》
❤️感谢大家点赞👍收藏⭐评论✍️
文章目录
- 一、生成可调式文件
- 1.1 release与debug
- 二、调试打开与关闭
- 2.1 启动调试
- 2.2 l 查看代码
- 2.3 退出调试
- 三、运行与断点
- 3.1 r 运行程序
- 3.2 b 断点操作
- 3.2.1 设置断点
- 3.2.2 查看断点信息
- 3.2.3 编号含义
- 3.2.4 取消断点
- 四、单行与单步
- 4.1 n 单步调试
- 4.2 s 单步调试
- 五、查看变量
- 5.1 bt 查看调用堆栈
- 5.2 p 临时查看变量
- 5.3 display 常显示变量
- 六、快速跳转
- 6.1 until 指定行
- 6.2 finish 函数
- 6.3 c 断点
- 七、其他命令
- 7.1 disable 断点使能
- 7.2 set var 设置条件
- 7.3 ptype 查看变量类型
vim
可以编写代码,gcc/g++
可以编译代码,此时只最后一件神器,就能进行完整的开发工作,那就是通过 gdb
调试代码,毕竟谁都不敢保证自己的代码没有问题,所以就有调试器这种东西帮助我们定位问题,进而解决问题
现在让我们一起进入 gdb
的世界,体验纯命令行调试代码的妙处
注意: 需要提前下载好 gdb
sudo yum install -y gdb
一、生成可调式文件
可能有的同学一安装好 gdb
就迫不及待地开始了调试,通过 gdb 最终生成文件
进入 gdb
后,会发现什么指令都用不了,除了 q
退出 gdb
和 r
运行程序
原因很简单:gcc/g++
默认生成的程序为 release
发行版,也就是说不含调试信息,所以我们首先要解决这个问题
1.1 release与debug
程序分为 release
与 debug
两个版本,其中前者是给测试工程师找毛病的,而后者则是我们开发使用的版本,debug
内置很多调试信息,因此它能很好的进行调试
而 gcc/g++
默认不会生成 debug
版的可执行程序,我们可以通过指令来搜索默认生成的程序中是否含有调试信息
readelf -S mytest | grep -i debug //在默认生成的可执行程序 mytest 中查找调试信息
想要解决问题也很简单:在编译时,指定编译器生成 debug
版的程序就行了
注意:因为已经学习了 Makefile
,我们直接在文件中更改就行了
# Makefile 文件中
g++ $^ -o $@ -g
# 注意:其中 -g 就是指定其生成 debug 版的程序
我们先通过 make clean
指令清理原来的解决方案,然后再通过 make mytest
指令编译程序
得到可执行程序后,用同样的方法对其进行查找
接下来就可以愉快的进入 gdb
进行调试了
二、调试打开与关闭
首先要学习如何打开和关闭 gdb
2.1 启动调试
我们调试的对象是已经生成的可执行程序,并非最开始的源文件
这很好理解,因为在VS中也是先编译、再调试
通过 Makefile
的自动化任务生成 mytest
可执行程序
然后通过指令 gdb mytest
即可进入调试
gdb mytest
注意: 调试的是最终生成的可执行程序;要确保生成的程序为 debug
版,不然后续无法调试
2.2 l 查看代码
只要进入了 gdb
,我们可以通过 l
指令随时随地查看我们的代码,且查看代码时不会干扰其他调试命令
l
命令一般是配合数字进行查看,每次只可查看十行,如 l 1
就表示从代码第一行开始查看其前后十行,按回车后可接着往下展示,直到代码展示完毕
(gdb) l 1 //从代码第一行开始查看其前后十行
(gdb) l //默认查看代码最中间的十行内容
注意: 经过测试发现,l
的查看策略是每次展示十行,然后想要查看的第n行位于中间,l 1
能直接能从第一行开始的原因是前面已经没有代码了,因此如果默认只输入 l
就会展示当前代码的最中间位置前后十行
2.3 退出调试
gdb
退出不像 vim
那样麻烦,指令 q
就表示退出 gdb
调试
(gdb) q //退出gdb 调试
三、运行与断点
调试最重要的目的是帮助我们快速定位到问题,然后分析解决,此时断点就显得很重要了,如果没有断点,那只能一步步的调试,效率很低,下面就来看看如何让程序在 gdb
中跑起来及断点相关操作
3.1 r 运行程序
gdb
中能直接快速运行程序,假设没有断点,那么程序会直接运行出结果
(gdb) r //运行程序
其实此时可以直接把这个看作VS中的黑框框,r
就相当于 F5
,在没有断点的情况下,程序会直接出结果的,而最终的结果值也会紧跟着输出
3.2 b 断点操作
断点在 gdb
中意为 breakpoint
,其中首字母 b
就表示断点的意思,因为是纯命令行操作,所以刚开始调试麻烦点是必然的
3.2.1 设置断点
(gdb) b 行号 //在指定行号打断点
(gdb) b 函数名 //在指定函数处打断点
注意: 纯命令打的断点不如图形化界面直观,但我们也可以通过指令查看断点信息
3.2.2 查看断点信息
指令 info b
可以搜索所有断点,并展示其详细信息
(gdb) info b //查看所有断点信息
3.2.3 编号含义
查看断点信息时,会发现有一栏 num
,这表示每个断点的编号,因为我们不能直接对断点进行区分,于是就需要引入编号这个概念,这个概念在 gdb
很多地方都有体现
注意: 除非 gdb
关闭,否则它的编号是一直累计的,比如我们把断点1、2都删了,然后再新打一个断点,断点编号就为3
3.2.4 取消断点
有时候想取消断点,就可以通过 d 断点编号
,取消指定断点
(gdb) d 断点编号 //由此可见断点编号的重要性
有了断点之后,我们就可以配合 r
指令,运行至断点处
注意: 不同于VS中的 F5
,r
指令要么运行至最近一个断点处,要么将程序运行完,也就是说,r
是无法实现两个断点间移动的,再次按 r
会提示是否重新运行程序
四、单行与单步
调试这个东西总得一步一步来,不然问题就不好找到了
4.1 n 单步调试
单行调试即逐过程调试,对应着VS中的 F10
,即遇到函数不会进入,指令为 n
(gdb) n //单行调试,不会进入函数内部
单行:一行一行的来,每次运行完一行内容即可
4.2 s 单步调试
单步调试对应着VS中的 F11
,不同于单行调试,单步调试能进入函数内部,指令为 s
(gdb) s //单步调试,会进入函数内部
单步:即一步一步的来,如果遇到函数,就会进入函数内部,确保程序的每一步都被执行
五、查看变量
调试过程中还有一个很重要的工作:查看变量信息,如VS中的监视窗口,假设没有监视功能,那么我们可能连变量的变化情况都无法捕捉到,庆幸的是 gdb
支持监视功能
5.1 bt 查看调用堆栈
(gdb) bt //查看调用堆栈情况
5.2 p 临时查看变量
指令 p 变量
可以查看指定变量的信息
注意: 指令 p
只能做到临时监视,当执行下一条指令后,原来监视的变量就看不到了;可以看出,p
监视出的值也是有编号的,每调用一次指令,编号就会累加一次
5.3 display 常显示变量
(gdb) display 变量 //常显示变量信息,不会随着指令的执行而消失
注意: 如果我们忘记了程序中有哪些变量,可以随时随地通过 l
指令查看,像这种查看式的指令,是不会影响其他指令运行的;不难发现,常显示的变量也有属于自己的编号,这个编号运行机制跟断点的一样,只要 gdb
不退出,它是会一直累加的
编号存在的主要意义就是方便我们进行监视变量删除
(gdb) undisplay 变量编号 //取消监视指定变量
六、快速跳转
gdb
提供了一些快速跳转的指令,赋予了我们在不打断点的情况下进行跳转的权力(注:先要打断点将程序运行起来),这是VS做不到的
6.1 until 指定行
程序运行后,我们可以直接通过 until 行号
的方式跳转至指定行,这个指令通常用来跳过循环
(gdb) until 行号 //跳转至指定行
6.2 finish 函数
这个指令主要是针对函数的,直接 finish
就可以在不打断点的情况下,跑完当前函数
6.3 c 断点
这个指令就是针对断点的了,前面说过 r
无法实现两个断点间的跳转,因此有一个专门的命令 c
进行断点跳转(注:依然需要先通过 r
指令把程序跑起来)
(gdb) c //进行断点间的跳转
七、其他命令
接下来再列举一些其他命令
7.1 disable 断点使能
使能 的意思就是开关,比如电灯的开与关,我们的断点也能设置开关状态,在不取消断点的情况下让断点失效
(gdb) disable 断点编号 // 关闭断点
能关闭当然也能打开
(gdb) enable 断点编号 //打开断点
7.2 set var 设置条件
给变量设置条件,使程序运行至设定值那一步,比如 set var i=5
后,程序就运行至 i=5
的那一步了
(gdb) set var 变量值 //设置变量值
这个功能就像VS中的给断点设置条件,然后跳转