Linux基础知识(15)- GDB 调试器(三)| 实时监控变量值、捕捉断点和条件断点
本文在 “Linux基础知识(14)- GDB 调试器(二)| 普通断点、单步调试和查看变量” 的基础上,继续演示实时监控变量值、捕捉断点和条件断点。
1. 实时监控变量值
使用 GDB 调试程序的过程中,借助观察断点可以监控程序中某个变量或者表达式的值,只要发生改变,程序就会停止执行。相比普通断点,观察断点不需要我们预测变量(表达式)值发生改变的具体位置。
所谓表达式,就是包含多个变量的式子,比如 a+b 就是一个表达式,其中 a、b 为变量。
对于监控 C、C++ 程序中某变量或表达式的值是否发生改变,watch 命令的语法非常简单,如下所示:
(gdb) watch num
其中,num 指的就是要监控的变量或表达式。
通过借助 watch 命令监控 num 的值,后续只要 num 的值发生改变,程序都会停止。watch 命令的功能是:只有当被监控变量(表达式)的值发生改变,程序才会停止运行。
和 watch 命令功能相似的,还有 rwatch 和 awatch 命令。其中:
rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
如下指令查看当前建立的观察点的数量:
(gdb) info watchpoints
1) 创建 C 程序
$ cd ~/
$ vim test3.c
#include <stdio.h> int main(int argc, char* argv[]) { int num = 1; while (num<=100) { num *= 2; } printf("%d\n", num); return 0; }
2)编译并设置调试信息
$ gcc -g test3.c -o test3
$ ./test3
128
3) 使用 watch 命令
# GDB 启动 test3 程序
$ gdb -q test3
Reading symbols from test3 ... (gdb) l 1 #include <stdio.h> 2 3 int main(int argc, char* argv[]) { 4 5 int num = 1; 6 while (num<=100) { 7 num *= 2; 8 } 9 printf("%d\n", num); 10 return 0; (gdb) 11 } 12 (gdb) b 5 Breakpoint 1 at 0x115c: file test3.c, line 5. (gdb) r Starting program: /home/xxx/test3 Breakpoint 1, main (argc=1, argv=0x7fffffffe228) at test3.c:5 5 int num = 1; (gdb) watch num Hardware watchpoint 2: num (gdb) c Continuing. Hardware watchpoint 2: num Old value = 0 New value = 1 main (argc=1, argv=0x7fffffffe228) at test3.c:6 6 while (num<=100) { (gdb) c Continuing. Hardware watchpoint 2: num Old value = 1 New value = 2 main (argc=1, argv=0x7fffffffe228) at test3.c:6 6 while (num<=100) {
2. 建立捕捉断点
GDB 调试器支持在被调试程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点,其中普通断点用 break 命令建立,观察断点用 watch 命令建立,本节将讲解如何使用 catch 命令建立捕捉断点。
普通断点作用于程序中的某一行,当程序运行至此行时停止执行,观察断点作用于某一变量或表达式,当该变量(表达式)的值发生改变时,程序暂停。而捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。
用捕捉断点监控某一事件的发生,等同于在程序中该事件发生的位置打普通断点。
建立捕捉断点的方式很简单,就是使用 catch 命令,其基本格式为:
(gdb) catch event
其中,event 参数表示要监控的具体事件。对于使用 GDB 调试 C、C++ 程序,常用的 event 事件类型如下表所示。
event 事件 | 描述 |
throw [exception] | 当程序中抛出 exception 指定类型异常时,程序停止执行。如果不指定异常类型(即省略 exception),则表示只要程序发生异常,程序就停止执行。 |
catch [exception] | 当程序中捕获到 exception 异常时,程序停止执行。exception 参数也可以省略,表示无论程序中捕获到哪种异常,程序都暂停执行。 |
load/unload [regexp] | 其中,regexp 表示目标动态库的名称,load 命令表示当 regexp 动态库加载时程序停止执行;unload 命令表示当 regexp 动态库被卸载时,程序暂停执行。regexp 参数也可以省略,此时只要程序中某一动态库被加载或卸载,程序就会暂停执行。 |
注意,当前 GDB 调试器对监控 C++ 程序中异常的支持还有待完善,使用 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 命令才会捕获并使程序暂停,之后将失效。
1) 创建 C++ 程序
$ cd ~/
$ vim test4.cpp
#include <iostream> using namespace std; int main() { int num = 1; while(num <= 5) { try { throw num; } catch (int e) { cout << num << endl; num++; } } cout << "finish" << endl; return 0; }
2)编译并设置调试信息
$ g++ -g test4.cpp -o test4
$ ./test4
1 2 3 4 5 finish
3) 处理 throw 事件
# GDB 启动 test4 程序
$ gdb -q test4
Reading symbols from test4 ... (gdb) catch throw int Catchpoint 1 (throw) (gdb) r Starting program: /home/xxx/test4 Catchpoint 1 (exception thrown), 0x00007ffff7e7e672 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6 # 程序暂停执行 (gdb) up # 运行 up 命令回到源码点 #1 0x00005555555552a6 in main () at test4.cpp:9 9 throw num; (gdb) c # 继续执行程序 Continuing. 1 Catchpoint 1 (exception thrown), 0x00007ffff7e7e672 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6 ... (gdb) c # 继续执行程序 Continuing. 5 finish [Inferior 1 (process 25566) exited normally]
4) 处理 catch 事件
# GDB 启动 test4 程序
$ gdb -q test4
Reading symbols from test4 ... (gdb) catch catch int Catchpoint 1 (catch) (gdb) r Starting program: /home/xxx/test4 Catchpoint 1 (exception caught), 0x00007ffff7e7d3e3 in __cxa_begin_catch () from /lib/x86_64-linux-gnu/libstdc++.so.6 # 程序暂停执行 (gdb) up # 运行 up 命令回到源码点 #1 0x00005555555552ef in main () at test4.cpp:10 10 } catch (int e) { (gdb) c Continuing. 1 Catchpoint 1 (exception caught), 0x00007ffff7e7d3e3 in __cxa_begin_catch () from /lib/x86_64-linux-gnu/libstdc++.so.6 ... (gdb) c Continuing. 5 finish [Inferior 1 (process 25658) exited normally]
5) 处理 load 事件
在个别场景中,还可以使用 catch 命令监控 C、C++ 程序动态库的加载和卸载。就以 test4 为例,其运行所需加载的动态库可以使用 ldd 命令查看,例如:
$ ldd test4
linux-vdso.so.1 (0x00007fff5d757000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1176ef9000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1176ede000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1176cec000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1176b9d000) /lib64/ld-linux-x86-64.so.2 (0x00007f11770f3000)
就以监控 libstdc++.so.6 为例。
# GDB 启动 test4 程序
$ gdb -q test4
Reading symbols from test4 ... (gdb) catch load libstdc++.so.6 Catchpoint 1 (load) (gdb) r Starting program: /home/xxx/test4 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: No such file or directory. (gdb) up #1 0x00007ffff7febc4b in _dl_sysdep_start (start_argptr=start_argptr@entry=0x7fffffffe220, dl_main=dl_main@entry=0x7ffff7fd15e0 <dl_main>) at ../elf/dl-sysdep.c:252 252 ../elf/dl-sysdep.c: No such file or directory. (gdb) c Continuing. 1 2 3 4 5 finish [Inferior 1 (process 25678) exited normally]
3. 条件断点
对于普通断点的建立,可以使用如下格式的 break 命令:
(gdb) break ... if cond
参数 ... 用于指定生成断点的具体位置;cond 参数用于代指某个表达式。通过此方式建立的普通断点,只有当表达式 cond 成立(值为 True)时,才会发挥它的作用;反之,断点并不会使程序停止执行。
类似上面这种,以某个表达式的是否成立作为条件,从而决定自身是否生效的断点,又称为条件断点。除了普通断点外,观察断点和捕捉断点也可以成为条件断点。
通过执行如下命令,即可直接生成一个观察条件断点:
(gdb) watch expr if cond
参数 expr 表示要观察的变量或表达式;参数 cond 用于代指某个表达式。
但是,以上创建条件断点的方法,不适用于捕捉断点。换句话说,捕捉条件断点无法直接生成,需要借助 condition 命令为现有捕捉断点增添一个 cond 表达式,才能使其变成条件断点。
condition 命令既可以为现有的普通断点、观察断点以及捕捉断点添加条件表达式,也可以对条件断点的条件表达式进行修改。语法格式如下:
(gdb) condition bnum expression
(gdb) condition bnum
参数 bnum 用于代指目标断点的编号;参数 expression 表示为断点添加或修改的条件表达式。
以上 2 种语法格式中,第 1 种用于为 bnum 编号的断点添加或修改 expression 条件表达式;第 2 种用于删除 bnum 编号断点的条件表达式,使其变成普通的无条件断点。
1) 创建 C++ 程序
$ cd ~/
$ vim test5.cpp
#include <iostream> using namespace std; int main() { int num = 1; while (num<10) { try { throw num; } catch (int &e) { num++; } } cout << num << endl; return 0; }
2)编译并设置调试信息
$ g++ -g test5.cpp -o test5
$ ./test5
10
3)使用 condition 命令
# GDB 启动 test5 程序
$ gdb -q test5
Reading symbols from test5 ... (gdb) l 1 #include <iostream> 2 using namespace std; 3 4 int main() { 5 int num = 1; 6 while (num<10) { 7 try { 8 throw num; 9 } catch (int &e) { 10 num++; (gdb) 11 } 12 } 13 14 cout << num << endl; 15 return 0; 16 } (gdb) b 10 # 添加普通断点 Breakpoint 1 at 0x12d0: file test5.cpp, line 10. (gdb) r Starting program: /home/xxx/test5 1 Breakpoint 1, main () at test5.cpp:10 10 num++; (gdb) rwatch num # 添加观察断点 Hardware read watchpoint 2: num (gdb) catch throw int # 添加捕捉断点 Catchpoint 3 (throw) (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x00005555555552d0 in main() at test5.cpp:10 breakpoint already hit 1 time 2 read watchpoint keep y num 3 catchpoint keep y exception throw matching: int (gdb) condition 1 num==3 # 为普通断点添加条件表达式 (gdb) condition 2 num==5 # 为观察断点添加条件表达式 (gdb) condition 3 num==7 # 为捕捉断点添加条件表达式 (gdb) c Continuing. Breakpoint 1, main () at test5.cpp:10 # 普通条件断点触发 10 num++; (gdb) p num $1 = 3 (gdb) c Continuing. Hardware read watchpoint 2: num # 观察条件断点触发 Value = 5 0x0000555555555260 in main () at test5.cpp:6 6 while (num<10) { (gdb) p num $2 = 5 ... (gdb) c Continuing. Catchpoint 3 (exception thrown), 0x00007ffff7e7e672 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6 # 捕捉条件断点触发 (gdb) up #1 0x0000555555555285 in main () at test5.cpp:8 8 throw num; (gdb) p num $5 = 7
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤