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

 

posted @ 2023-01-03 16:30  垄山小站  阅读(1678)  评论(0编辑  收藏  举报