GDB调试指南-单步调试

野性酷女 2022-02-24 12:10 991阅读 0赞

前言

前面通过《启动调试》,《断点设置》,《变量查看》,我们已经了解了GDB基本的启动,设置断点,查看变量等,如果这些内容你还不知道,建议先回顾一下前面的内容。在启动调试设置断点观察之后,没有我们想要的信息怎么办呢?这个时候,就需要单步执行或者跳过当前断点继续执行等等。而本文所说的单步调试并非仅仅指单步执行,而是指在你的控制之下,按要求执行语句。

准备

老规矩,先准备一个示例程序如下:

  1. 1/*gdbStep.c*/
  2. 2#include<stdio.h>
  3. 3/*计算简单乘法,这里没有考虑溢出*/
  4. 4int add(int a, int b)
  5. 5{
  6. 6 int c = a + b;
  7. 7 return c;
  8. 8}
  9. 9/*打印从0到num-1的数*/
  10. 10int count(int num)
  11. 11{
  12. 12 int i = 0;
  13. 13 if(0 > num)
  14. 14 return 0;
  15. 15 while(i < num)
  16. 16 {
  17. 17 printf("%d\n",i);
  18. 18 i++;
  19. 19 }
  20. 20 return i;
  21. 21}
  22. 22int main(void)
  23. 23{
  24. 24 int a = 3;
  25. 25 int b = 7;
  26. 26 printf("it will calc a + b\n");
  27. 27 int c = add(a,b);
  28. 28 printf("%d + %d = %d\n",a,b,c);
  29. 29 count(c);
  30. 30 return 0;
  31. 31}

编译:

  1. gcc -g -o gdbStep gdbStep.c

程序的功能比较简单,这里不多做解释。

特别简单说明一条命令,list(可简写为l),它可以将源码列出来,例如:

  1. (gdb) list
  2. 1 #include<stdio.h>
  3. 2
  4. 3 /*计算简单乘法,这里没有考虑溢出*/
  5. 4 int add(int a, int b)
  6. 5 {
  7. 6 int c = a * b;
  8. 7 return c;
  9. 8 }
  10. 9 int main(void)
  11. 10 {
  12. (gdb) l
  13. 11 int a = 13;
  14. 12 int b = 57;
  15. 13 printf("it will calc a * b\n");
  16. 14 int c = add(a,b);
  17. 15 printf("%d*%d = %d\n",a,b,c);
  18. 16 return 0;
  19. 17 }
  20. (gdb)

单步执行-next

next命令(可简写为n)用于在程序断住后,继续执行下一条语句,假设已经启动调试,并在第12行停住,如果要继续执行,则使用n执行下一条语句,如果后面跟上数字num,则表示执行该命令num次,就达到继续执行n行的效果了:

  1. $ gdb gdbStep #启动调试
  2. (gdb)b 25 #将断点设置在12行
  3. (gdb)run #运行程序
  4. Breakpoint 1, main () at gdbStep.c:25
  5. 25 int b = 7;
  6. (gdb) n #单步执行
  7. 26 printf("it will calc a + b\n");
  8. (gdb) n 2 #执行两次
  9. it will calc a + b
  10. 28 printf("%d + %d = %d\n",a,b,c);
  11. (gdb)

从上面的执行结果可以看到,我们在25行处断住,执行n之后,运行到26行,运行n 2之后,运行到28行,但是有没有发现一个问题,为什么不会进入到add函数内部呢?那就需要用到另外一个命令啦。

单步进入-step

对于上面的情况,如果我们想跟踪add函数内部的情况,可以使用step命令(可简写为s),它可以单步跟踪到函数内部,但前提是该函数有调试信息并且有源码信息。

  1. $ gdb gdbStep #启动调试
  2. (gdb) b 25 #在12行设置断点
  3. Breakpoint 1 at 0x4005d3: file gdbStep.c, line 25.
  4. (gdb) run #运行程序
  5. Breakpoint 1, main () at gdbStep.c:25
  6. 25 int b = 7;
  7. (gdb) s
  8. 26 printf("it will calc a + b\n");
  9. (gdb) s #单步进入,但是并没有该函数的源文件信息
  10. _IO_puts (str=0x4006b8 "it will calc a + b") at ioputs.c:33
  11. 33 ioputs.c: No such file or directory.
  12. (gdb) finish #继续完成该函数调用
  13. Run till exit from #0 _IO_puts (str=0x4006b8 "it will calc a + b")
  14. at ioputs.c:33
  15. it will calc a + b
  16. main () at gdbStep.c:27
  17. 27 int c = add(a,b);
  18. Value returned is $1 = 19
  19. (gdb) s #单步进入,现在已经进入到了add函数内部
  20. add (a=13, b=57) at gdbStep.c:6
  21. 6 int c = a + b;

从上面的过程可以看到,s命令会尝试进入函数,但是如果没有该函数源码,需要跳过该函数执行,可使用finish命令,继续后面的执行。如果没有函数调用,s的作用与n的作用并无差别,仅仅是继续执行下一行。它后面也可以跟数字,表明要执行的次数。

当然它还有一个选项,用来设置当遇到没有调试信息的函数,s命令是否跳过该函数,而执行后面的。默认情况下,它是会跳过的,即step-mode值是off:

  1. (gdb) show step-mode
  2. Mode of the step operation is off.
  3. (gdb) set step-mode on
  4. (gdb) set step-mode off

还有一个与step相关的命令是stepi(可简写为si),它与step不同的是,每次执行一条机器指令:

  1. (gdb) si
  2. 0x0000000000400573 6 int c = a + b;
  3. (gdb) display/i $pc
  4. 1: x/i $pc
  5. => 0x400573 <add+13>: mov -0x18(%rbp),%eax
  6. (gdb)

继续执行到下一个断点-continue

我们可能打了多处断点,或者断点打在循环内,这个时候,想跳过这个断点,甚至跳过多次断点继续执行该怎么做呢?可以使用continue命令(可简写为c)或者fg,它会继续执行程序,直到再次遇到断点处:

  1. $ gdb gdbStep
  2. (gdb)b 18 #在count函数循环内打断点
  3. (gdb)run
  4. Breakpoint 1, count (num=10) at gdbStep.c:18
  5. 18 i++;
  6. (gdb) c #继续运行,直到下一次断住
  7. Continuing.
  8. 1
  9. Breakpoint 1, count (num=10) at gdbStep.c:18
  10. 18 i++;
  11. (gdb) fg #继续运行,直到下一次断住
  12. Continuing.
  13. 2
  14. Breakpoint 1, count (num=10) at gdbStep.c:18
  15. 18 i++;
  16. (gdb) c 3 #跳过三次
  17. Will ignore next 2 crossings of breakpoint 1. Continuing.
  18. 3
  19. 4
  20. 5
  21. Breakpoint 1, count (num=10) at gdbStep.c:18
  22. 18 i++;

继续运行到指定位置-until

假如我们在25行停住了,现在想要运行到29行停住,就可以使用until命令(可简写为u):

  1. $ gdb gdbStep
  2. (gdb)b 25
  3. (gdb)run
  4. (gdb) u 29
  5. it will calc a + b
  6. 3 + 7 = 10
  7. main () at gdbStep.c:29
  8. 29 count(c);
  9. (gdb)

可以看到,在执行u 29之后,它在29行停住了。它利用的是临时断点。

跳过执行—skip

skip可以在step时跳过一些不想关注的函数或者某个文件的代码:

  1. $ gdb gdbStep
  2. (gdb) b 27
  3. Breakpoint 1 at 0x4005e4: file gdbStep.c, line 27.
  4. (gdb) skip function add #step时跳过add函数
  5. Function add will be skipped when stepping.
  6. (gdb) info skip #查看step情况
  7. Num Type Enb What
  8. 1 function y add
  9. (gdb) run
  10. Starting program: /home/hyb/workspaces/gdb/gdbStep
  11. it will calc a + b
  12. Breakpoint 1, main () at gdbStep.c:27
  13. 27 int c = add(a,b);
  14. (gdb) s
  15. 28 printf("%d + %d = %d\n",a,b,c);
  16. (gdb)

可以看到,再使用skip之后,使用step将不会进入add函数。
step也后面也可以跟文件:

  1. (gdb)skip file gdbStep.c

这样gdbStep.c中的函数都不会进入。

其他相关命令:

  • skip delete [num] 删除skip
  • skip enable [num] 使能skip
  • skip disable [num] 去使能skip

其中num是前面通过info skip看到的num值,上面可以带或不带该值,如果不带num,则针对所有skip,如果带上了,则只针对某一个skip。

总结

本文主要介绍了一些简单情况的单步调试方法或常见命令使用,但这些已经够用了,毕竟大部分程序的执行或停止都在我们的掌控之中了。

推荐阅读:

福利④期|小米手环+高质量书籍

GDB调试指南-启动调试

GDB调试指南-断点设置

GDB调试指南-变量查看

关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源。

640?wx\_fmt=jpeg

发表评论

表情:
评论列表 (有 0 条评论,991人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GDB 调试

    1、首先需要用gcc(g++) 对源文件进行编译生成可执行文件,并且在编译时加上选项-g,把调试信息加到目标文件中。 2、假设生成的可执行文件为test,那么gdb tes