天天看點

使用GDB調試Coredump檔案

轉自:http://blog.ddup.us/?p=176

寫C/C++程式經常要直接和記憶體打交道,一不小心就會造成程式執行時産生Segment Fault而挂掉。一般這種情況都是因為數組越界通路,空指針或是野指針讀寫造成的。程式小的話還比較好辦,對着源代碼仔細檢查就能解決。但是對于代碼量較大的程式,裡邊包含N多函數調用,N多數組指針通路,這時想定位問題就不是很容易了(此時牛人依然可以通過在适當位置打printf加二分查找的方式迅速定位:P)。懶人的話還是直接GDB搞起吧。

神馬是Core Dump檔案

偶爾就能聽見某程式員同學抱怨“擦,又出Core了!”。簡單來說,core dump說的是作業系統執行的一個動作,當某個程序因為一些原因意外終止(crash)的時候,作業系統會将這個程序當時的記憶體資訊轉儲(dump)到磁盤上1。産生的檔案就是core檔案了,一般會以core.xxx形式命名。

如何産生Core Dump

發生doredump一般都是在程序收到某個信号的時候,Linux上現在大概有60多個信号,可以使用 kill -l 指令全部列出來。

sagi@sagi-laptop:~$ kill -l
     1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
      6) SIGABRT     7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
      11) SIGSEGV   12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
      16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
      21) SIGTTIN   22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
      26) SIGVTALRM 27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
      31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
      38) SIGRTMIN+4    39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
      43) SIGRTMIN+9    44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
      48) SIGRTMIN+14   49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
      53) SIGRTMAX-11   54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
      58) SIGRTMAX-6    59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
      63) SIGRTMAX-1    64) SIGRTMAX      

針對特定的信号,應用程式可以寫對應的信号處理函數。如果不指定,則采取預設的處理方式, 預設處理是coredump的信号如下:

3)SIGQUIT   4)SIGILL    6)SIGABRT   8)SIGFPE    11)SIGSEGV    7)SIGBUS    31)SIGSYS
5)SIGTRAP   24)SIGXCPU  25)SIGXFSZ  29)SIGIOT      

我們看到SIGSEGV在其中,一般數組越界或是通路空指針都會産生這個信号。另外雖然預設是這樣的,但是你也可以寫自己的信号處理函數改變預設行為,更多信号相關可以看參考連結33。

上述内容隻是産生coredump的必要條件,而非充分條件。要産生core檔案還依賴于程式運作的shell,可以通過ulimit -a指令檢視,輸出内容大緻如下:

sagi@sagi-laptop:~$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 20
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 16382
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) unlimited
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited      

看到第一行了吧,core file size,這個值用來限制産生的core檔案大小,超過這個值就不會儲存了。我這裡輸出是0,也就是不會儲存core檔案,即使産生了,也儲存不下來==! 要改變這個設定,可以使用ulimit -c unlimited。

OK, 現在萬事具備,隻缺一個能産生Core的程式了,介個對C程式員來說太容易了。

#include <stdio.h>;
#include <stdlib.h>;
 
    int crash()
    {
            char *xxx = "crash!!";
            xxx[1] = 'D'; // 寫隻讀存儲區!
            return 2;
    }
 
    int foo()
    {
            return crash();
    }
 
    int main()
    {
            return foo();
    }      

上手調試

上邊的程式編譯的時候有一點需要注意,需要帶上參數-g, 這樣生成的可執行程式中會帶上足夠的調試資訊。編譯運作之後你就應該能看見期待已久的“Segment Fault(core dumped)”或是“段錯誤 (核心已轉儲)”之類的字眼了。看看目前目錄下是不是有個core或是core.xxx的檔案。祭出linux下經典的調試器GDB,首先帶着core檔案載入程式:gdb exefile core,這裡需要注意的這個core檔案必須是exefile産生的,否則符号表會對不上。載入之後大概是這個樣子的:

sagi@sagi-laptop:~$ gdb coredump core
Core was generated by ./coredump'.
    Program terminated with signal 11, Segmentation fault.
#0  0x080483a7 in crash () at coredump.c:8
    8       xxx[1] = 'D';
(gdb)      

我們看到已經能直接定位到出core的地方了,在第8行寫了一個隻讀的記憶體區域導緻觸發Segment Fault信号。在載入core的時候有個小技巧,如果你事先不知道這個core檔案是由哪個程式産生的,你可以先随便找個代替一下,比如/usr/bin/w就是不錯的選擇。比如我們采用這種方法載入上邊産生的core,gdb會有類似的輸出:

sagi@sagi-laptop:~$ gdb /usr/bin/w core
Core was generated by ./coredump'.
    Program terminated with signal 11, Segmentation fault.
#0  0x080483a7 in ?? ()
    (gdb)      

可以看到GDB已經提示你了,這個core是由哪個程式産生的。

GDB 常用操作

上邊的程式比較簡單,不需要另外的操作就能直接找到問題所在。現實卻不是這樣的,常常需要進行單步跟蹤,設定斷點之類的操作才能順利定位問題。下邊列出了GDB一些常用的操作。

  • 啟動程式:run
  • 設定斷點:b 行号|函數名
  • 删除斷點:delete 斷點編号
  • 禁用斷點:disable 斷點編号
  • 啟用斷點:enable 斷點編号
  • 單步跟蹤:next 也可以簡寫 n
  • 單步跟蹤:step 也可以簡寫 s
  • 列印變量:print 變量名字
  • 設定變量:set var=value
  • 檢視變量類型:ptype var
  • 順序執行到結束:cont
  • 順序執行到某一行: util lineno
  • 列印堆棧資訊:bt

bt這個指令重點推薦,尤其是當函數嵌套很深,調用關系複雜的時候,他能夠顯示出整個函數的調用堆棧,調用關系一目了然。另外上邊有兩個單步執行的指令,一個是n,一個是s。主要差別是n會将函數調用當成一步執行,而s會跟進調用函數内部。

參考

  1. http://en.wikipedia.org/wiki/Coredump
  2. http://www.kernel.org/doc/man-pages/online/pages/man5/core.5.html
  3. http://www.kernel.org/doc/man-pages/online/pages/man7/signal.7.html
  4. http://cs.baylor.edu/~donahoo/tools/gdb/tutorial.html
  5. http://bloggerdigest.blogspot.com/2006/09/gnu-gdb-core-dump-debugging.html

繼續閱讀