前言:

这篇文章只介绍到日常够用的一些gdb命令,如果想了解更多可以直接翻看文章末尾的链接。

gdb调试

gdb是什么?

gdb全称“GNU symbolic debugger”,诞生于GUN计划(跟之前讲的gcc是表兄弟关系),是linux下常用的程序调试器

当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。

Windows 操作系统中,人们更习惯使用一些已经集成好的开发环境(IDE),如 VS、VC、Dev-C++ 等,它们的内部已经嵌套了相应的调试器。

gdb有什么用

总的来说,借助 GDB 调试器可以实现以下几个功能:

程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态;
程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。

如何启动gdb

生成gdb用的可执行文件

gdb调试器需要特殊的可执行文件。可以在编译时用gcc -c main.c -g命令生成debug版本。

或者用gcc -o main main.c -g生成也可以。

启动gdb

然后用命令gdb main.exe启动gdb调试器。
会出现以下信息:

GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.

(gdb)

屏蔽免责条款

这个时候会打印出一堆免责条款。可以通过添加--slient | -q | --quiet来屏蔽掉这些信息。

gdb main.exe --silent

会出现以下信息:

Reading symbols from main.exe…(no debugging symbols found)…done.
(gdb)

gdb常用调试指令

调试指令 作用
b(break) 行号 在源代码指定的某一行设置断点,其中行号用于指定具体打断点的位置。
r(run) 执行被调试的程序,其会自动在第一个断点处暂停执行。
n(next) 令程序一行代码一行代码的执行。
p(print) 变量名 打印指定变量的值
l(list) 显示源程序代码的内容,包括各行代码所在的行号。
q(quit) 终止调试。

启动程序指令

根据不同场景的需要,GDB 调试器提供了多种方式来启动目标程序,其中最常用的就是 run 指令,其次为 start 指令。也就是说,run 和 start 指令都可以用来在 GDB 调试器中启动程序
但是他们还是有区别的!

run: 默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;
start: start 指令会执行程序至 main() 主函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。

问:是不是启用gdb就可以直接使用run或者start呢?

我们来看一个例子:

# gdb

以下是打印的信息:

GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.
… <-- 省略部分输出信息
Type “apropos word” to search for commands related to “word”.
(gdb)

我们都没有指定目标文件,拿什么来进行调试!

在启动对文件的调试之前,不仅需要目标文件,还需要一些其他的必要的准备工作

  • 如果启动 GDB 调试器时<mark>未指定要调试的目标程序</mark>,或者由于各种原因 GDB 调试器并为找到所指定的目标程序,这种情况下就需要再次手动指定;
  • 有些 C 或者 C++ 程序的执行,需要<mark>接收一些参数</mark>(程序中用 argc 和 argv[] 接收);
    目标程序在执行过程中,可能需要临时设置 PATH 环境变量;
  • 默认情况下,GDB 调试器将启动时所在的目录作为工作目录,但很多情况下,该目录并不符合要求,需要在启动程序手动为 GDB 调试器<mark>指定工作目录</mark>。
  • 默认情况下,GDB 调试器启动程序后,会接收键盘临时输入的数据,并将执行结果会打印在屏幕上。但 GDB 调试器允许对执行程序的输入和输出进行重定向,使其从文件或其它终端接收输入,或者将执行结果输出到文件或其它终端。
如何指定目标文件程序

首先对于已经启动的gdb调试器,我们可以通过l验证其是否找到目标程序文件。

(gdb) l

如果没有的话会打印一下信息:

No symbol table is loaded. Use the “file” command.

这个时候我们就需要手动去指定要调试的目标程序。

(gdb) file 路径/目标文件
如何传递文件

总的来说,为 GDB 调试器指定的目标程序传递参数,常用的方法有 3 种。

  • 启动 GDB 调试器时,可以在指定目标调试程序的同时,使用 --args 选项指定需要传递给该程序的数据。
# gdb --args main.exe a.txt
  • GDB 调试器启动后,可以借助 set args 命令指定目标调试程序启动所需要的数据。
(gdb) set args a.txt
  • 除此之外,还可以使用 run 或者 start 启动目标程序时,指定其所需要的数据。
(gdb) run a.txt
(gdb) start a.txt
如何切换工作目录

默认情况下,GDB 调试器的工作目录为启动时所使用的目录。例如在 ~ 路径下启动的 GDB 调试器,其工作目录就为 ~(当前用户的 home 目录)。当然,GDB 调试器提供有修改工作目录的指令,即 cd 指令。

(gdb) cd /tmp/demo
如何改变PATH环境变量
(gdb) path 路径

注意,此修改方式只是临时的,退出 GDB 调试后会失效。

设置断点

默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。 要想观察程序运行的内部细节,可以借助 GDB 调试器在程序中的某个地方设置断点,这样当程序执行到这个地方时就会停下来。

break命令设置断点

gdb中的break命令就是用来设置断点的,主要有两种格式。

(gdb) break location      // b 位置
(gdb) break ... if cond   // b 表达式
  • 在第一种格式中,location 用于指定打断点的具***置,其表示方式有多种。
location 的值 含 义
linenum linenum 是一个整数,表示要打断点处代码的行号。
filename:linenum filename 表示源程序文件名;linenum 为整数,表示具体行数。整体的意思是在指令文件 filename 中的第 linenum 行打断点。
+ offset offset 为整数,+offset 表示以当前程序暂停位置为准,向后数 offset 行处打断点
- offset offset 为整数,-offset 表示以当前程序暂停位置为准,向前数 offset 行处打断点
function function 表示程序中包含的函数的函数名,即 break 命令会在该函数内部的开头位置打断点,程序会执行到该函数第一行代码处暂停。
filename:function filename 表示远程文件名;function 表示程序中函数的函数名。整体的意思是在指定文件 filename 中 function 函数的开头位置打断点。
  • 第二种格式中,cond 为某个表达式。整体的含义为:每次程序执行到 … 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。
(gdb) b 7 if num>10     // 如果 num>10 在第 7 行打断点
tbreak命令设置断点

tbreak 命令可以看到是 break 命令的另一个版本,tbreak 和 break 命令的用法和功能都非常相似,唯一的不同在于,使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。

其他都和break的用法一样,这里就不多介绍了。

rbreak命令设置断点

break 和 tbreak 命令不同,rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点。

(gdb) tbreak 表达式		//例如(gdb) rbreak rb_*       <--匹配所有以 rb_ 开头的函数

监控变量值指令

在这里,主要介绍的就是watch命令,其用来监控某个变量或者表达式的值,通过值的变化情况判断程序的执行过程是否存在异常或者 Bug。

GDB 调试器支持在程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点。其中 break 命令打的就是普通断点,而 watch 命令打的为观察断点。

(gdb) watch cond 	//conde 指的就是要监控的变量或表达式

watch 命令的功能是:只有当被监控变量(表达式)的值发生改变,程序才会停止运行。

建立捕捉断点指令

刚刚我们提到了gdb调试中三种断点中的两种。现在就来说说最后一种。

首先我们要认识三种断点的区别

普通断点作用于程序中的某一行,当程序运行至此行时停止执行,观察断点作用于某一变量或表达式,当该变量(表达式)的值发生改变时,程序暂停。而捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。

(gdb) catch event		//event 参数表示要监控的具体事件
event事件 含义
throw [exception] 当程序中抛出 exception 指定类型异常时,程序停止执行。如果不指定异常类型(即省略 exception),则表示只要程序发生异常,程序就停止执行。
catch [exception] 当程序中捕获到 exception 异常时,程序停止执行。exception 参数也可以省略,表示无论程序中捕获到哪种异常,程序都暂停执行。
load [regexp] regexp 表示目标动态库的名称,load 命令表示当 regexp 动态库加载时程序停止执行;regexp 参数也可以省略,此时只要程序中某一动态库被加载或卸载,程序就会暂停执行。
unload [regexp] unload 命令表示当 regexp 动态库被卸载时,程序暂停执行。

查看变量指令

对于在调试期间查看某个变量或表达式的值,GDB 调试器提供有 2 种方法,即使用 print 命令或者 display 命令。

print指令

print 命令可以缩写为 p,最常用的语法格式(基本够用)如下所示:

(gdb) print num		//参数 num 用来代指要查看或者修改的目标变量或者表达式
(gdb) p num

其完整语法如下:

(gdb) print [options --] [/fmt] expr

各个参数的意义:

options:表示该命令所支持的选项,这些选项可以控制 print 命令输出指定内容的变量或者表达式的值;
fmt:指定输出变量或表达式值时所采用的格式;
expr:指定要查看的变量或表达式。

options参数详情:

display指令

和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。

(gdb) display expr			//expr 表示要查看的目标变量或表达式
(gdb) display/fmt expr		//参数 fmt 用于指定输出变量或表达式的格式

常用fmt参数:

参考文献

[1] GDB调试教程