GDB技巧:使用checkpoint解决难以复现的Bug

本文的copyleft归gfree.wind@gmail.com全部,使用GPL发布,能够自由拷贝,转载。但转载请保持文档的完整性,注明原做者及原连接,严禁用于任何商业用途。
做者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
 
css


做为程序员,调试是一项很重要的基本功。调试的技巧和水平,直接决定了解决问题的时间。通常状况下,GDB的基本命令已经足以应付大多数问题了。可是,对于有些问题,仍是须要更高级一些的命令。今天介绍一下checkpoint。

有一些bug,可能很难复现,当好不容易复现一次,且刚刚进入程序的入口时,咱们须要珍惜这个来之不易的机会。若是只使用基本命令的话,对于大部分代码,咱们都须要使用step来步进。这样无疑会耗费大量的时间,由于大部分的代码可能都没有问题。但是一旦不当心使用next,结果刚好该语句的函数调用返回出错。那么对于此次来之不易的机会,咱们只获得了部分信息,即肯定问题出在该函数,可是哪里出错仍是不清楚。因而还须要再一次的复现bug,时间就这样浪费了。

因此,对于这种问题,就是checkpoint大显身手的时候。先看一下GDB关于checkpoint的说明:
On certain operating system(Currently, only GNU/Linux), GDB is able to save a snapshot of a program's state, called a checkpoint and come back to it later.
Returning to a checkpoint effectively undoes everything that has happened in the program since the checkpoint was saved. This includes changes in memory, register, and even(within some limits) system state. Effectively, it is like going back in time to the moment when the checkpoint was saved.
也就是说checkpoint是程序在那一刻的快照,当咱们发现错过了某个调试机会时,能够再次回到checkpoint保存的那个程序状态。

举例说明一下:
  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. static int func()
  4. {
  5.     static int i = 0;
  6.     ++i;
  7.     if (i == 2) {
  8.         return 1;
  9.     }
  10.     return 0;
  11. }

  12. static int func3()
  13. {
  14.     return func();
  15. }

  16. static int func2()
  17. {
  18.     return func();
  19. }

  20. static int func1()
  21. {
  22.     return func();
  23. }

  24. int main()
  25. {
  26.     int ret = 0;

  27.     ret += func1();
  28.     ret += func2();
  29.     ret += func3();

  30.     return ret;
  31. }
当咱们执行这个程序时,发现程序返回1,不是指望的成功0。因而开始调试程序,因为函数调用的嵌套过多,咱们无法一眼看出是main中的哪一个函数调用出错了。因而在ret += func1()前,咱们保存一个checkpoint。
  1. (gdb) b main
  2. Breakpoint 1 at 0x80483e0: file test.c, line 31.
  3. (gdb) r
  4. Starting program: /home/fgao/works/test/a.out

  5. Breakpoint 1, main () at test.c:31
  6. 31 int ret = 0;
  7. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.i686
  8. (gdb) n
  9. 33 ret += func1();
  10. (gdb) checkpoint
  11. checkpoint: fork returned pid 2060.
  12. (gdb)
而后使用next步进,并每次调用完毕,打印ret的值
  1. Breakpoint 1, main () at test.c:31
  2. 31 int ret = 0;
  3. (gdb) n
  4. 33 ret += func1();
  5. (gdb) checkpoint
  6. checkpoint: fork returned pid 2060.
  7. (gdb) n
  8. 34 ret += func2();
  9. (gdb) p ret
  10. $4 = 0
  11. (gdb) n
  12. 35 ret += func3();
  13. (gdb) p ret
  14. $5 = 1
结果发现,在调用func2()调用后,ret的值变为了1。但是此时,咱们已经错过了调试func2的机会。若是没有checkpoint,就须要再次从头调试了——对于这个问题从头调试很容易,可是对于很难复现的bug可就不说那么容易的事情了。

ok,使用checkpoint恢复
  1. (gdb) restart 1
  2. Switching to process 2060
  3. #0 main () at test.c:33
  4. 33 ret += func1();
  5. (gdb)
很简单,如今GDB恢复到了保存checkpoint时的状态了。上面“restart 1“中的1为checkpoint的id号,可使用info查看。
  1. (gdb) info checkpoints
  2. * 1 process 2060 at 0x80483e7, file test.c, line 33
  3.   0 process 2059 (main process) at 0x80483f7, file test.c, line 35

从上面能够看出checkpoint的用法很简单,可是颇有用。就是在平时的简单的bug修正中,也能够加快咱们的调试速度——毕竟减小了没必要要的重现bug的时间。
相关文章
相关标签/搜索