Skip to content

LV077-gdb-catch

GDB 调试器支持在被调试程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点,其中普通断点用 break命令建立,观察断点用 watch 命令建立,这一节我们来看一下捕捉点断点的建立。

一、catch

1. 捕捉断点

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

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

2. 语法格式

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

shell
(gdb) catch event

其中,event 参数表示要监控的具体事件。对于使用 GDB 调试 C、C++程序,常用的 event 事件类型如表 1 所示。

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

当前 GDB 调试器对监控 C++ 程序中异常的支持还有待完善,使用 catch 命令时,有以下几点需要说明:

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

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

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

二、tcatch

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

三、使用示例

1. 测试程序

这里要用c++程序来测试,因为C 语言本身没有异常处理机制,就不是很好演示了。

c
#include <iostream>
using namespace std;
int main(int argc, const char *argv[]) {
    int num = 1;
    while (num <= 5) {
        try {
            throw 100;
        }
        catch (int e) {
            num++;
            cout << "next" << endl;
        }
    }
    cout << "over" << endl;
    return 0;
}

当前程序中通过 throw 手动抛出了 int 异常,此异常能够被 catch 成功捕获。假设我们使用 catch 命令监控:只要程序中引发 int 异常,程序就停止执行。我们在ubuntu中测试,所以直接用下面的命令编译:

shell
cd ~/workspace/c-learning/02-c-basic/21-debug
g++ 029-gdb-catch.cpp -g

2. 调试示例

2.1 catch throw int

shell
 sumu@virtual-machine:~/workspace/c-learning/02-c-basic/21-debug [main  +1 ~0 -0 !]
$ gdb a.out -q
Reading symbols from a.out...
(gdb) catch throw int  # <=== 1. 指定捕获 throw int 事件
Catchpoint 1 (throw)
(gdb) r                # <=== 2. 运行程序
Starting program: /home/sumu/workspace/c-learning/02-c-basic/21-debug/a.out 

Catchpoint 1 (exception thrown), 0x00007ffff7e7a662 in __cxa_throw ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6    # <=== 程序暂停执行
(gdb) up               # <=== 3. 回到源码
#1  0x000055555555528e in main (argc=1, argv=0x7fffffffce28) at 029-gdb-catch.cpp:7
7                   throw 100;
(gdb) c                # <=== 4. 继续执行程序
Continuing.
next

Catchpoint 1 (exception thrown), 0x00007ffff7e7a662 in __cxa_throw ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) up 
#1  0x000055555555528e in main (argc=1, argv=0x7fffffffce28) at 029-gdb-catch.cpp:7
7                   throw 100;
(gdb) info b
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      exception throw
        matching: int
        catchpoint already hit 2 times
(gdb)

catch 命令设置了一个捕获断点,该断点用于监控 throw int 事件,只要发生程序就会暂停执行。由此当程序执行时,其会暂停至 libstdc++ 库中的某个位置,借助 up 指令我们可以得知该异常发生在源代码文件中的位置。

Tips:up 是 GDB 中用于在**调用栈(call stack)**中向上移动的命令。栈帧(Stack Frame)是程序运行时,调用栈上为每一次函数调用所分配的一块独立内存区域。每一次函数调用都会创建一个新的栈帧,里面主要存放:函数参数、局部变量、返回地址以及保存的寄存器等。

当程序暂停时,GDB 会显示当前执行点的函数。使用 bt(backtrace)可以看到从最外层到最内层的所有函数调用帧,每一帧都有编号。up 的作用就是从当前帧,向调用者(外层函数)方向移动,让我们可以查看外层函数的局部变量和状态。

2.2 catch load xxx.so

以监控 libstdc++.so.6 为例,在 GDB 调试器中,通过执行如下指令,即可监控该动态库的加载:

shell
 sumu@virtual-machine:~/workspace/c-learning/02-c-basic/21-debug [main  +1 ~0 -0 !]
$ gdb a.out -q
Reading symbols from a.out...
(gdb) catch load libstdc++.so.6
Catchpoint 1 (load)
(gdb) r
Starting program: /home/sumu/workspace/c-learning/02-c-basic/21-debug/a.out 

Catchpoint 1
  Inferior loaded /lib/x86_64-linux-gnu/libstdc++.so.6
    /lib/x86_64-linux-gnu/libgcc_s.so.1
    /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/libm.so.6
dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, 
    auxv=<optimized out>) at rtld.c:2358
2358    rtld.c: 没有那个文件或目录.
(gdb) info b
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      load of library matching libstdc++.so.6
        catchpoint already hit 1 time
(gdb) bt
#0  dl_main (phdr=<optimized out>, phnum=<optimized out>, user_entry=<optimized out>, 
    auxv=<optimized out>) at rtld.c:2358
#1  0x00007ffff7febc4b in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffce20, 
    dl_main=dl_main@entry=0x7ffff7fd15e0 <dl_main>) at ../elf/dl-sysdep.c:252
#2  0x00007ffff7fd104c in _dl_start_final (arg=0x7fffffffce20) at rtld.c:449
#3  _dl_start (arg=0x7fffffffce20) at rtld.c:539
#4  0x00007ffff7fd0108 in _start () from /lib64/ld-linux-x86-64.so.2
#5  0x0000000000000001 in ?? ()
#6  0x00007fffffffd105 in ?? ()
#7  0x0000000000000000 in ?? ()
(gdb)