Skip to content

LV073-gdb-break

默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。要想观察程序运行的内部细节(例如某变量值的变化情况),可以借助 GDB 调试器在程序中的某个地方设置断点,这样当程序执行到这个地方时就会停下来。

一、断点

1. 什么是断点

断点(BreakPoint),可以理解为障碍物,人遇到障碍物不能行走,程序遇到断点就暂停执行

2. 怎么打断点

在 GDB 调试器中对 C、C++ 程序打断点,最常用的就是 break 命令,有些场景中还会用到 tbreak 或者 rbreak 命令。

二、break

1. 语法格式

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

shell
(gdb) break location      # b location
(gdb) break ... if cond   # b ... if cond

1.1 b location

location 用于指定打断点的具体位置,其表示方式有多种,如表所示:

表 1 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 函数的开头位置打断点。

1.2 b ... if cond

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

2. 使用示例

2.1 测试程序

c
#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[]) {
    /* code */
    int i = 0;
    printf("Hello, World! \n");

    while (1) {
        i++;
        sleep(1);
        if (i > 100) {
            printf("i=%d\n", i++);
            i = 0;
        }
    }

    return 0;
}

我们在ubuntu中测试,所以直接用下面的命令编译:

shell
cd ~/workspace/c-learning/02-c-basic/21-debug
gcc 025-gdb-break.c -g

2.2 调试示例

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) l            # <=== 1. 查看源码
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main(int argc, const char *argv[]) {
5           /* code */
6           int i = 0;
7           printf("Hello, World! \n");
8
9           while (1) {
10              i++;
(gdb) b 6          # <=== 2. 在第6行打断点
Breakpoint 1 at 0x119c: file 010-gdb-debug-running.c, line 6.
(gdb) r            # <=== 3. 运行程序,到第6行暂停
Starting program: /home/sumu/workspace/c-learning/02-c-basic/21-debug/a.out 

Breakpoint 1, main (argc=1, argv=0x7fffffffce28) at 010-gdb-debug-running.c:6
6           int i = 0;
(gdb) b +1         # <=== 4. 在第6行的基础上,在第7行打上断点
Breakpoint 2 at 0x5555555551a3: file 010-gdb-debug-running.c, line 7.
(gdb) c            # <=== 5. 继续运行到第7行,然后暂停(第7行还未执行)
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffce28) at 010-gdb-debug-running.c:7
7           printf("Hello, World! \n");
(gdb) b 11 if i>3  # <=== 6. 如果i大于3了,就在第11行打断点,程序暂停
Breakpoint 3 at 0x5555555551b3: file 010-gdb-debug-running.c, line 11.
(gdb) c            # <=== 7. 程序继续执行
Continuing.
Hello, World! 
                   # <=== 程序在第11行暂停了
Breakpoint 3, main (argc=1, argv=0x7fffffffce28) at 010-gdb-debug-running.c:11
11              sleep(1);
(gdb) p i          # <=== 8. 查看i变量的值
$1 = 4             # <=== i的值为4,大于了3,所以这里有一个断点,程序暂停在sleep(1)这一行了。
(gdb) info b       # <=== 9. 查看所有的断点
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555519c in main at 010-gdb-debug-running.c:6
        breakpoint already hit 1 time
2       breakpoint     keep y   0x00005555555551a3 in main at 010-gdb-debug-running.c:7
        breakpoint already hit 1 time
3       breakpoint     keep y   0x00005555555551b3 in main at 010-gdb-debug-running.c:11
        stop only if i>3
        breakpoint already hit 1 time
(gdb) c            # <=== 10. 再运行一次
Continuing.
                   # <=== 程序再次暂停在11行
Breakpoint 3, main (argc=1, argv=0x7fffffffce28) at 010-gdb-debug-running.c:11
11              sleep(1);
(gdb) p i          # <=== 11. 查看i变量的值
$2 = 5             # <=== 值为5,也是大于3的,这个断点依然存在所以
(gdb)

三、tbreak

1. 语法格式

tbreak 命令可以看到是 break 命令的另一个版本,tbreak 和 break 命令的用法和功能都非常相似,唯一的不同在于,使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。tbreak 命令的使用格式和 break 完全相同,有下面两种格式:

shell
(gdb) tbreak location      # tb location
(gdb) tbreak ... if cond   # tb ... if cond

其中,location、... 和 cond 的含义都和 break 命令中的参数含义相同,即表 1 也同样适用于 tbreak 命令。

2. 使用示例

2.1 测试程序

c
#include <stdio.h>

int main(int argc, const char *argv[]) {
    int i = 1;
    printf("Hello, World! \n");

    while (i < 30) {
        i += 2;
    }
    printf("i = %d\n", i);

    return 0;
}

我们在ubuntu中测试,所以直接用下面的命令编译:

shell
cd ~/workspace/c-learning/02-c-basic/21-debug
gcc 026-gdb-tbreak.c -g

2.2 调试示例

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) l                     # <=== 1. 查看源码
1       #include <stdio.h>
2
3       int main(int argc, const char *argv[]) {
4           int i = 1;
5           printf("Hello, World! \n");
6
7           while (i < 30) {
8               i += 2;
9           }
10          printf("i = %d\n", i);
(gdb) tb 8 if i>10         # <=== 2. 使用tb打上一个端点
Temporary breakpoint 1 at 0x1191: file 026-gdb-tbreak.c, line 8.
(gdb) info b               # <=== 3. 查看断点信息
Num     Type           Disp Enb Address            What
1       breakpoint     del  y   0x0000000000001191 in main at 026-gdb-tbreak.c:8
        stop only if i>10
(gdb) r                   # <=== 4. 运行到断点处停下
Starting program: /home/sumu/workspace/c-learning/02-c-basic/21-debug/a.out 
Hello, World! 
                          # <=== 程序暂停
Temporary breakpoint 1, main (argc=1, argv=0x7fffffffce28) at 026-gdb-tbreak.c:8
8               i += 2;
(gdb) p i                 # <=== 5. 查看i的值
$1 = 11                   # <=== i=11,大于10了,所以上面断点生效,程序暂停
(gdb) info b              # <=== 6. 查看断点信息
No breakpoints or watchpoints. # <=== 断点消失
(gdb) c
Continuing.
i = 31                         # <=== 7. 程序运行结束
[Inferior 1 (process 13426) exited normally]
(gdb)

三、rbreak

1. 语法格式

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

shell
(gdb) rbreak regex # rb regex

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

2. 使用示例

2.1 测试程序

c
#include <stdio.h>

void rb_first() {
    printf("rb_first\n");
}

void rb_second() {
    printf("rb_second\n");
}

int main(int argc, const char *argv[]) {
    printf("Hello, World! \n");

    rb_first();
    rb_second();

    return 0;
}

我们在ubuntu中测试,所以直接用下面的命令编译:

shell
cd ~/workspace/c-learning/02-c-basic/21-debug
gcc 027-gdb-rbreak.c -g

2.2 调试示例

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) l                 # <=== 1. 查看源码
1       #include <stdio.h>
2
3       void rb_first() {
4           printf("rb_first\n");
5       }
6
7       void rb_second() {
8           printf("rb_second\n");
9       }
10
(gdb)                  # <=== 2. 按下了Enter,显示剩下的代码(默认只显示10行)
11      int main(int argc, const char *argv[]) {
12          printf("Hello, World! \n");
13
14          rb_first();
15          rb_second();
16
17          return 0;
18      }
(gdb) rb rb_*         # <=== 3. 匹配所有以 rb_ 开头的函数
Breakpoint 1 at 0x1149: file 027-gdb-rbreak.c, line 3.
void rb_first();
Breakpoint 2 at 0x1160: file 027-gdb-rbreak.c, line 7.
void rb_second();
(gdb) info b         # <=== 4. 查看所有的断点,过执行rbreak rb_*指令,找到了程序中所有以 tb_* 开头的函数,并在这些函数内部的开头位置打上了断点
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001149 in rb_first at 027-gdb-rbreak.c:3
2       breakpoint     keep y   0x0000000000001160 in rb_second at 027-gdb-rbreak.c:7
(gdb) r             
Starting program: /home/sumu/workspace/c-learning/02-c-basic/21-debug/a.out 
Hello, World! 

Breakpoint 1, rb_first () at 027-gdb-rbreak.c:3
3       void rb_first() {
(gdb) c
Continuing.
rb_first

Breakpoint 2, rb_second () at 027-gdb-rbreak.c:7
7       void rb_second() {
(gdb) c
Continuing.
rb_second
[Inferior 1 (process 14010) exited normally]
(gdb)