利用Core Dump調試程序 描述 簡介 舉例 其它

描述

這裏介紹Linux環境下使用gdb結合core dump文件進行程序的調試和定位。

簡介

當用戶程序運行,可能會由於某些原因發生崩潰(crash),這個時候可以產生一個Core Dump文件,記錄程序發生崩潰時候內存的運行狀況。這個Core Dump文件,一般名稱爲core或者core.pid(pid就是應用程序運行時候的pid號),它可以幫助我們找出程序崩潰的原因。

對於一個運行出錯的程序,我們可以有多種方法調試它,以便發生錯誤的原因:

  1. 通過閱讀代碼;
  2. 通過在代碼中設置一些打印語句(插旗子);
  3. 通過使用gdb設置斷點來跟蹤程序的運行。

但是這些方法對於調試程序運行崩潰這樣類似的錯誤,定位都不夠迅速,如果程序代碼很多的話,顯然前面的方法有很多缺陷。

在後面,我們來看看另外一種可以定位錯誤的方法: 使用gdb結合Core Dump文件來迅速定位到這個錯誤

這個方法,如果程序運行崩潰,那麼可以迅速找到導致程序崩潰的原因。

當然,調試程序,沒有哪個方法是最好的,這裏只對最後一種方法重點講解,實際過程中,往往根據需要和其他方法結合使用。

舉例

下面,給出一個實際的操作過程,描述我們使用gdb調試工具,結合Core Dump文件,定位程序崩潰的位置。

一、程序源代碼

下面是我們的程序源代碼:

1 #include <iostream>
2 using std::cerr;
3 using std::endl;
4
5 void my_print(int d1, int d2);
6 int main(int argc, char *argv[])
7 {
8 int a = 1;
9 int b = 2;
10 my_print(a,b);
11 return 0;
12 }
13
14 void my_print(int d1, int d2)
15 {
16 int *p1=&d1;
17 int *p2 = NULL;
18 cerr<<"first is:"<<*p1<<",second is:"<<*p2<<endl;
19 }

這裏,程序代碼很少,我們可以直接通過代碼看到這個程序第17行的p2是NULL,而18行卻用*p2來進行引用,明顯這樣訪問一個空的地址是一個錯誤(也許我們的初衷是使用p2來指向d2)。

我們可以有多種方法調試這個程序,以便發生上面的錯誤:

  1. 通過閱讀代碼;
  2. 通過在代碼中設置一些打印語句(插旗子);
  3. 通過使用gdb設置斷點來跟蹤程序的運行。

但是這些方法對於這個程序中類似的錯誤,定位都不夠迅速,如果程序代碼很多的話,顯然前面的方法有很多缺陷。在後面,我們來看看另外一種可以定位錯誤的方法:d)使用gdb結合Core Dump文件來迅速定位到這個錯誤。

二、編譯程序

編譯過程如下:

# ls
main.cpp
# g++ -g main.cpp
# ls
a.out main.cpp

這樣,編譯main.cpp生成了可執行文件a.out,一定注意,因爲我們要使用gdb進行調試,所以我們使用'g++'的'-g'選項。

三、運行程序

運行過程如下:

# ./a.out
段錯誤
# ls
a.out main.cpp

這裏,如我們所期望的,會打印段錯誤的信息,但是並沒有生成Core Dump文件。

配置生成Core Dump文件的選項,並生成Core Dump:

# ulimit -c unlimited
# ./a.out
段錯誤 (core dumped)
# ls
a.out core.30557 main.cpp

這裏,我們看到,使用'ulimit'配置之後,程序崩潰的時候就會生成Core Dump文件了,這裏的文件是core.30557,文件名稱不同的系統生成的名稱有一點不同,這裏linux生成的名稱是:"core"+".pid"。

四、調試程序

使用Core Dump文件,結合gdb工具進行調試,過程如下:

1)初步定位:

# gdb a.out core.30557
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-23.el5_5.2)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/test/a.out...done.
Reading symbols from /usr/lib/libstdc++.so.6...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x0804870e in my_print (d1=1, d2=2) at main.cpp:18
18 cerr<<"first is:"<<*p1<<",second is:"<<*p2<<endl;

這裏,我們就進入了gdb的調試交互界面,看到gdb直接定位到導致程序出錯的位置了。我們還可以使用如下命令:"#gdb a.out –core=core.30557"。

通過錯誤,我們知道程序由於"signal 11"導致終止,如果想要大致瞭解"signal 11",那麼我們可查看signal的man手冊:

#man 7 signal

這樣,在輸出的信息中我們可以看見“SIGSEGV 11 Core Invalid memory reference”這樣的字樣,意思是說,signal(信號)11表示非法內存引用。注意這裏使用"man 7 signal"而不是"man signal",因爲我們要查看的不是signal函數或者signal命令,而是signal的其他信息,其他的信息在man手冊的第7節,具體需要了解一些使用man的命令。

2)查看具體調用關係

(gdb) bt
#0 0x0804870e in my_print (d1=1, d2=2) at main.cpp:18
#1 0x08048799 in main (argc=<value optimized out>, argv=<value optimized out>) at main.cpp:10
這裏,我們通過backtrace(簡寫爲bt)命令可以看到,導致崩潰那條語句是通過什麼調用途徑被調用到的。

3)設置斷點,並進行調試等:
(gdb) b main.cpp:10
Breakpoint 1 at 0x8048787: file main.cpp, line 10.
(gdb) r
Starting program: /root/test/a.out

Breakpoint 1, main (argc=<value optimized out>, argv=<value optimized out>) at main.cpp:10
10 my_print(a,b);
(gdb) s
my_print (d1=1, d2=2) at main.cpp:16
16 int *p1=&d1;
(gdb) n
17 int *p2 = NULL;
(gdb) n
18 cerr<<"first is:"<<*p1<<",second is:"<<*p2<<endl;
(gdb) p d1
$1 = 1
(gdb) p d2
$2 = 2
(gdb) p *p1
$1 = 1
(gdb) p *p2
Cannot access memory at address 0x0
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0x0804870e in my_print (d1=1, d2=2) at main.cpp:18
18 cerr<<"first is:"<<*p1<<",second is:"<<*p2<<endl;

這裏,我們在開始初步的定位的基礎上,通過設置斷點(break),運行(run),gdb的單步跟進(step),單步跳過(next),變量的打印(print)等各種gdb命令,來了解產生崩潰時候的具體情況,確定產生崩潰的原因。

4)退出gdb:

(gdb) q
A debugging session is active.

Inferior 3 [process 30584] will be killed.
Inferior 1 [process 1] will be killed.

Quit anyway? (y or n) y
Quitting: Couldn't get registers: 沒有那個進程.
#
# ls
a.out core.30557 core.30609 main.cpp

這裏,我們看到又產生了一個core文件。因爲剛纔調試,導致又產生了一個core文件。實際,如果我們只使用"gdb a.out core.30557"初步定位之後,不進行調試就退出gdb的話,就不會再生成core文件。

五、修正錯誤

1)通過上面的過程我們最終修正錯誤,得到正確的源代碼如下:

1 #include <iostream>
2 using std::cerr;
3 using std::endl;
4
5 void my_print(int d1, int d2);
6 int main(int argc, char *argv[])
7 {
8 int a = 1;
9 int b = 2;
10 my_print(a,b);
11 return 0;
12 }
13
14 void my_print(int d1, int d2)
15 {
16 int *p1=&d1;
17 //int *p2 = NULL;//lvkai-
18 int *p2 = &d2;//lvkai+
19 cerr<<"first is:"<<*p1<<",second is:"<<*p2<<endl;
20 }

2)編譯並運行這個程序,最終產生結果如下:

# g++ main.cpp
# ls
a.out main.cpp
# ./a.out
first is:1,second is:2

這裏,得到了我們預期的結果。

另外,有個小技巧,如果對Makefile有些瞭解的話可以充分利用make的隱含規則來編譯單個源文件的程序,

過程如下:

# ls
main.cpp
# make main
g++ main.cpp -o main
# ls
main main.cpp
# ./main
first is:1,second is:2

這裏注意,make的目標參數必須是源文件"main.cpp"去掉後綴之後的"main",等價於"g++ main.cpp -o main",這樣編譯的命令比較簡單。

其它

其它內容有待添加。

認真地工作並且思考,是最好的老師。在工作的過程中思考自己所缺乏的技術,以及學習他人的經驗,才能在工作中有所收穫。這篇文章原來是工作中我的一個同事加朋友的經驗,我站在這樣的經驗的基礎上,進行了這樣總結。

以上文章,如果有什麼問題或者更好的建議,都可以聯繫我,謝謝。_

作者:QuietHeart

Email:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章