一、什么是GDB
1. 为什么要有GDB
我们在开发程序的过程中,应该很少会有一次就编译通过的吧,有时候即便是写了短短几十行的代码,都难免会有一些小的疏忽,更何况是几千上万甚至更大的代码,反正我在开发中几乎每次写完程序都会经过反复的调试,键盘的F11键经常会坏掉。在程序中,出现的错误主要分为 2大 类,即语法错误和逻辑错误:
- 语法错误,顾名思义就是不符合编程语言语法的错误,这类错误一般都可以由编译器诊断出来,GCC编译器的编译阶段会进行语法检查(这方面内容我在GCC编译器那篇文章中已经详细介绍过了);
- 逻辑错误,这部分错误是指我们在程序设计的逻辑上的错误,程序编译通过,但是执行结果并不符合我们的预期,这类错误就没有办法依靠GCC编译器去检查了,需要我们自己调试分析;
程序出现语法错误,可以依靠GCC检查出来,而逻辑错误就要我们今天的主角GDB登场解决了。所谓调试(Debug),就是单步执行代码,或通过断点让程序执行到某个位置,以此来逐步锁定程序出现问题的范围。在单步调试的过程中,我们可以监控程序执行的每一个行为,包括变量值的变化、函数的调用、内存中数据的变化、线程的调度等等,以此来修复BUG或者优化代码。
我们在Windows下开发最常用的Visual Studio,它自带的调试器是Remote Debugger,调试器与整个IDE无缝衔接,使用非常方便。在Linux下C/C++必备的调试器就是GDB了,下面讲解如何查看GDB版本及安装GDB。
2. 下载安装GDB
(1)查看GDB版本
gdb -v gdb --version
如果你的执行结果如下,说明已经安装好了gdb,版本号如下,一般我们装好Linux后可以通过这个命令来测试是否已经安装gdb调试器。
如果你的运行结果显示 not found ,说明未安装gdb调试器,安装gdb的方法主要有两个,下面一节介绍安装方法。
bash: gdb: command not found
(2)安装GDB调试器
安装gdb主要有两种方法:
① 直接安装。通常我们安装好Linux之后,操作系统内会附带有gdb的安装包,我们可以直接使用操作系统内已有的gdb安装包,使用包管理器进行安装。这种方法简单有效,只需要一条命令就可以安装成功(以CentOS为例)
yum -y install gdb
安装好后,可以通过 gdb -v 查看版本,一般来说通过这种方式安装的gdb都不是最新版本,并且无法自己选择版本。
② 通过源码安装。源码安装是指首先去网上下载源码压缩包,然后在本地解压安装,我们可以选择自己需要的版本进行安装,可以直接点击源码包的链接gdb源码去下载。
里面有很多版本和格式,我们可以选择一个自己需要的版本 .tar.gz 格式下载,下载后进行下面的操作,比如说我下载最新的版本gdb-11.2.tar.gz。(通过源码安装文件以及 tar 压缩包管理命令可以查看我的文章《【Linux王者之路基础篇:基本命令与基础知识】Linux常用shell命令(及相关知识)详解与用法演示》第六章节《六、压缩文件管理相关命令》有详细介绍)
- 解压文件
找到下载好的压缩包并解压
tar -zxvf gdb-11.2.tar.gz
如果你是在Windows下下载好的压缩包,要传到Linux下,可以借助SecureCRT的rz命令,教程请见《【Linux开发环境搭建:工具篇】SecureCRT工具连接虚拟机、rz/sz传输、中文乱码问题解决》。
我下载的太慢了,半小时才下载三分之一,所以后面就只说命令了。
- 解压后进入解压出来的目录
- 运行 configure 文件配置环境,这时候会创建一个Makefile文件
- make 编译源码文件
- 安装 make install
- gdb -v 查看
tar -zxvf gdb-11.2.tar.gz cd gdb-11.2 ./configure make make install gdb -v
(3)卸载GDB
gdb调试器的卸载命令
yum remove gdb
二、GDB的启动与调试程序的上下文设置
1. 准备知识
(1)测试程序中的main函数参数解析argc与argv[]
首先我们创建一个C文件gdb_test.c,以用于后面举例使用,程序如下
#include <stdio.h> #include <stdlib.h> struct st { int a; int b; }; void print_array(char* array, int len) { int i = 0; for(i = 0; i < len; i++) { printf("array[%d]: %c\n", i, array[i]); } } int main(int argc, char* argv[]) { struct st st_temp; int i = 0; char array[5]; st_temp.a = 10; st_temp.b = 11; for(i = 0; i < 5; i++) { array[i] = i + '0'; } print_array(array, 5); for(i = 0; i < argc; i++) { printf("hello...argv[%d]: %s\n", i, argv[i]); } return 0; }
在这个测试程序中,main函数貌似有点不同寻常啊
int main(int argc, char* argv[])
多了两个东西,argc和argv,其实在main函数中本就应该有这两个参数,只不过在我们平常的大部分学习中,都弱化了这两个参数的作用,估计大部分人在学习编程时都从来没有写过这两个参数。第一个参数argc用来统计程序运行时传递给main函数的命令行参数的个数,这个不需要我们设置;argv是一个字符串数组,用来存放我们传入的参数,其中argv[0]默认就是程序运行的路径名。说起来不好理解,我们举个例子,就用上面给出的gdb_test.c文件,我们编译好运行一下,并传递参数
gcc gdb_test.c -o g3 ./g3 111111
首先可以看到argc的值是2,argv的第一个参数是 ./g3 表示当前目录,第二个参数是我们传入的111111。如果我们不传任何参数,argc就是1,argv只有一个字符串就是当前路径。
(2)gcc编译时 -g 选项帮我们做了什么?
gdb主要的作用是跟踪程序的执行过程,所以要想用gdb调试程序,首先要把源程序编译为可执行文件。但是,我们正常使用gcc命令编译出来的可执行文件是无法通过gdb调试的,因为这样编译出来的可执行文件缺少gdb调试所需要的调试信息(比如每一行代码的行号、包含程序中所有符号的符号表等信息)。要想生成带有gdb调试信息的可执行文件,就要在gcc编译的时候添加==-g== 选项。
你可能通过尝试后会说,不加gcc的 -g 选项也能进入gdb调试,确实是这样,但是进入gdb并不代表就可以调试,比如下面
我们不加 -g 编译一个源文件,并启动gdb
进入gdb后我们发现,使用 r 命令执行可以,但是通过 list 查看源代码却不行。这是因为,我们不加 -g 编译出来的可执行文件不包含行号和符号表等调试所需要的信息,所以你想查看源码、添加断点都是无法实现的。而这就是 -g 选项的作用,我们可以对比一下加与不加 -g 选项生成的可执行文件大小
能够看得出,加了 -g 选项后编译出来的可执行文件占据了更多个空间,这是因为,它包含了调试信息。
有时候,我们在编译时会组合 -g 和 -O 来使用,通常用 -Og 来实现在保证快速编译和更好的调试前提下,进行一定的优化。
(3)启动GDB与指定目标调试程序的方式
启动gdb调试器分为四种情况:
① 调试非运行状态且编译通过可运行的可执行文件
gdb exe(可执行文件名) gdb ./exe(可执行文件名)
② 调试正在运行的可执行文件
gdb -p pid(进程号)
③ 调试core
gdb exe(可执行文件名) core.19761(core文件名) gdb ./exe(可执行文件名) core.19761(core文件名)
上面这三种情况会在后面对应的章节详细介绍。
④ 假如直接使用 gdb 命令进入gdb调试器,gdb自己是无法确定要调试哪个可执行文件的,即使当前目录只有一个可执行文件也无法自动识别,这时我们可以手动指定目标调试文件。
提示信息中已经告诉我们使用哪个命令来指定待调试程序了,那就是 file 命令,使用方法是 file 直接加可执行文件所在目录以及可执行文件名,如果可执行文件就在gdb当前工作目录下,可以不加目录,这样我们就可以使用gdb调试 file 命令指定的可执行文件了
不管哪种情况,我们进入gdb时,总会打印一堆声明
要想去掉这些声明,可以在gdb后面加 –silent 或 -q 或 –quiet 选项。
只要最下面有一个 (gdb) 就说明进入成功。
2. 程序上下文
(1)gdb工作目录
默认情况下,GDB调试器会把启动时所在的目录作为工作目录,但有时候我们可能需要根据情况去改变gdb的工作目录,查看gdb当前工作目录和改变工作目录的命令和 shell 下一样。
① 查看当前gdb工作目录
pwd 命令可以查看当前gdb工作目录
② 改变gdb工作目录
使用shell下的 cd 命令,可以改变gdb工作目录,用法与shell下一样
另外提示一下,gdb调试时,也可以使用 tab 键命令补全、上下键查看历史命令等。
(2)程序运行参数
传递运行参数的方式有三种:
① 启动gdb时指定(exe表示可执行文件名,paras表示参数)
gdb --args exe paras
我们用前面的gdb_test.c编译为g3,并传入参数111111111
② set命令
gdb调试器启动后,在运行过程中,可以借助 set 命令指定目标调试程序启动所需要的运行参数
set paras
我们在函数print_array()处设置一个断点,并执行到断点处,然后把函数参数len设置为2,也就是只打印两个数据(array总共5个数据,可以看前面的图中打印结果)
可以看到 set 在运行的过程中改变了参数len的值。
③ 运行时指定
gdb调试器启动后,在运行时可以通过run 和 start 来指定参数
run paras start paras
(3)查看及修改运行环境
① 查看程序的运行路径
show paths
② 设置程序的运行路径
path /xxx/xxx/
③ 查看环境变量
show environment
④ 设置环境变量
set environment PARA=para
(4)输入输出重定向
① 输入输出重定向
默认情况下,程序中的输出都是打印在终端上的,通过重定向可以把结果打印到指定位置。比如,我们可以把程序中的打印结果都打印到某个文件中
可以看到,运行程序后,屏幕上没有任何输出,我们退出gdb查看1.txt文件
程序运行结果都被打印到了该文件中。
② 选择终端
使用终端tty1,命令如下
tty /dev/tty1