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

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

在 GDB 调试器中对 C、C++ 程序打断点,最常用的就是 break 命令,有些场景中还会用到 tbreak 或者 rbreak 命令,本节将对这 3 个命令的功能和用法做详细的讲解。

为了让大家更好地了解给程序打断点的作用,这里以一段完整的 C 语言程序为例:

  1. #include<stdio.h>
  2. int main(int argc,char* argv[]) {
  3. int num = 1;
  4. while(num<100) {
  5. num *= 2;
  6. }
  7. printf("num=%d",num);
  8. return 0;
  9. }

程序存储在 /tmp/demo/main.c文件中,并已经生成了具备调试信息的 main.exe 可执行文件:

  1. [root@bogon demo]# ls
  2. main.c main.exe
  3. [root@bogon demo]# gdb main.exe -q
  4. Reading symbols from main.exe...
  5. (gdb)

GDB break命令

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

  1. (gdb) break location # 格式1: b location
  2. (gdb) break ... if cond # 格式2: b .. if cond

§ 8.GDB break(b):设置断点 - 图1 格式 1

第一种格式中,location 用于指定打断点的具体位置,其表示方式有多种,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 函数的开头位置打断点。

§ 8.GDB break(b):设置断点 - 图2 格式 2

第二种格式中,

  • ... 可以是上表中所有参数的值,用于指定打断点的具体位置;
  • cond 为某个表达式。

整体的含义为:每次程序执行到 ... 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。

如下演示了以上 2 种打断点方式的具体用法:

  1. (gdb) l
  2. 1 #include<stdio.h>
  3. 2 int main(int argc,char* argv[])
  4. 3 {
  5. 4 int num = 1;
  6. 5 while(num<100)
  7. 6 {
  8. 7 num *= 2;
  9. 8 }
  10. 9 printf("num=%d",num);
  11. 10 return 0;
  12. (gdb)
  13. 11 }
  14. (gdb) b 4 <-- 程序第 4 行打断点
  15. Breakpoint 1 at 0x1138: file main.c, line 4.
  16. (gdb) r <-- 运行程序,至第 4 行暂停
  17. Starting program: /home/ubuntu64/demo/main.exe
  18. Breakpoint 1, main (argc=1, argv=0x7fffffffe078) at main.c:4
  19. 4 int num = 1;
  20. (gdb) b +1 <-- 在第 4 行的基础上,在第 5 行代码处打断点
  21. Breakpoint 2 at 0x55555555513f: file main.c, line 5.
  22. (gdb) c <-- 继续执行程序,至第 5 行暂停
  23. Continuing.
  24. Breakpoint 2, main (argc=1, argv=0x7fffffffe078) at main.c:5
  25. 5 while(num<100)
  26. (gdb) b 7 if num>10 <-- 如果 num>10 在第 7 行打断点
  27. Breakpoint 3 at 0x555555555141: file main.c, line 7.
  28. (gdb) c <-- 继续执行
  29. Continuing.
  30. Breakpoint 3, main (argc=1, argv=0x7fffffffe078) at main.c:7
  31. 7 num *= 2; <-- 程序在第 7 行暂停
  32. (gdb) p num <-- p 命令查看 num 当前的值
  33. $1 = 16 <-- num=16

有关 cp 命令,后续章节会做详细讲解,这里会用即可,不必深究。

GDB tbreak命令

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

tbreak 命令的使用格式和 break 完全相同,有以下 2 种:

  1. (gdb) tbreak location # 使用格式1
  2. (gdb) tbreak ... if cond # 使用格式2

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

仍以 main.exe 为例,如下演示了 tbreak 命令的用法:

  1. (gdb) tbreak 7 if num>10 # 如果num>10,则在第7行打断点
  2. Temporary breakpoint 1 at 0x1165: file main.c, line 7.
  3. (gdb) r
  4. Starting program: /home/ubuntu64/demo/main.exe
  5. Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe088) at main.c:7
  6. 7 num *= 2; # 在第7行暂停
  7. (gdb) p num
  8. $1 = 16
  9. (gdb) c <-- 继续执行程序,则原使用 tbreak 在第 7 行打的断点将不再起作用
  10. Continuing.
  11. num=128[Inferior 1 (process 6534) exited normally]
  12. (gdb)

可以看到,自 num=16开始,后续循环过程中 num 的值始终大于 10,则num>10表达式的值永远为 True,理应在第 7 行暂停多次。但由于打断点采用的是 tbreak 命令,因此断点的作用只起 1 次。

GDB rbreak 命令

breaktbreak 命令不同,rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点。

rbreak 命令的使用语法格式为:

  1. (gdb) rbreak regex

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

这里我们对 main.c 源文件的程序做如下修改:

  1. (gdb) l <-- 显示源码
  2. 1 #include<stdio.h>
  3. 2 void rb_one(){
  4. 3 printf("rb_one\n");
  5. 4 }
  6. 5 void rb_second(){
  7. 6 printf("rb_second");
  8. 7 }
  9. 8 int main(int argc,char* argv[])
  10. 9 {
  11. 10 rb_one();
  12. (gdb)
  13. 11 rb_second();
  14. 12 return 0;
  15. 13 }
  16. (gdb) rbreak rb_* <--匹配所有以 rb_ 开头的函数
  17. Breakpoint 1 at 0x1169: file main.c, line 2.
  18. void rb_one();
  19. Breakpoint 2 at 0x1180: file main.c, line 5.
  20. void rb_second();
  21. (gdb) r
  22. Starting program: /home/ubuntu64/demo/main.exe
  23. Breakpoint 1, rb_one () at main.c:2
  24. 2 void rb_one(){
  25. (gdb) c
  26. Continuing.
  27. rb_one
  28. Breakpoint 2, rb_second () at main.c:5
  29. 5 void rb_second(){
  30. (gdb) c
  31. Continuing.
  32. rb_second[Inferior 1 (process 7882) exited normally]
  33. (gdb)

可以看到,通过执行rbreak rb_*指令,找到了程序中所有以 tb_* 开头的函数,并在这些函数内部的开头位置打上了断点(如上所示,分别为第 2 行和第 5 行)。

总结

在 GDB 调试器中,为程序的适当位置打断点,是最常用的调试程序的方法。不过,本节仅介绍了如何使用 breaktbreakrbreak)在程序中打断点,实际场景中还可以使用 catch 或者 watch 中断程序的运行,有关它们的功能和用法,会在后续章节中给大家做详细讲解。