利用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:quiet_heart000@126.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351