前言
Linux基础工具:安装软件我们用的是yum,写代码用的是vim,编译代码用gcc/g++,调试代码用gdb,自动化构建用make/Makefile,多人协作上传代码到远端用的是git。
在前面我们把yum、vim、gcc、make、git都已经学习了,今天我们就将学习最后一种基础工具gdb调试器
调试器gdb
1. 背景——debug和release
- 源文件编译时有有两种模式,debug模式和release模式
- debug通常称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序(因为debug模式在形成可执行程序的时候,添加了debug信息,所以可以被追踪调试)
- release称为发布版本,它往往是进行了各种优化,是程序在代码大小和运行速度上都是最优的,以便用户很好的使用
- 在Linux系统上gcc/g++形成的可执行程序,默认是release版本
- 如果想以debug形式编译,我们需要添加-g选项(-g生成调试信息)
- 验证:readelf -S指令可以读取可执行程序对应的二进制构成,再通过grep debug可以将debug信息过滤出来
2. 使用gdb
- 启动gdb:gdb 可执行程序
- 调试需要启动gdb
- 注意在Linux系统上gcc/g++形成的可执行程序,默认是release版本。
- 如果想以debug形式编译,我们需要添加-g选项
- 退出gdb:quit/q
- 调试结束,退出gdb
- 查看到调试的代码:list/l
(gdb) l #list不一定从第一行开始显示调试代码,并且只显示一部分代码
(gdb) l n #指定显示第n行调试代码
(gdb) l function #指定显示某函数的代码块
(gdb) l [begin,end] #指定显示某区间的代码块
- list查看调试代码,注意list不一定从第一行开始显示调试代码,并且只显示一部分代码
- 如果我们想从第一行开始显示所有调试代码,list 0之后继续不断list或者直接回车即可显示所有代码
- gdb会自动记录最近一条指令
- 运行程序:run/r
- 运行程序,直到遇到断点或者程序运行结束
- 在gdb中的run指令相当于VS中运行的快捷键Ctrl+F5
- 设置断点:break/b
(gdb) b 行号 #在某一行设置断点
(gdb) b 函数名 #在某一个函数开头设置断点,即函数代码块的起始位置第一条有效语句
(gdb) b 文件名:行号N #在某个文件的第N行打断点,即跨文件设置断点
(gdb) b 文件名:函数名 #在指定文件的指定函数设置断点
- 要调试代码,我们一般都需要先设置断点
- 在VS中我们设置了断点,就可以在该代码行的最左侧显示一个红点,表示在当前行设置了断点,但是在gdb中它不是图形化界面,不会显示红点也没有任何提示,那我们在gdb中我们如何知道在哪里设置了断点了
- 查看断点信息:info break
- 查看断点信息
- 注意当我们退出了gdb,那我们在上一次所有设置的断点就没有了
- 删除断点:delete/d
(gdb) delete [breakpoints] n #删除断点编号为n的断点
(gdb) delete [breakpoints] #删除所有断点
- 注意删除断点是根据断点编号删除的,不能像通过断点的行号删
- 删除所有断点
- 禁用断点:disable
(gdb) disable 断点编号N #禁用断点编号为N的断点
- 有的时候我们不想删除某个断点,但也不想使这个断点起效那该怎么办呢——禁用断点
- 不想删某个断点是因为想保留调试的痕迹,不想使这个断点起效是因为我们已经确定问题不在该断点处
- 启用断点:enable
(gdb) enable 断点编号N #启用断点编号为N的断点
- 逐语句和逐过程:step/s & next/n
- 我们调试代码一般先run将代码运行至断点停下来之后,就需要逐语句或逐过程一步一步慢慢的调试代码找出问题所在
- 逐语句和逐过程:
- 逐语句:遇到函数,进入函数体中,不跳过函数体中的语句
- 逐过程:把函数也当做一个语句,直接调试到下一条语句
- 区别:逐语句和逐过程都是一次执行一条语句,唯一的区别就在于逐语句会进入函数,逐过程不会进入函数
- gdb调试会直接跳过空行,跳转到下一句有效代码处
- 我们现在可以gdb中设置断点,逐过程的调试代码了,但是在调试代码时我们还需要监视变量
- 打印变量值:print/p 变量
- 打印变量值
- 但是我们发现print打印变量的值不能边调试边打印变量值,即不能常显示变量值
- 边调试边打印变量值(常显示变量值):display 变量
- 跟踪查看一个变量,每次停下来都显示它的值
- 取消常显示变量值:undisplay
(gdb) undisplay 变量编号 #取消指定变量编号的常显示
(gdb) undisplay #取消所有变量的常显示
- 取消对常显示变量的监视,注意不是通过变量名取消的,而是通过变量编号取消的
- 跳转到N行:until 行号N
- 例如现在我们的代码陷入到了一个循环中,我们想直接把这个循环执行完,那就可以使用until直接跳转到循环的下一行
- 快速调试一个函数是否有bug:finish
- finish:执行到当前函数返回,然后停下来等待命令
- 注意:使用finish需要先进入到函数
- 从一个断点处运行到下一个断点处:continue/c
- continue:从当前位置开始连续而非单步执行程序,一般用于从一个断点处运行到下一个断点处
- run VS continue
- run:从开始连续 而非单步执行程序——一般run运行程序到第一个断点处停下来
- continue:从当前位置开始连续 而非单步执行程序——一般continue从当前断点运行到下一个断点停下来
- until & finish & continue
- until:在函数内范围式 的运行
- finish:按函数为单位 的运行
- continue:从一个断点到下一个断点 大面积的范围式的运行
- 调试:调试的本质是先一定要找到问题,只有找到了问题位置,才能通过分析它周边的数据,分析上下文,分析出问题出错的原因,原因清楚了,我们才知道对应的解决方案!
- 我们可以通过until、finish、continue、等操作不断地缩小定位问题所在位置。
- 一般先使用continue不断将问题从多个函数缩小到两个函数,再通过finish将问题缩小到一个函数,最后再通until、逐语句逐过程的定位问题所在位置
- 修改变量的值:set var 变量=变量值
- 例如在一个循环中,我们知道了当循环变量的值为98的时候就会出现bug,那我们怎么可以快速执行到循环变量值为98的时候呢——修改变量的值
- 查看各级函数调用及参数:breaktrace/bt
- 查看各级函数调用及参数
- 查看当前栈帧局部变量的值:info/i locals