LV077-gdb-catch
GDB 调试器支持在被调试程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点,其中普通断点用 break命令建立,观察断点用 watch 命令建立,这一节我们来看一下捕捉点断点的建立。
一、catch
1. 捕捉断点
普通断点作用于程序中的某一行,当程序运行至此行时停止执行,观察断点作用于某一变量或表达式,当该变量(表达式)的值发生改变时,程序暂停。而捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。
Tips:用捕捉断点监控某一事件的发生,等同于在程序中该事件发生的位置打普通断点。
2. 语法格式
建立捕捉断点的方式是使用 catch 命令,其基本格式为:
(gdb) catch event其中,event 参数表示要监控的具体事件。对于使用 GDB 调试 C、C++程序,常用的 event 事件类型如表 1 所示。
| 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 语言本身没有异常处理机制,就不是很好演示了。
#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中测试,所以直接用下面的命令编译:
cd ~/workspace/c-learning/02-c-basic/21-debug
g++ 029-gdb-catch.cpp -g2. 调试示例
2.1 catch throw int
✓ 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 调试器中,通过执行如下指令,即可监控该动态库的加载:
✓ 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)