GDB是什么?
【该章节转载于http://c.biancheng.net/view/8123.html】
从现在开始,我将系统教大家学习使用 GDB,本节先解决第一个问题,即 GDB 是什么。
要知道,哪怕是开发经验再丰富的程序员,编写的程序也避免不了出错。程序中的错误主要分为 2 类,分别为语法错误和逻辑错误:
程序中的语法错误几乎都可以由编译器诊断出来,很容易就能发现并解决;
逻辑错误指的是代码思路或者设计上的缺陷,程序出现逻辑错误的症状是:代码能够编译通过,没有语法错误,但是运行结果不对。对于这类错误,只能靠我们自己去发现和纠正。
也就是说,程序中出现的语法错误可以借助编译器解决;但逻辑错误则只能靠自己解决。实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。
所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。
也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。
对于初学者来说,学习调试可以增加编程的功力,能让我们更加了解自己的程序,比如变量是什么时候赋值的、内存是什么时候分配的,从而弥补学习的纰漏。调试是每个程序员必须掌握的基本技能,没有选择的余地!
就好像编译程序需要借助专业的编译器,调试程序也需要借助专业的辅助工具,即调试器(Debugger)。表 1 罗列了当下最流行的几款调试器:
表 1 常用的调试器
调试器名称 | 特 点 |
---|---|
Remote Debugger | Remote Debugger 是 VC/VS 自带的调试器,与整个IDE无缝衔接,使用非常方便。 |
WinDbg | 大名鼎鼎的 Windows 下的调试器,它的功能甚至超越了 Remote Debugger,它还有一个命令行版本(cdb.exe),但是这个命令行版本的调试器指令比较复杂,不建议初学者使用。 |
LLDB | XCode 自带的调试器,Mac OS X 下开发必备调试器。 |
GDB | Linux 下使用最多的一款调试器,也有 Windows 的移植版。 |
本教程讲解的就是 GDB 调试器。
GDB是什么
GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。
Windows 操作系统中,人们更习惯使用一些已经集成好的开发环境(IDE),如 VS、VC、Dev-C++ 等,它们的内部已经嵌套了相应的调试器。
GDB的吉祥物:弓箭鱼
图 1 GDB 的吉祥物:弓箭鱼
总的来说,借助 GDB 调试器可以实现以下几个功能:
程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;
程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。
后续章节会做以上功能做详细的讲解,这里简单了解一下即可,不必深究。
正如从事 Windows C/C++ 开发的一定要熟悉 Visual Studio、从事 Java 开发的要熟悉 Eclipse 或 IntelliJ IDEA、从事 Android 开发的要熟悉 Android Studio、从事 iOS 开发的要熟悉 XCode 一样,从事 Linux C/C++ 开发要熟悉 GDB。
另外,虽然 Linux 系统下读者编写 C/C++ 代码的 IDE 可以自由选择,但调试生成的 C/C++ 程序一定是直接或者间接使用 GDB。可以毫不夸张地说,我所做那些 C/C++ 项目的开发和调试包括故障排查都是利用 GDB 完成的,调试是开发流程中一个非常重要的环节,因此对于从事 Linux C/C++ 的开发人员熟练使用 GDB 调试是一项基本要求。
“工欲善其事、必先利其器”,作为一名合格的软件开发者,至少得熟悉一种软件开发工具和调试器, 而对于 Linux C/C++ 后台开发,舍 GDB 其谁。
那么,GDB 如何安装,又该怎样使用,需要记住哪些指令呢?别急,我会后续的文章中给大家做详细的讲解。
GDB下载和安装教程
【该章节转载于https://c.biancheng.net/view/8130.html】
基于 Linux 系统的免费、开源,衍生出了多个不同的 Linux 版本,比如 Redhat、CentOS、Ubuntu、Debian 等。这些 Linux 发行版中,有些默认安装有 GDB 调试器,但有些默认不安装。
判断当前 Linux 发行版是否安装有 GDB 的方法也很简单,就是在命令行窗口中执行 gdb -v 命令。以本机安装的 CentOS 系统为例:
[root@bogon ~]# gdb -v
bash: gdb: command not found
如上所示,执行结果为“command not found”,表明当前系统中未安装 GDB 调试器。反之,若执行结果为:
[root@bogon ~]# gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
....... <-省略部分信息
则表明当前系统安装了 GDB 调试器。
对于尚未安装 GDB 的 Linux 发行版,安装方法通常有以下 2 种:
直接调用该操作系统内拥有的 GDB 安装包,使用包管理器进行安装。此安装方式的好处是速度快,但通常情况下安装的并非 GDB 的最新版本;
前往 GDB 官网下载源码包,在本机编译安装。此安装方式的好处是可以任意选择 GDB 的版本,但由于安装过程需要编译源码,因此安装速度较慢。
注意,不同的 Linux 发行版,管理包的工具也不同。根据维护团体(商业公司维护和社区组织维护)的不同,可以将众多 Linux 发行版分为 2 个系列,分别为 RedHat 系列和 Debian 系列。其中 RedHat 系列代表 Linux 发行版有 RedHat、CentOS、Fedora 等,使用 yum 作为包管理器;Debian 系列有 Debian、Ubuntu 等,使用 apt 作为包管理器。
快速安装GDB
对于 RedHat 系列的 Linux 发行版,通过在命令行窗口中执行sudo yum -y install gdb指令,即可实现 GDB 调试器的安装。这里以 CentOS 为例,执行该指令的过程为:
[root@bogon ~]# gdb -v
bash: gdb: command not found <--当前系统中没有GDB
[root@bogon ~]# sudo yum -y install gdb <--安装 GDB
Loaded plugins: fastestmirror, refresh-packagekit, security
Loading mirror speeds from cached hostfile
...... <-省略部分过程
Installed:
gdb.x86_64 0:7.2-92.el6
Complete!
[root@bogon ~]# gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6) <--安装成功
可以看到,GDB 安装成功。
对于 Debian 系列的 Linux 发行版,通过执行sudo apt -y install gdb指令,即可实现 GDB 的安装。感兴趣的读者可自行验证,这里不再过多赘述。
注意,使用 yum 或者 apt 更多情况需要借助网络下载所需使用的安装包,这也就意味着,如果读者所用系统没有网络环境,很有可能安装失败。
源码安装GDB
和使用 yum(apt)自动安装 GDB 不同,手动安装需提前到 GDB 官网下载相应的源码包,读者可直接点击 GDB源码包进行下载。值得一提的是,每个 GDB 版本都提供有 tar.gz 和 tar.xz 这 2 种格式的压缩包,这里以 tar.gz 格式为例教大家安装 GDB。
注意,在安装 GDB 之前,读者必须保证当前操作系统中有可以使用的编译器,比如最常用的 GCC 编译器(应同时支持 gcc 和 g++ 指令),有关 GCC 编译器的下载和安装,读者可阅读《GCC下载和安装》一文。另外,源码安装 GDB 需要用到 Makefile 相关的知识,读者可完全遵循以下步骤“照猫画虎”地安装 GDB。对 Makefile 感兴趣的读者,可前往《Makefile教程》做系统了解。
本节下载的 GDB 源码包为 gdb-9.2-tar.gz,接下来以 CentOS 系统为例(也同样适用于其它 Linux 发行版),给大家演示整个安装过程:
- 找到 gdb-9.2-tar.gz 文件,笔者将下载好的 gdb-9.2-tat.gz 放置在 /usr/local/src 目录下:
[root@bogon ~]# cd /usr/local/src
[root@bogon src]# ls
gdb-9.2.tar.gz
- 使用 tar 命令解压该文件,执行命令如下:
[root@bogon ~]# tar -zxvf gdb-9.2.tar.gz
--省略解压过程的输出结果
[root@bogon src]# ls
gdb-9.2 gdb-9.2.tar.gz
此步骤会得到 gdb-9.2.tar.gz 相应的解压文件 gdb-9.2 。
- 进入 gdb-9.2 目录文件,创建一个 gdb_build_9.2 目录并进入,为后续下载并放置安装 GDB 所需的依赖项做准备:
[root@bogon src]# cd gdb-9.2
[root@bogon gdb-9.2]# mkdir gdb-build-9.2
[root@bogon src]# cd gdb-build-9.2
- 在此基础上,继续执行如下指令:
[root@bogon gdb-build-9.2]# ../configure
...... <--省略众多输出
configure: creating ./config.status
config.status: creating Makefile
- 执行 make 指令编译整个 GDB 源码文件,此过程可能会花费很长时间,读者耐心等待即可:
[root@bogon gdb-build-9.2]# make
...... <-- 省略编译过程产生的输出结果
注意,如果编译过程中出现错误,极有可能是所用的 GCC 编译器版本过低导致的,可尝试升级 GCC 版本后再重新执行 make 命令。
- 确定整个编译过程没有出错之后,执行sudo make install指令(其中使用 sudo 指令是为了避免操作权限不够而导致安装失败),正式开始安装 GDB 调试器:
[root@bogon gdb-build-9.2]# sudo make install
...... <-- 省略输出结果
以上过程全部成功执行,则表示 GDB 安装成功。通过再次执行 gdb -v 指令,可验证其是否被成功安装。
[root@bogon gdb-build-9.2]# gdb -v
GNU gdb (GDB) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
...... <-- 省略部分输出
GDB调试C/C++程序
【该章节转载于https://blog.csdn.net/qq_35358125/article/details/106390171】
程序用GCC进行编译时要加上“-g”选项
##一.调用 GDB 调试器的4 种方法
1.直接使用 gdb 指令启动 GDB 调试器:由于事先未指定要调试的具体程序,因此需启动后借助 file 或者 exec-file 命令指定
1. [root@bogon ~]# gdb -q (gdb) file /tmp/demo/test
2.调试尚未执行的程序:输入GDB和要调试的可执行文件即可,如下所示。
1. gdb test
3.调试正在执行的程序
a.首先使用pidof查出进程PID号
1. `pidof test`
b.使用以下三种方法调用GDB
1) gdb attach PID
2) gdb test PID
3) gdb -p PID
注1:当调试完成后,如果想令当前程序进行执行,消除调试操作对它的影响,需手动将 GDB 调试器与程序分离,分离过程分为 2 步:
1. 执行 detach 指令,使 GDB 调试器和程序分离;
2. 执行 quit(或 q)指令,退出 GDB 调试。 注2:GDB 调试器成功连接到指定进程上时,程序执行会暂停
4.调试执行异常崩溃的程序
Linux 操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为 core 文件,Linux 系统所具备的这种功能又称为核心转储(core dump).
Linux 系统默认不开启 core dump 功能,可以执行ulimit -c指令查看系统是否开启此功能,如果 core file size(core 文件大小)对应的值为 0,表示当前系统未开启 core dump 功能,可以通过执行如下指令改变 core 文件的大小:
[root@bogon demo]# ulimit -a
core file size (blocks, -c) 0
......
[root@bogon demo]# ulimit -c unlimited #unlimited 表示不限制 core 文件的大小。
[root@bogon demo]# ulimit -a
core file size (blocks, -c) unlimited
......
对于 core 文件的调试,其调用 GDB 调试器的指令为:
gdb test core
对于 core 文件中记录的崩溃信息,可以使用 where、print、bt 等指令查看 gdb启动时可设置的参数
二.查看文件“l”(list)就可以查看所载入的文件
三.运行代码
run 命令除了可以启动程序的执行,还可以在任何时候重新启动程序,且加上行号即可从程序中指定行开始运行
四.传递参数的3 种方法:argv[] 字符串数组接收
1.启动 GDB,在指定目标调试程序的同时,使用 --args 选项指定需要传递给该程序的数据。
[root@bogon demo]# gdb --args test a.txt
2.GDB启动后,借助 set args 命令指定目标调试程序启动所需要的数据。
(gdb) set args a.txt
3.除此之外,还可以使用 run 或者 start 启动目标程序时,指定其所需要的数据。
(gdb) run a.txt
(gdb) start a.txt
五.断点
1.普通断点(break、tbreak、rbreak)
break的两种方法
(gdb) break location // b location
(gdb) break ... if cond // b .. if cond
a.第一种格式中,location 用于指定打断点的具体位置
b.第二种格式中,… 可以是表 1 中所有参数的值,用于指定打断点的具体位置;cond 为某个表达式。整体的含义为:每次程序执行到 …
位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。
tbreak 命令可以看到是 break 命令的另一个版本,使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。
rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点。tbreak 命令的使用语法格式为:
(gdb) tbreak regex
其中 regex 为一个正则表达式,程序中函数的函数名只要满足 regex 条件,tbreak
命令就会其内部的开头位置打断点。值得一提的是,tbreak 命令打的断点和 break 命令打断点的效果是一样的,会一直存在,不会自动消失。
2.观察断点(watch、rwatch、awatch)
(gdb) watch cond #conde 指的就是要监控的变量或表达式。所谓表达式,就是包含多个变量的式子,比如 a+b 就是一个表达式,其中 a、b 为变量。
(gdb) watch conde if cond
rwatch程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
awatch程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
注:
a. 当监控的变量(表达式)为局部变量(表达式)时,一旦局部变量(表达式)失效,则监控操作也随即失效;
b.监控指针变量时(例如 *p),则 watch *p 和 watch p 是有区别的,前者监控的是 p 所指数据的变化情况,而后者监控的是 p 指针本身有没有改变指向;
c.可监控数组中元素值的变化情况,对于 a[10] 这个数组,watch a 表示只要 a 数组中存储的数据发生改变,程序就会停止执行。
3.捕捉断点(catch、tcatch)
捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。
(gdb) catch event
(gdb) watch event if cond
注:
a. catch匹配过程需要借助 libstdc++ 库中的一些 SDT 探针,其需求GCC 编译器的版本最低为 4.8。但即便如此,catch 命令是否能正常发挥作用,还可能受到系统中其它因素的影响。
b. 当 catch 命令捕获到指定的 event 事件时,程序暂停执行的位置往往位于某个系统库(例如 libstdc++)中。这种情况下,通过执行 up 命令,即可返回发生 event 事件的源代码处。
c. catch 无法捕获以交互方式引发的异常。
tcatch 命令只监控一次,只有目标时间第一次触发时,tcath 命令才会捕获并使程序暂停,之后将失效。
info b [n] 查看断点设置情况。在GDB中可以设置多个断点。n 为某个断点的编号,可省略。
info watchpoints [n] 查看观察断点设置情况。在GDB中可以设置多个断点。
condition 命令的功能是:既可以为现有的普通断点、观察断点以及捕捉断点添加条件表达式,也可以对条件断点的条件表达式进行修改。语法格式如下:
(gdb) condition bnum expression
#参数 bnum 用于代指目标断点的编号;参数 expression 表示为断点添加或修改的条件表达式。
(gdb) condition bnum
以上 2 种语法格式中,第 1 种用于为 bnum 编号的断点添加或修改 expression 条件表达式;第 2 种用于删除 bnum 编号断点的条件表达式,使其变成普通的无条件断点。
ignore 命令可以使目标断点暂时失去作用
ignore bnum count #参数 bnum 为某个断点的编号;参数 count 用于指定该断点失效的次数。
clear 命令可以删除指定位置处的所有断点
(gdb) clear location #ocation 通常为某一行代码的行号或者某个具体的函数名。当 location
参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。
delete 命令(可以缩写为 d )通常用来删除所有断点,也可以删除指定编号的各类型断点
(gdb) delete [breakpoints] [num] #breakpoints 参数可有可无,num 参数为指定断点的编号,其可以是 delete 删除某一个断点,而非全部。
disable 命令可以禁用断点
(gdb) disable [breakpoints] [num…] #breakpoints 参数可有可无;num…
表示可以有多个参数,每个参数都为要禁用断点的编号。如果指定 num…,disable 命令会禁用指定编号的断点;反之若不设定
num…,则 disable 会禁用当前程序中所有的断点。enable [breakpoints] [num…] 激活用 num… 参数指定的多个断点,如果不设定
num…,表示激活所有禁用的断点 enable [breakpoints] once num… 临时激活以
num… 为编号的多个断点,但断点只能使用 1 次,之后会自动回到禁用状态 enable [breakpoints] count
num… 临时激活以 num… 为编号的多个断点,断点可以使用 count 次,之后进入禁用状态 enable
[breakpoints] delete num… 激活 num… 为编号的多个断点,但断点只能使用 1
次,之后会被永久删除。
六.查看变量值:
display 命令每次程序暂停执行时都会自动打印出目标变量或表达式的值
print 命令只能查看 1 次某个变量或表达式的值,其复杂语法为:
(gdb) print [options --] [/fmt] expr
options:表示该命令所支持的选项;
fmt:指定输出变量或表达式值时所采用的格式;
expr:指定要查看的变量或表达式。
注意,options 参数和 /fmt 或者 expr 之间,必须用–( 2 个 - 字符)分隔。
当 print 命令不指定任何 options 参数时,print 和 /fmt 之间不用添加空格
除了表达式本身外,GDB 调试器还支持使用@和::运算符
(gdb) p array[0]@2
(gdb) p 'main.c'::num
(gdb) p main::num
七.单步运行
n(next)在“n”后面加上行号即可从程序停止处运行指定行数 (不进入子函数)
s(step) 在“n”后面加上行号即可从程序停止处运行指定行数 (进入子函数)
u(until)快速运行完当前的循环体,并运行至循环体外停止。注意,until只有当执行至循环体尾部(最后一行代码)时,until 命令才会发生此作用,否则作用同next。
八.程序继续运行:c(continue)
九.退出GDB:q(quit)
十.调试运行环境相关命令
临时(退出 GDB 调试后会失效)修改 PATH 环境变量,此时就可以借助 path 指令。
##(gdb) path /temp/demo
finish 命令会执行函数到正常退出
return<返回值>命令是立即结束执行当前函数并返回
jump 命令的功能是直接跳到指定行继续执行程序
十一.堆栈相关命令
十二.多线程调试