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的時間。
相關文章
相關標籤/搜索