Linux学习——GDB调试

Linux——GDB调试

  • GDB简介
  • GDB下载和安装
  • GDB调试C/C++的过程
  • GDB run(r)命令:启动程序
  • GDB break(b):设置断点
  • GDB watch命令:监控变量值的变化
  • GDB catch命令:建立捕捉断点
  • GDB条件断点(condition命令)
  • GDB单步调试程序
  • GDB print和display命令:查看变量的值
  • GDB禁用和删除断点
  • GDB handle命令:信号处理
  • GDB frame和backtrace命令:查看栈信息
  • GDB编辑和搜索源码
  • GDB help命令:查看目标命令的具体用法

GDB简介

程序中的错误主要分为 2 类,分别为语法错误和逻辑错误:

程序中的语法错误几乎都可以由编译器诊断出来,很容易就能发现并解决;

逻辑错误指的是代码思路或者设计上的缺陷,程序出现逻辑错误的症状是:代码能够编译通过,没有语法错误,但是运行结果不对。
对于这类错误,只能靠我们自己去发现和纠正。

实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。
所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。
通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。

常用的调试器

调试器名称 特点
Remote Debugger Remote Debugger 是 VC/VS 自带的调试器,与整个IDE无缝衔接,使用非常方便。
WinDbg 大名鼎鼎的 Windows 下的调试器,它的功能甚至超越了 Remote Debugger,它还有一个命令行版本(cdb.exe),但是这个命令行版本的调试器指令比较复杂,不建议初学者使用。
LLDB XCode 自带的调试器,Mac OS X 下开发必备调试器。
GDB Linux 下使用最多的一款调试器,也有 Windows 的移植版。

GDB是什么
GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。
Windows 操作系统中,人们更习惯使用一些已经集成好的开发环境(IDE),如 VS、VC、Dev-C++ 等,它们的内部已经嵌套了相应的调试器。

借助 GDB 调试器可以实现以下几个功能:

程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;

可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),
即支持断点调试;

程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。

GDB下载和安装

在命令行窗口中执行 gdb -v 命令,判断当前 Linux 发行版是否安装有 GDB 。
在这里插入图片描述
如上所示,执行结果为“command not found”,表明当前系统中未安装 GDB 调试器。

对于尚未安装 GDB 的 Linux 发行版,安装方法通常有以下 2 种:

  1. 直接调用该操作系统内拥有的 GDB 安装包,使用包管理器进行安装。此安装方式的好处是速度快,但通常情况下安装的并非 GDB 的最新版本;

  2. 前往 GDB 官网下载源码包,在本机编译安装。此安装方式的好处是可以任意选择 GDB的版本,但由于安装过程需要编译源码,因此安装速度较慢。

GDB调试C/C++的过程

例:
一段能够正常编译运行的 C 语言程序,文件路径为 /tmp/demo/main.c 。

#include 
int main ()
{
    unsigned long long int n, sum;
    n = 1;
    sum = 0;
    while (n <= 100)
    {
        sum = sum + n;
        n = n + 1;
    }
    return 0;
} 

使用 gcc -g 选项编译源文件,即可生成满足 GDB 要求的可执行文件。

# gcc main.c -o main.exe -g

启动GDB调试器
在生成包含调试信息的 main.exe 可执行文件的基础上,启动 GDB 调试器的指令如下:

# gdb main.exe

该指令在启动 GDB 的同时,会打印出一堆免责条款。通过添加 --silent(或者 -q、–quiet)选项,可将比部分信息屏蔽掉:

#  gdb main.exe --silent

启动成功的标志就是最终输出的 (gdb)。
通过在 (gdb) 后面输入指令,即可调用 GDB 调试进行对应的调试工作。

GDB常用的调试指令

调试指令 作用
(gdb) break xxx (gdb) b xxx 在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置。
(gdb) run (gdb) r 执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue (gdb) c 当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束。
(gdb) next (gdb) n 令程序一行代码一行代码的执行。
(gdb) print xxx (gdb) p xxx 打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list (gdb) l 显示源程序代码的内容,包括各行代码所在的行号。
(gdb) quit (gdb) q 终止调试。

GDB run(r)命令:启动程序

run 和 start 指令都可以用来在 GDB 调试器中启动程序。
它们之间的区别是:

默认情况下,run 指令会一直执行程序,直到执行结束。
如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;

start 指令会执行程序至 main() 主函数的起始位置,
即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。

在进行 run 或者 start 指令启动目标程序之前,还可能需要做一些必要的准备工作,大致包括以下几个方面:

如果启动 GDB 调试器时未指定要调试的目标程序,或者由于各种原因 GDB 调试器并为找到所指定的目标程序,这种情况下就需要再次手动指定;

有些 C 或者 C++ 程序的执行,需要接收一些参数(程序中用 argc 和 argv[] 接收);

目标程序在执行过程中,可能需要临时设置 PATH 环境变量;

默认情况下,GDB 调试器将启动时所在的目录作为工作目录,但很多情况下,该目录并不符合要求,需要在启动程序手动为 GDB 调试器指定工作目录。

默认情况下,GDB 调试器启动程序后,会接收键盘临时输入的数据,并将执行结果会打印在屏幕上。
但 GDB 调试器允许对执行程序的输入和输出进行重定向,使其从文件或其它终端接收输入,或者将执行结果输出到文件或其它终端。

GDB 调试器指定的目标程序传递参数,常用的方法有 3 种:

1、启动 GDB 调试器时,可以在指定目标调试程序的同时,使用 --args 选项指定需要传递给该程序的数据。
2、GDB 调试器启动后,可以借助 set args 命令指定目标调试程序启动所需要的数据。
3、除此之外,还可以使用 run 或者 start 启动目标程序时,指定其所需要的数据。

只有将调试程序所需的运行环境搭建好后,才能使用 run 或者 start 命令开始调试。

如下是一个完整的实例,演示了 GDB 调试 mian.exe 之前所做的准备工作:

[root@yan demo]# pwd   <--显示当前工作路径
/tmp/demo
[root@yan demo]# ls       <-- 显示当前路径下的文件
a.txt  main.c  main.exe
[root@yan demo]# cd ~  <-- 进入 home 目录
[root@yan ~]# gdb -q      <-- 开启 GDB 调试器
(gdb) cd /tmp/demo            <-- 修改 GDB 调试器的工作目录
Working directory /tmp/demo.
(gdb) file main.exe               <-- 指定要调试的目标文件
Reading symbols from main.exe...
(gdb) set args a.txt               <-- 指定传递的数据
(gdb) run                               <-- 运行程序
Starting program: /tmp/demo/main.exe a.txt
file open true[Inferior 1 (process 43065) exited normally]

GDB break(b):设置断点

所谓断点(BreakPoint),读者可以理解为障碍物,人遇到障碍物不能行走,程序遇到断点就暂停执行。
在 GDB 调试器中对 C、C++ 程序打断点,最常用的就是 break 命令,有些场景中还会用到 tbreak 或者 rbreak 命令。

GDB break命令

break 命令(可以用 b 代替)常用的语法格式有以下 2 种。

1、(gdb) break location     // b location
2、(gdb) break ... if cond   // b .. if cond

第一种格式:
location 参数的表示方式

location的值 含义
linenum linenum 是一个整数,表示要打断点处代码的行号。要知道,程序中各行代码都有对应的行号,可通过执行 l(小写的 L)命令看到。
filename:linenum filename 表示源程序文件名;linenum 为整数,表示具体行数。整体的意思是在指令文件 filename 中的第 linenum 行打断点。
+ offset - offset offset 为整数(假设值为 2),+offset 表示以当前程序暂停位置(例如第 4 行)为准,向后数 offset 行处(第 6 行)打断点;-offset 表示以当前程序暂停位置为准,向前数 offset 行处(第 2 行)打断点。
function function 表示程序中包含的函数的函数名,即 break 命令会在该函数内部的开头位置打断点,程序会执行到该函数第一行代码处暂停。
filename:function filename 表示远程文件名;function 表示程序中函数的函数名。整体的意思是在指定文件 filename 中 function 函数的开头位置打断点。

第二种格式:可以是上表中所有参数的值,用于指定打断点的具体位置。
cond 为某个表达式。整体的含义为:每次程序执行到 … 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。

GDB tbreak命令

使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。
tbreak 命令的使用格式和 break 完全相同,有以下 2 种:

1、(gdb) tbreak location
2、(gdb) tbreak ... if cond

GDB rbreak 命令

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

tbreak 命令的使用语法格式为:

(gdb) tbreak regex

其中 regex 为一个正则表达式,程序中函数的函数名只要满足 regex 条件,tbreak 命令就会其内部的开头位置打断点。值得一提的是,tbreak 命令打的断点和 break 命令打断点的效果是一样的,会一直存在,不会自动消失。

GDB watch命令:监控变量值的变化

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

对于监控 C、C++ 程序中某变量或表达式的值是否发生改变,watch 命令的语法非常简单,如下所示:

(gdb) watch cond

conde 指的就是要监控的变量或表达式。

和 watch 命令功能相似的,还有 rwatch 和 awatch 命令。其中:

rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。

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

如果我们想查看当前建立的观察点的数量,借助如下指令即可:

(gdb) info watchpoints

对于使用 watch(rwatch、awatch)命令监控 C、C++ 程序中变量或者表达式的值,有以下几点需要注意:

当监控的变量(表达式)为局部变量(表达式)时,一旦局部变量(表达式)失效,则监控操作也随即失效;

如果监控的是一个指针变量(例如 *p),则 watch *p 和 watch p 是有区别的,
前者监控的是 p 所指数据的变化情况,而后者监控的是 p 指针本身有没有改变指向;

这 3 个监控命令还可以用于监控数组中元素值的变化情况,
例如对于 a[10] 这个数组,watch a 表示只要 a 数组中存储的数据发生改变,程序就会停止执行。

watch命令的实现原理

watch 命令实现监控机制的方式有 2 种,一种是为目标变量(表达式)设置硬件观察点,另一种是为目标变量(表达式)设置软件观察点。

所谓软件观点(software watchpoint),即用 watch 命令监控目标变量(表达式)后,GDB 调试器会以单步执行的方式运行程序,并且每行代码执行完毕后,都会检测该目标变量(表达式)的值是否发生改变,如果改变则程序执行停止。

所谓硬件观察点(Hardware watchpoint),和前者最大的不同是,它在实现监控机制的同时不影响程序的执行效率。简单的理解,系统会为 GDB 调试器提供少量的寄存器(例如 32 位的 Intel x86 处理器提供有 4 个调试寄存器),每个寄存器都可以作为一个观察点协助 GDB 完成监控任务。

注意,awatch 和 rwatch 命令只能设置硬件观察点,如果系统不支持或者借助如上命令禁用,则 GDB 调试器会打印如下信息:

Expression cannot be implemented with read/access watchpoint.

GDB catch命令:建立捕捉断点

使用 catch 命令建立捕捉断点。

捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。

用捕捉断点监控某一事件的发生,等同于在程序中该事件发生的位置打普通断点。

建立捕捉断点的方式很简单,就是使用 catch 命令,其基本格式为:

(gdb) catch event

其中,event 参数表示要监控的具体事件。

常见的 event 事件

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

使用 catch 命令时,有以下几点需要说明:

  1. 对于使用 catch 监控指定的 event 事件,其匹配过程需要借助 libstdc++ 库中的一些 SDT
    探针,而这些探针最早出现在 GCC 4.8 版本中。也就是说,想使用 catch 监控指定类型的 event 事件,系统中 GCC
    编译器的版本最低为 4.8,但即便如此,catch 命令是否能正常发挥作用,还可能受到系统中其它因素的影响。

  2. 当 catch 命令捕获到指定的 event 事件时,程序暂停执行的位置往往位于某个系统库(例如
    libstdc++)中。这种情况下,通过执行 up 命令,即可返回发生 event 事件的源代码处。

  3. catch 无法捕获以交互方式引发的异常。

catch 命令也有另一个版本,即 tcatch 命令。tcatch 命令和 catch 命令的用法完全相同,唯一不同之处在于,对于目标事件,catch 命令的监控是永久的,而 tcatch 命令只监控一次,也就是说,只有目标时间第一次触发时,tcath 命令才会捕获并使程序暂停,之后将失效。

GDB条件断点(condition命令)

对于普通断点的建立,可以使用如下格式的 break 命令:

(gdb) break ... if cond

... 参数用于指定生成断点的具体位置;
cond 参数用于代指某个表达式。
通过此方式建立的普通断点,只有当表达式 cond 成立(值为 True)时,才会发挥它的作用;
反之,断点并不会使程序停止执行。

以某个表达式的是否成立作为条件,从而决定自身是否生效的断点,又称为条件断点。

创建普通条件断点的方式,也同样适用于观察条件断点。通过执行如下命令,即可直接生成一个观察条件断点:

(gdb) watch expr if cond

参数 expr 表示要观察的变量或表达式;参数 cond 用于代指某个表达式。

捕捉条件断点无法直接生成,需要借助 condition 命令为现有捕捉断点增添一个 cond 表达式,才能使其变成条件断点。

GDB condition命令

condition 命令的功能是:既可以为现有的普通断点、观察断点以及捕捉断点添加条件表达式,也可以对条件断点的条件表达式进行修改。

condition 命令没有缩写形式,使用方法很简单,语法格式如下:

(gdb) condition bnum expression
(gdb) condition bnum

参数 bnum 用于代指目标断点的编号;参数 expression 表示为断点添加或修改的条件表达式。

第 1 种用于为 bnum 编号的断点添加或修改 expression 条件表达式;第 2 种用于删除 bnum 编号断点的条件表达式,使其变成普通的无条件断点。

GDB ignore命令

ignore 命令也可以使一个断点成为条件断点,但这里的“条件”并非自定义的表达式,而仅为一个整数,它用来表示该断点失效的次数。也就会说,ignore 命令可以使目标断点暂时失去作用,当断点失效的次数超过指定次数时,断点的功能会自动恢复。

ignore 命令也没有缩写形式,其语法格式如下:

ignore bnum count

参数 bnum 为某个断点的编号;参数 count 用于指定该断点失效的次数。

GDB单步调试程序

借助 next 命令可以控制 GDB 单步执行程序。

所谓单步调试,就是通过一行一行的执行程序,观察整个程序的执行流程,进而尝试发现一些存在的异常或者 Bug。

GDB 调试器共提供了 3 种可实现单步调试程序的方法,即使用 next、step 和 until 命令。

GDB next 命令

next 是最常用来进行单步调试的命令,其最大的特点是当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完。
也就是说,对于调用的函数来说,next 命令只会将其视作一行代码。

next 命令可以缩写为 n 命令,使用方法也很简单,语法格式如下:

(gdb) next count

参数 count 表示单步执行多少行代码,默认为 1 行。

GDB step命令

step 命令和 next 命令的功能相同,都是单步执行程序。
不同之处在于,当 step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。

step 命令可以缩写为 s 命令,用法和 next 命令相同,语法格式如下:

(gdb) step count

参数 count 表示一次执行的行数,默认为 1 行。

GDB until命令

until 命令可以简写为 u 命令,有 2 种语法格式,如下所示:

1、(gdb) until
2、(gdb) until location

其中,参数 location 为某一行代码的行号。
不带参数的 until 命令,可以使 GDB 调试器快速运行完当前的循环体,并运行至循环体外停止。

注意,until 命令并非任何情况下都会发挥这个作用,只有当执行至循环体尾部(最后一行代码)时,until 命令才会发生此作用;
反之,until 命令和 next 命令的功能一样,只是单步执行程序。

until 命令还可以后跟某行代码的行号,以指示 GDB 调试器直接执行至指定位置后停止。

GDB print和display命令:查看变量的值

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

GDB print命令

print 命令,它的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值。

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

(gdb) print num
(gdb) p num

其中,参数 num 用来代指要查看或者修改的目标变量或者表达式。

GDB display命令

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

display 命令没有缩写形式,常用的语法格式如下 2 种:

(gdb) display expr
(gdb) display/fmt expr

其中,expr 表示要查看的目标变量或表达式;参数 fmt 用于指定输出变量或表达式的格式,表 1 罗列了常用的一些 fmt 参数。

/fmt 常用的值

/fmt 功能
/x 以十六进制的形式打印出整数。
/d 以有符号、十进制的形式打印出整数。
/u 以无符号、十进制的形式打印出整数。
/o 以八进制的形式打印出整数。
/t 以二进制的形式打印出整数。
/f 以浮点数的形式打印变量或表达式的值。
/c 以字符形式打印变量或表达式的值。

注意,display 命令和 /fmt 之间不要留有空格。以 /x 为例,应写为 (gdb)display/x expr。

GDB禁用和删除断点

禁用和删除断点,

常用的方式有 2 种:

使用 quit 命令退出调试,然后重新对目标程序启动调试,此方法会将消除上一次调试操作中建立的所有断点;

使用专门删除或禁用断点的命令,既可以删除某一个断点,也可以删除全部断点。

查看当前已建好的断点

对于当前调试环境中已经建好且尚未删除的断点,可以通过以下 2 种方式查看它们。

  1. 借助如下指令,可以查看当前调试环境中存在的所有断点,包括普通断点、观察断点以及捕捉断点:

     (gdb) info breakpoint [n]
     (gdb) info break [n]
    

    参数 n 作为可选参数,为某个断点的编号,表示查看指定断点而非全部断点。

  2. 除此之外,对于调试环境中已经建好且未删除的观察断点,也可以使用 info watchpoint 命令进行查看,语法格式如下:

     (gdb) info watchpoint [n]
    

    n 为可选参数,为某个观察断点的编号,功能是只查看该编号的观察断点的信息,而不是全部的观察断点。

GDB删除断点

无论是普通断点、观察断点还是捕捉断点,都可以使用 clear 或者 delete 命令进行删除。

  1. clear命令
    clear 命令可以删除指定位置处的所有断点,常用的语法格式如下所示:

    (gdb) clear location
    

    参数 location 通常为某一行代码的行号或者某个具体的函数名。当 location 参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。

  2. delete 命令
    delete 命令(可以缩写为 d )通常用来删除所有断点,也可以删除指定编号的各类型断点,语法格式如下:

    delete [breakpoints] [num]
    

    其中,breakpoints 参数可有可无,num 参数为指定断点的编号,其可以是 delete 删除某一个断点,而非全部。

如果不指定 num 参数,则 delete 命令会删除当前程序中存在的所有断点。

GDB禁用断点

所谓禁用,就是使目标断点暂时失去作用,必要时可以再将其激活,恢复断点原有的功能。

禁用断点可以使用 disable 命令,语法格式如下:

disable [breakpoints] [num...]

breakpoints 参数可有可无;num… 表示可以有多个参数,每个参数都为要禁用断点的编号。如果指定 num…,disable 命令会禁用指定编号的断点;反之若不设定 num…,则 disable 会禁用当前程序中所有的断点。

对于禁用的断点,可以使用 enable 命令激活,该命令的语法格式有多种,分别对应有不同的功能:

语法格式 功能
enable [breakpoints] [num…] 激活用 num… 参数指定的多个断点,如果不设定 num…,表示激活所有禁用的断点
enable [breakpoints] once num… 临时激活以 num… 为编号的多个断点,但断点只能使用 1 次,之后会自动回到禁用状态
enable [breakpoints] count num… 临时激活以 num… 为编号的多个断点,断点可以使用 count 次,之后进入禁用状态
enable [breakpoints] delete num… 激活 num… 为编号的多个断点,但断点只能使用 1 次,之后会被永久删除。

其中,breakpoints 参数可有可无;num… 表示可以提供多个断点的编号,enable 命令可以同时激活多个断点。

GDB handle命令:信号处理

GDB 调试器可以自动捕获 C、C++ 程序中出现的信号,并根据事先约定好的方式处理它。

Linux 系统中已经事先定义好了诸多中信号,我们可以通过执行如下命令查看:

[root@yan demo]# kill -l
1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL  5) SIGTRAP
6) SIGABRT  7) SIGBUS  8) SIGFPE  9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
.......   <-- 省略部分输出

其中,每个信号代表着不同的含义,以 SIGINT 信号为例,它表示程序停止执行,该信号可以通过按 Ctrl+c组合键发出。换句话说,对于正在执行的程序,通过按Ctrl+c键向程序发出 SIGINT 信号,可以使程序停止执行。

GDB 调试器提供了info handles指令,用于查看 GDB 可以处理的信号种类,以及各个信号的具体处理方式。

GDB handle命令

handle 命令的语法格式如下:

(gdb) handle signal mode

其中,signal 参数表示要设定的目标信号,它通常为某个信号的全名(SIGINT)或者简称(去除‘SIG’后的部分,如 INT);如果要指定所有信号,可以用 all 表示。

mode 参数用于明确 GDB 处理该目标信息的方式,其值可以是如下几个:

nostop:当信号发生时,GDB 不会暂停程序,其可以继续执行,但会打印出一条提示信息,告诉我们信号已经发生;
stop:当信号发生时,GDB 会暂停程序执行。
noprint:当信号发生时,GDB 不会打印出任何提示信息;
print:当信号发生时,GDB 会打印出必要的提示信息;
nopass(或者 ignore):GDB 捕获目标信号的同时,不允许程序自行处理该信号;
pass(或者 noignore):GDB 调试在捕获目标信号的同时,也允许程序自动处理该信号。

GDB frame和backtrace命令:查看栈信息

程序中每个被调用的函数在执行时,都会生成一些必要的信息,包括:

函数调用发生在程序中的具体位置;
调用函数时的参数;
函数体内部各局部变量的值等等。

这些信息会集中存储在一块称为“栈帧”的内存空间中。也就是说,程序执行时调用了多少个函数,就会相应产生多少个栈帧,其中每个栈帧自函数调用时生成,函数调用结束后自动销毁。

注意,这些栈帧所在的位置也不是随意的,它们集中位于一个大的内存区域里,我们通常将其称为栈区或者栈。

这也就意味着,当程序因某种异常暂停执行时,如果其发生在某个函数内部,我们可以尝试借助该函数对应栈帧中记录的信息,找到程序发生异常的原因。

GDB 调试器为了方便用户在调试程序时查看某个栈帧中记录的信息,提供了 frame 和 backtrace 命令。

GDB frame命令

main() 主函数对应的栈帧,又称为初始帧或者最外层的帧。

frame 命令的常用形式有 2 个:

  1. 根据栈帧编号或者栈帧地址,选定要查看的栈帧,语法格式如下:

    (gdb) frame spec
    该命令可以将 spec 参数指定的栈帧选定为当前栈帧。spec 参数的值,常用的指定方法有 3 种:

     通过栈帧的编号指定。0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
     
     借助栈帧的地址指定。栈帧地址可以通过 info frame 命令(后续会讲)打印出的信息中看到;
     
     通过函数的函数名指定。注意,如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。
    

对于选定一个栈帧作为当前栈帧,GDB 调试器还提供有 up 和 down 两个命令。其中,up 命令的语法格式为:

(gdb) up n

其中 n 为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定 m+n 为编号的栈帧作为新的当前栈帧。

相对地,down 命令的语法格式为:

(gdb) down n

其中 n 为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定 m-n 为编号的栈帧作为新的当前栈帧。

  1. 借助如下命令,我们可以查看当前栈帧中存储的信息:

    (gdb) info frame
    该命令会依次打印出当前栈帧的如下信息:

     当前栈帧的编号,以及栈帧的地址;
     当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
     当前函数的调用者,对应的栈帧的地址;
     编写此栈帧所用的编程语言;
     函数参数的存储地址以及值;
     函数中局部变量的存储地址;
     栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用 eip 表示)、堆栈基指针寄存器(64位环境用 rbp 表示,32位环境用 ebp 表示)等。
    

还可以使用info args命令查看当前函数各个参数的值;使用info locals命令查看当前函数中各局部变量的值。

GDB backtrace命令

backtrace 命令用于打印当前调试环境中所有栈帧的信息,常用的语法格式如下:

(gdb) backtrace [-full] [n]

其中,用 [ ] 括起来的参数为可选项,它们的含义分别为:

n:一个整数值,当为正整数时,表示打印最里层的 n 个栈帧的信息;n 为负整数时,那么表示打印最外层 n 个栈帧的信息;
-full:打印栈帧信息的同时,打印出局部变量的值。

注意,当调试多线程程序时,该命令仅用于打印当前线程中所有栈帧的信息。如果想要打印所有线程的栈帧信息,应执行thread apply all backtrace命令。

GDB编辑和搜索源码

GDB edit命令:编辑文件

在 GDB 中编辑源文件中使用 edit 命令,该命令的语法格式如下:

(gdb) edit [location]
(gdb) edit [filename] : [location]

location 表示程序中的位置。这个命令表示激活文件的指定位置,然后进行编辑。

注意,上面修改编辑器的方法只是临时生效,当退出 shell 终端后配置就会被还原,下次使用的时候还要再次使用这条命令。想要永久的实现配置,需要去修改配置文件,具体做法是:修改当前 home 目录下的”~/.bashrc”文件,在文件的最后添加上述的命令就可以实现文件的永久配置(修改的是当前用户的配置文件,一般不建议修改系统的配置文件,出现问题不容易恢复)。配置完成重启 shell 终端,就可以完成配置。

GDB search命令:搜索文件

search 命令的语法格式为:

search 
reverse-search 

第一项命令格式表示从当前行的开始向前搜索,后一项表示从当前行开始向后搜索。其中 regexp 就是正则表达式,正则表达式描述了一种字符串匹配的模式,可以用来检查一个串中是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串。很多的编程语言都支持使用正则表达式。

使用命令时可能会下面的情况:

(gdb) search func
Expression not found

表示搜索的范围没有出现要寻找的字符串或者定位到了代码行的末尾。

GDB help命令:查看目标命令的具体用法

GDB 提供了 help 命令,它可以帮用户打印出目标命令的功能和具体用法。

为了方便用户能够快速地从众多 GDB 命令中查找到目标命令,help 命令根据不同 GDB 命令的功能对它们做了分类:

(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

GDB自动补全命令

除了 help 命令辅助用户使用 GDB 调试器之外,GDB 还支持命令的自动补全。
所谓自动补全,即在 (gdb) 右侧输入 GDB 命令时,对于特别长的命令,我们只需要输入命令的前几个字符,然后按下 Tab 键,GDB 就会自动帮我们补全整个命令。

举个例子:

(gdb) info bre   

如上所示,当我们按下 Tab 键时,GDB 会自行将 bre 补全为 break。

当我们输入完成的 GDB 命令后,通过双击 Tab 键,GDB 会给我们罗列出该命令可用的所有参数。比如:

(gdb) catch 
assert     exception  fork       load       signal     throw      vfork
catch      exec       handlers   rethrow    syscall    unload

学习参考资料:

http://c.biancheng.net/gdb/

你可能感兴趣的:(Linux,1024程序员节)