目录
前言:
这篇文章只介绍到日常够用的一些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调试教程