前言
当前,我们可以使用 make/makefile 来程序化执行代码文件;可以使用 gcc/g++ 等编译器来编译代码;可以使用 vim 编辑器来编写代码;其实在 Linux 当中还有一个工具,可以实现调试工作,这个工具就是 -- gdb。
在了解调试器之前,你应该对代码的发布版本做一些了解:
我们在 VS 当中,在开始执行代码之前,可以选择以两种方式执行这个代码:debug & release :
一般在开发期间使用的都是 debug 模式,在编写好代码之后,如果我们的代码提交到远端,到客户手上的时候,比如在公司当中,用git提交到公司的 仓库当中,公司就可以以release 版本发布,同时,测试人员测试的模式也是 release 版本的。
debug 版本能调试 而 release 版本不能调试。而且,debug 版本代码文件要比 release 版本文件要大上不少。release 版本在执行效率上也要比 debug 要高。
那么出现上述几种区别的原因就是:debug 版本的代码,在形成可执行文件的时候,会生成调试信息,而 这个调试信息 是 release 版本没有的。
在 Linux 当中使用 gcc/g++ 编译器编译可执行文件时候,默认是以 release 的方式进行发布的。无法直接调试,如果要想以 dubug 方式发布,在使用 gc/g++ 编译的时候,需要带上 -g 选项来进行编译。
在 下述博客的 进行了详细介绍,包括 如果查看 debug 文件等等:
Linux - 配置系统白名单 - gcc/g++_linux 白名单-CSDN博客
gdb 调试器
使用如下命令,就可以调试一个 可执行文件:
gdb 可执行文件名
如果,在使用上述命令之后,出现如下这个谈话框,并且没有任何报错的话,说明,当前你已经进入了 调试的 交互界面了:
如上所示,text 是一个可以调试的可执行程序,出现上述界面代表你已经成功进入到 调试界面了。
如果想退出的话,可以使用 q/quit 。
此时,我们还看不到代码,因为gdb 是以命令行的形式来进行 查看代码 和 调试代码的,不像 windows 当中 是使用图形化界面来实现,我们可以直接看到代码。
此时,你可以使用 list/l 来查看全部代码 :
你可以发现,我们上述是使用了两次命令(一次 list 一次 l),才查看到了全部的代码,其实 单独使用 list/l 不一定会从第一行显示全部的代码。 所以,我们的带上参数:
l/list 0 (0代表行数,可是代码当中任意行开始)就代表 从第 0 行开始显示:
上述都是只打印了 10 行,如果我们想显示全部代码的话,下面有一种方式:
当我们使用了 l/list 0 这个命令之后,其实 gdb 是会自动记录上一条指令的,当我们按下 回车之后(前提是 使用了 l/list 0 这个命令之后 ) ,他就会认为我们此时想要继续执行 list/l 命令,那么他就会继续显示代码(在上一次显示的最后一行的下一行开始):
而且,最后还会提示,从共有多少行代码。
还可以 使用下述命令,来查看 代码当中某一个函数的全部代码:
list/l 函数名
那么我们在调试的时候,肯定不只是 查看代码,而是要先找到问题所在,对于问题的所在,我们可以看报错位置,然后推测报错位置,然后使用条件断点,或者是在代码当中写入停止的代码语句,比如用 if 判断一下 ,当 程序走到哪一步的时候就停止,这种方法在 程序栈帧迭代较深的 代码当中尤其适用,比如 在 八数码问题当中,我们很有可能会遍历到很深的 状态矩阵当中(在八数码问题当中我们 不管使用 A* 还是sm 启发式算法,都会遍历出很多的 状态矩阵,这些状态矩阵就是 我们在挪动 其中方块,移动不同方块,和方块移动位置不同,都会产生不同的状态矩阵(也就是一种中间过程的情况)),关于八数码问题的介绍具体请看下述博客:八数码问题-c语言_八数码问题c语言代码_chihiro1122的博客-CSDN博客
之所以在上述举出这么多篇幅的例子,就是想说:调试最离不开的就是断点,不管是是在最开始的时候,比如在 VS 当中我们需要打上断点,然后按下 F5 程序就会直接运行到 断点处停止,然后我们进行 进步一调试;还有一种可能是在 调试阶段打上断点,在调试过程当中可能也会遇到程序运行比较麻烦的地方,也可以在调试过程当中打上断点来跳过这一步骤。
那么 gdb 作为一个好用的 调试器,他肯定也是支持断点的,那么接下来,将会对 gdb 当中打断点方式进行说明:
在 gdb 当中 运行程序,打断点
在 gdb 调试界面下,使用 r 命令就可以直接运行程序:
由于此时我们没有在程序当中打上断点,所以此时程序就直接运行到结束了。退出的时候也是正常退出的,就类似在 VS 当中的 F5/ shift F5 开始调试一样。
使用 在 gdb 调试窗口下,使用 b 行号 可以在当前调试文件当中的 指定行打上断点。此时我们在使用 r 命令既可以从程序运行,运行到 指定断点处 停下来了:
在 VS 当中如果我们在某一行打上了断点,是可以直接看到 这个红色的断点在哪一行存在的:
但是在 gdb 当中,这个断点我们是看不见的,就算我们 打完断点之后再去 使用 l 0 查看整个代码还是看不了断点:
其实使用 info b 就可以查看我们打上的所有所在断点行数了:
我们发现,在使用 info b 命令之后,出现的不只有 断点所在代码行数,还有一些其他信息,这些信息所代表的意思,如下所示:
我们发现,在gdb 当中的断点是有 编号的,当我们像删除某一个断点的时候,不能像之前一样使用 行号来删除断点,而是应该使用 断点的编号来删除断点。在 gdb 当中我们可以使用 d 断点编号
来删除某一个结点:
而且,需要注意的是,我们设置的断点这些信息,都是在当前gdb 运行进程之内使用的,也就是说,我们在当前 gdb 调试窗口下设置的各个调试信息,如果退出一个 gdb 然后在进去 gdb 调试的话,尽管是同一个文件,在上一次 gdb 进程当中保存的 调试信息都会删除。
在gbd 当中进行 逐语句 逐过程 调试
在 VS 当中有两种 逐语句的方式 调试,一种是 F10 逐过程,另一种是 F11 逐语句。
逐语句好理解,逐过程其实就是跳过一个过程,一个函数可以被称为一个过程。
使用 n/next 命令就可以进行 逐过程调试:
当我们进行调试运行代码之后,在断点信息当中还有多出一个信息:断点被命中的次数:
像上述两个断点,被命中的次数都是 1。
使用 s 命令 进行 逐语句调试:
gdb 当中的监视窗口
在 VS 当中的监视窗口也是必不可少的,监视可以极大的方便我们查看某变量当前是否合法,或则可以查看很多很多的信息,具体要看自己怎么使用调试:
在gdb 当中也肯定不能缺少 调试窗口,但是因为 gdb 不是图形化界面,所以要 手动输入某个变量,来让 gdb 知道你当前想要查看哪一个变量的值。
我们使用 p 变量名/变量的某些变形之后的值 这个命令就可以查看 这个变量的值,或者是这个变量变形之后的值了。
此时我们就知道了,a 变量的值 在当前程序执行状态下 就是 10。
和VS 当中的调试窗口一样,我们还可以通过调试窗口,查看到 a 的地址等等变形信息:
但是,你有没有发现上述的监视太挫了,VS 当中还可以一遍进行 逐步调试,一般查看 变量的值,而上述 的 p 还有一个一个打出来。
其实 gdb 当中也是有 常显示 监视窗口命令的:display 变量名/变量的某些变形之后的值 ,这样的话,输入的变量 就会一直跟着你调试一起 走:
如果不想 常显示变量值,可以使用 undisplay 命令,但是这个命令不能再后面直接加 变量名等等信息来删除掉 该变量信息的 监视信息:
可以发现,是不行的。
需要注意的是:在常显示的 变量信息 当中,每一个信息是有自己的编号的,和上述 删除断点一样,需要按照编号来进行删除,也就是 使用 undisplay 常显示变量信息编号 删除某一个常显示信息:
可以发现此时就删除成功了。
对于 常显示信息编号,就是显示信息的最左侧数字:
until 跳转指令 - finish 结束当前函数执行指令
假设我们现在在一个函数的循环体当中,现在已经陷入了这个循环体,但是这个我们又想跳出当前这个循环体 ,查看这个循环体结束之后,修改的内容,此时我们就可以使用 until 指定行 来跳转到某一行。
像上述我们使用 until 跳转出函数的循环体的时候,需要查看我们想要跳转到那一行,如果当前只是想要 跳出这个函数的话,也就是只是把当前函数执行完毕,然后查看这个函数的执行结果,那么我们可以在函数当中运行时,使用 finish 指令来结束当前函数的执行。
finish 的使用场景,举个例子,比如当前写的代码崩掉了,但是在主函数当中有 很多个函数需要我们去判断,此时我们要想知道是哪一个函数当中导致代码奔溃的话,就可以执行具体函数当中,使用 finish 来执行函数体,看看是那个函数导致程序奔溃的。
continue 跳到下一个断点
VS 当中你肯定使用 F5 来跳到下一个断点处,在gdb 当中使用 countinue 也能达到 直接从当前位置跳到下一个断点的功能。
修改断点的 Enb 值
VS 当中右键 断点可以选择禁用断点,或者是启用断点,同样的 在gdb 当中有 Enb 值,这个值只有两个值,y or n ,也就代表着这个断点是否被启用。
使用 disable 断点编号 就可以修改 Enb 值:
此时我们 r 运行程序,直接就运行到 第三个断点处了,第一第二个断点已经被跳过了(上述是21 行是因为 20 行断点处为空行,gdb 自动跳过 了)
gdb 命令总结
gdb 当中还有很多命令,像 b 命令打断点,可以用 b 文件名:行号 给指定可执行文件的行号位置处打上断点。
而 b 函数名 这种方式打出的断点,本质是就是在函数体的起始位置处,也就是函数体的第一行代码当中打上断点:
总结:
- list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
- list/l 函数名:列出某个函数的源代码。
- r或run:运行程序。
- n 或 next:单条执行。
- s或step:进入函数调用
- break(b) 行号:在某一行设置断点
- break 函数名:在某个函数开头设置断点
- info break :查看断点信息。
- finish:执行到当前函数返回,然后挺下来等待命令
- print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
- p 变量:打印变量值。
- set var:修改变量的值
- continue(或c):从当前位置开始连续而非单步执行程序
- run(或r):从开始连续而非单步执行程序
- delete breakpoints:删除所有断点
- delete breakpoints n:删除序号为n的断点
- disable breakpoints:禁用断点
- enable breakpoints:启用断点
- info(或i) breakpoints:参看当前设置了哪些断点
- display 变量名:跟踪查看一个变量,每次停下来都显示它的值
- undisplay:取消对先前设置的那些变量的跟踪
- until X行号:跳至X行
- breaktrace(或bt):查看各级函数调用及参数
- info(i) locals:查看当前栈帧局部变量的值(相当于查看 VS 当中本地变量这个对话框)
- quit:退出gdb