目录
Linux下的gcc与gdb
代码编译与链接
函数库
gdb介绍和安装
gdb基本使用指令
示例代码
debug模式和release模式
基本指令
进入gdb调试与显示调试代码
创建断点与删除断点
启用和禁用断点
执行代码
逐语句和逐过程调试
断点跳转
显示指定变量以及对应内容
打印变量的值
执行到指定行
执行完当前作用域的代码
监视变量
运行时临时修改变量内容
条件断点
退出gdb调试
cgdb增强调试过程
Linux下的gcc与gdb
在前面C语言的编译与链接章节中学到了C语言的可执行程序是如何从代码一步一步生成的,下面是前面用到的指令
代码编译与链接
代码编译预处理
gcc -E test.c -o test.i
- 选项
-E
,该选项的作用是让gcc在预处理结束后停止编译过程 - 选项
-o
是指目标文件,.i
文件为已经过预处理的C原始程序
预处理生成汇编
gcc -S test.c -o test.s
如果已经在前面执行了编译预处理生成了test.i
文件,则可以直接用test.i
文件而不是test.c
文件
- 用户可以使用
-S
选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码
汇编生成二进制文件
gcc -c test.c -o test.o
如果已经在前面执行了编译预处理生成了test.s
文件,则可以直接用test.s
文件而不是test.c
文件
- 使用选项
-c
就可看到汇编代码已转化为.o
的二进制目标代码,.o
文件也被称为可重定位目标文件
二进制文件链接生成可执行程序
gcc test.c -o test
如果已经在前面执行了编译预处理生成了test.o
文件,则可以直接用test.o
文件而不是test.c
文件
函数库
函数库一般分为静态库和动态库两种
- 静态库:编译链接时把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,在运行时不再需要库文件了。其后缀名一般为
.a
- 动态库:在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为
.so
。gcc
在编译时默认使用动态库。完成了链接之后,gcc
就可以生成可执行文件
直接编译时选择动态库:gcc test.c -o test
使用静态编译选项-static
时选择静态库:gcc -o test test.c -static
gdb
介绍和安装
gdb
(GNU调试器)是GNU项目的一部分,用于调试C、C++、Pascal、Objective-C、Ada以及其他语言编写的程序。它允许开发者执行一系列的调试操作,如启动程序、设置断点、监视变量、单步执行代码、改变程序状态等,以便于找出并修正程序中的错误
在CentOS安装gdb
可以使用下面的指令:
sudo yum install -y gdb
需要查看gdb
版本时可以使用下面的命令:
gdb --version
gdb
基本使用指令
示例代码
以下面的代码作为本次的调试用例:
#include <stdio.h>int add(int start, int end) {int sum = 0;for(int i = start; i <= end; i++) {sum += i;}return sum;
}int main() {printf("Start programme\n");int start = 0;int end = 20;int result = add(start, end);printf("result = %d\n", result);printf("End programme\n");return 0;
}
对应的Makefile
文件内容如下:
TARGET=test
SRC=test.c$(TARGET):$(SRC)$(CC) -o $@ $^ -std=gnu99.PHONY:clean
clean:rm -rf test
debug
模式和release
模式
默认情况下,直接使用gcc
编译生成的可执行程序是release
模式下的程序,不包含任何debug
调试信息,并且release
模式下无法进行调试。如果需要使用gcc
编译生成的可执行程序是debug
模式下的程序,就需要带上-g
选项,生成的可执行文件包含debug
信息,并且debug
模式可以对代码进行调试
在CentOS下可以使用下面的指令判断一个文件是否使用debug
模式编译生成
readelf -S test | grep -i debug
在Ubuntu下直接使用file+可执行程序文件
即可查看到是否带有debug
的字段即可判断是否使用debug
模式编译生成
使用初始的Makefile
进行编译时,生成的是release
模式下的可执行程序,所以不带有任何debug
信息:
而将Makefile
中的内容修改为如下:
# ...
$(TARGET):$(SRC)$(CC) -o $@ $^ -std=gnu99 -g
# ...
此时就会显示debug
相关字段的信息
基本指令
进入gdb
调试与显示调试代码
在创建出debug
模式下的可执行程序test
后,在终端输入gdb test
,即可进入调试模式
在调试中,输入l+对应数字
显示指定行以及之前的代码,默认一共显示10行代码,而l
后方的数字对应行的代码一般会出现在展示的10行代码中央
如果输入l 0
则默认从第一行代码展示,一共展示10行,效果同l 1
。另外,gdb
中存在指令记忆,不输入任何指令会默认执行上一条指令
技巧:在gdb
调试过程中,如果想要看到文件中的完整代码,可以输入l 0
或者l 1
,在展示完10行代码后接着按回车即可继续展示后10行代码,以此类推直到抵达文件内容末尾。
也可以使用l+文件:数字
显示指定文件中的对应行相应代码,其余效果与l+数字
相同
创建断点与删除断点
在gdb
中,使用b/break+需要添加断点的行号
即可在指定行添加断点
需要查看已经添加断点可以使用info + b
,显示的表格即为已经添加的断点信息
在gdb
中的断点都拥有自己的编号,删除断点时使用del + 断点编号
需要注意,编号的大小依次递增,并且其编号生命周期随着 gdb
结束而结束,所以尽管删除断点,删除断点的编号依旧不会被新的断点使用
例如,在代码的16行和20行创建断点,展示断点信息,再删除20行的断点,再在第5行添加断点
- 创建断点
- 删除断点和添加新断点
同样,也可以使用break 文件:行号
指定文件名中的行位置创建断点,还可以指定函数名b+函数名
添加断点,函数名断点会默认跳到函数的第一条语句
启用和禁用断点
在gdb
中,使用enable+断点编号
启动对应行的断点,使用disable+断点编号
禁用断点
- 对于启用的断点来说,使用
info b
查看断点信息可以在Enb
列看到其值为y
(代表yes
) - 对于禁用的断点来说,使用
info b
查看断点信息可以在Enb
列看到其值为n
(代表no
)
例如,禁用第5行的断点,再启用
- 初始状态
- 禁用状态
- 启用状态
执行代码
使用r
指令运行代码
默认情况下,使用r
指令运行代码会直接显示代码运行结果
如果添加了断点,使用r
指令运行代码时,会直接运行到断点所在行然后等待(前面的代码会执行,但是断点所在行不会执行),此时会显示当前运行所在行
如果代码已经处于运行状态,再使用r
指令会收到提示是否重新运行,按照需求选择即可
例如,在第17行添加断点,执行代码
逐语句和逐过程调试
在gdb
中,使用n
指令进行逐过程调试,在逐过程中,不会执行函数细节,即遇到函数不会进入函数;使用s
指令进行逐语句调试,与逐过程相反,在逐语句中,会执行函数细节,但是不会执行库中的函数
逐语句和逐过程调试过程中,显示的行是下一步需要执行的行,而不是已经执行之后的行
逐语句和逐过程不会在多个断点间跳转
断点跳转
在gdb
中,如果创建了多个断点需要直接从一个断点跳到下一个断点位置并执行两个断点间的代码,可以使用c
指令
例如,在第17行和第5行创建两个断点,使用指令从第17行的断点跳转到第5行的断点
显示指定变量以及对应内容
在gdb
中,使用display + 变量名
可以显示指定变量的值,显示的变量会在每一次调试语句中显示对应的内容,显示的变量会一直持续显示除非离开变量所在作用域、显式取消显示或者退出gdb
显示变量必须在对应变量所在作用域,否则会报错为:No symbol "xxx" in current context
需要查看变量的地址,也可以使用display+&变量名
每一个变量也存在变量,并且变量编号不受作用域的限制,并且也满足线性递增,所以取消显示已经显示的变量,使用undisplay + 变量编号
打印变量的值
使用p+变量名
指令可以打印对应变量的值
执行到指定行
在gdb
中,可以使用until+行号
直接跳转到对应行,并且执行起始位置和指定行位置之前的代码。如果until
中指定行的代码执行完毕之后没有遇到断点,程序就会直接运行到当前作用域结束,否则跳转到下一个断点处
执行完当前作用域的代码
如果需要快速执行完当前函数作用域(非最外层函数作用域,本示例代码中最外层函数作用域为main
函数所在作用域,非最外层函数作用域为add
函数所在作用域)的代码,则可以使用finish
指令
对长的代码块进行区间调试技巧:将断点打在多个函数所在行,使用断点+finish/until+c依次检查函数是否出现问题
监视变量
所谓监视变量,与前面打印变量的值基本一样,但是不同的是,监视变量在gdb中本质是打一个断点,并且只有在变量的值发生变化时,才会再次自动打印变量以及对应的值
监视变量使用watch+变量名
,删除监视变量与删除断点的方式相同,也可以在断点列表中查看到对应的监视变量断点
运行时临时修改变量内容
部分情况下问题已经暴露并且需要改变一下变量内容临时查看是否修改对应变量的内容可以解决问题,此时就可以使用set 变量名=值
在调试代码时临时改变指定变量的值
条件断点
在gdb
中,一共有两种添加条件断点的方式:
- 在指定位置新增条件断点:使用
b/break+断点行号+if 条件
- 在已有断点位置添加条件:使用
condition+已有断点编号+条件
条件断点在新增后,会在断点列表显示断点跳转条件
退出gdb
调试
使用quit
指令退出调试,如果调试的代码还在运行,会询问用户是否结束并退出调试,根据需要选择即可
cgdb
增强调试过程
直接使用gdb
调试需要一直使用指令查看代码以及断点,为了使调试更加方便,可以使用cgdb
代替gdb
,但是基本使用方式与gdb
完全相同,只是cgdb
视觉上更加直观
在CentOS下,使用下面的指令安装cgdb
sudo yum install -y cgdb
使用cgdb调试代码就可以看到下面的界面:
代码行号处的箭头表示下一步需要执行的代码,刚开始并未没有断点或未开始情况下,会指向main
函数的第一条语句
如果添加了断点,则断点所在行号会变成红色,例如下面的结果:
尽量不要使用鼠标滚轮拖动代码窗口,防止出现问题