gdb调试coredump(使用篇)

什么是coredump

Coredump叫做核心转储,它是进程运行时在突然崩溃的那一刻的一个内存快照。操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下,会把进程此刻内存、寄存器状态、运行堆栈等信息转储保存在一个文件里。

该文件也是二进制文件,可以使用gdb、elfdump、objdump或者windows下的windebug、solaris下的mdb进行打开分析里面的具体内容。

注:core是在半导体作为内存材料前的线圈,当时用线圈当做内存材料,线圈叫做core。用线圈做的内存叫做core memory。

ulimit

虽然我们知道进程在coredump的时候会产生core文件,但是有时候却发现进程虽然core了,但是我们却找不到core文件。

在linux和Solaris下是需要进行设置的。

ulimit -c 可以设置core文件的大小,如果这个值为0.则不会产生core文件,这个值太小,则core文件也不会产生,因为core文件一般都比较大。

使用ulimit -c unlimited来设置无限大,则任意情况下都会产生core文件。

Windows下miniDump和FullDump的设置

Windows下需要在下面的注册表,

[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindowsError Reporting]

下面加一项LocalDumps,并做如下项配置:

上面的配置信息,可以通过打开注册表来手动添加。

首先,打开cmd或者运行程序(微软图标+R)

如上截图,可以通过图形界面手动添加这些注册表信息,然后windows系统在有进程crash的时候就会保存fulldump的文件。

或者通过reg文件的方式来进行注册

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps]

"DumpFolder"="F:\study_Test\Dump"

"DumpCount"=dword:a

"DumpType"=dword:1

如上,通过新建一个fulldump.reg的文件,保存上面内容,双击后,这些信息就会注册到注册表中。

gdb 调试coredump的简单示例

#include "stdio.h"

#include "stdlib.h"

void dumpCrash()

{

char *pStr = "test_content";

free(pStr);

}

int main()

{

dumpCrash();

return 0;

}

如上代码,pStr指针指向的是字符串常量,字符串常量是保存在常量区的,free释放常量区的内存肯定会导致coredump。

首先把上面的代码拷贝到linux机器上,保存为dumpTest.c文件,gcc编译

gcc -o dumpTestdumpTest.c

运行dumpTest产生core文件

生成core文件

如上,运行dumpTest的时候进程coredump了,但是没有产生core文件

如截图所示,系统设置的core文件大小为0,此时即使产生了coredump,也不会产生core文件。

如截图所示,ulimit -c unlimited设置core文件大小后,产生了名字为core的core文件。

此时生成的core文件名称都是统一的”core”命名。

自定义core文件的文件名

上面的设置只是使能了core dump功能,缺省情况下,内核在coredump时所产生的core文件放在与该程序相同的目录中,并且文件名固定为core。很显然,如果有多个程序产生core文件,或者同一个程序多次崩溃,就会重复覆盖同一个core文件。

我们通过修改kernel的参数,可以指定内核所生成的coredump文件的文件名。例如,Easwy使用下面的命令使kernel生成名字为core_filename_time_pid格式的core dump文件:

echo /usr/core_log/core_%e_%t_%p > /proc/sys/kernel/core_pattern

echo后面内容最好不要带上引号,有的系统会把引号也带入,如下:

这样,系统是不识别该内容的,也就会导致程序coredump而不会生成core文件。

如上截图,通过设置core文件的名称以及路径,程序coredump的时候就会在指定路径按照指定的规则命名生成core文件。

可以在core_pattern模板中使用变量见下面的列表:

%%单个%字符

%p所dump进程的进程ID

%u所dump进程的实际用户ID

%g所dump进程的实际组ID

%s导致本次core dump的信号

%t core dump的时间 (由1970年1月1日计起的秒数)

%h主机名

%e程序文件名

设置永久保存

上面截图可以看到,我后面再次执行生成coredump文件的时候实际上又再次设置了ulimit-c unlimited的,因为中间机器重启了。上面的设置都只是临时的,重启之后就需要重新设置,如何设置永久生效呢?

打开/etc/security/limits.conf 文件,在该文件的最后加上两行

#下面是我的配置

@root soft core unlimited

@root hard core unlimited

配置好后,放回原目录,重启reboot。

命名规则的修改在/proc/sys/kernel/core_pattern中也只是临时的,这个也是动态加载和生成的。永久修改在/etc/sysctl.conf文件中,在该文件的最后加上两行:

kernel.core_pattern = /var/core_log/core_%e_%t_%p

kernel.core_uses_pid = 0

可以使用以下命令,使修改结果马上生效。

#sysctl –p

如上截图,当前生成的core文件命名按照上面定义的规则加上了程序名称、coredump时间,进程ID等信息,并放到了指定目录/var/core_log

gdb调试coredump初步尝试

gdb打开core文件的格式为

gdb程序名(包含路径) core*(core文件名和路径),如下截图

如上,gdb打开core文件时,有显示没有调试信息,因为之前编译的时候没有带上-g选项,没有调试信息是正常的,实际上它也不影响调试core文件。因为调试core文件时,符号信息都来自符号表,用不到调试信息。如下为加上调试信息的效果。

查看coredump时的堆栈

查看堆栈使用bt或者where命令

如上,在带上调试信息的情况下,我们实际上是可以看到core的地方和代码行的匹配位置。

但往往正常发布环境是不会带上调试信息的,因为调试信息通常会占用比较大的存储空间,一般都会在编译的时候把-g选项去掉。

没有调试信息的情况下找core的代码行

如上截图,没有调试信息的情况下,打开coredump堆栈,并不会直接显示core的代码行。

此时,frame addr(帧数)或者简写如上,f 1 跳转到core堆栈的第1帧。因为第0帧是libc的代码,已经不是我们自己代码了。

disassemble打开该帧函数的反汇编代码。

#1 0x080483ec in dumpCrash ()

(gdb) disassemble

Dump of assembler code for function dumpCrash:

0x080483d4 <+0>: push %ebp

0x080483d5 <+1>: mov %esp,%ebp

0x080483d7 <+3>: sub $0x28,%esp

0x080483da <+6>: movl $0x80484d0,-0xc(%ebp)

0x080483e1 <+13>: mov -0xc(%ebp),%eax

0x080483e4 <+16>: mov %eax,(%esp)

0x080483e7 <+19>: call 0x80482f0

=> 0x080483ec <+24>: leave

0x080483ed <+25>: ret

End of assembler dump.

如上箭头位置表示coredump时该函数调用所在的位置

如上截图,shell echo free@plt |c++filt 去掉函数的名词修饰

不过上面的free使用去掉名词修饰效果和之前还是一样的。但是我们可以推测到这里是在调用free函数。

如此,我们就能知道我们coredump的位置,从而进一步能推断出coredump的原因。

当然,现实环境中,coredump的场景肯定远比这个复杂,都是逻辑都是一样的,我们需要先找到coredump的位置,再结合代码以及core文件推测coredump的原因。

寻找this指针和虚指针

#include "stdio.h"

#include

#include "stdlib.h"

using namespace std;

class base

{

public:

base();

virtual void test();

private:

char *basePStr;

};

class dumpTest : public base

{

public:

void test();

private:

char *childPStr;

};

base::base()

{

basePStr = "test_info";

}

void base::test()

{

cout<

}

void dumpTest::test()

{

cout<<"dumpTest"<

delete childPStr;

}

void dumpCrash()

{

char *pStr = "test_content";

free(pStr);

}

int main()

{

dumpTest dump;

dump.test();

return 0;

}

如上代码,实现了一个简单的基类和一个子类。在main函数里定义一个子类的实例化对象,并调用它的虚函数方法test,test里由于直接delete没有初始化的指针childPStr,肯定会造成coredump。本次我们就希望通过dump文件,找到子类dumpTest的this指针和虚函数指针。

和gcc一样,使用g++ -o DumpCppTest dumpTest.cpp编译cpp文件生成可执行程序。

./DumpCppTest 执行该程序,程序因为直接delete未初始化的指针,肯定会coredump。生成core文件如下

如上,使用gdb打开core文件,同时bt打开core的堆栈信息。

从堆栈可以看到,最后两帧为我们程序自己的函数,其他的都是libc的代码。

f 6 调到第6帧上,之后info frame查看堆栈寄存器信息。

如上截图所示,前一帧的栈寄存器地址是0xbf8cdb50,它的前一帧也就是main函数的位置,main函数里调用dump.test()的位置,那我们在这个地址上应该可以找到dump的this指针和它的虚指针,以及虚指针指向的虚函数表

如图所示,0xbf8cdb50地址指向的是前一帧保存dump信息的位置,0xbf8cdc14bf8cdb64就表示dump的this指针,而this指针指向的第一个8字节0x0804893008048958就表示虚指针,如上,通过x 0x0804893008048958看到_ZTV8dumpTest+8的内容。

shell echo_ZTV8dumpTest|c++filt 可以看到“vtable for dumpTest”的内容。这个就表示dumpTest的虚函数表。

从上面也可以看到,这个地址指向的是虚函数表+8的偏移位置,而这个位置0x000000000804876a 通过x 0x000000000804876a 可以看到,存储的内容就是

dumpTest::test() 函数。

这里也印证了,在继承关系里,基类的虚函数是在子类虚函数的前面。

如上,x 0x000000000804876a-4 就可以看到dumpTest的基类base的虚函数test的位置。

如上,在实际问题中,C++程序的很多coredump问题都是和指针相关的,很多segmentfault都是由于指针被误删或者访问空指针、或者越界等造成的,而这些都一般意味着正在访问的对象的this指针可能已经被破坏了,此时,我们通过去寻找函数对应的对象的this指针、虚指针能验证我们的推测。之后再结合代码寻找问题所在。

gdb 查看core进程的所有线程堆栈

#include

#include

#include

using namespace std;

#define NUM_THREADS 5 //线程数

int count = 0;

void* say_hello( void *args )

{

while(1)

{

sleep(1);

cout<<"hello..."<

if(NUM_THREADS == count)

{

char *pStr = "";

delete pStr;

}

}

} //函数返回的是函数指针,便于后面作为参数

int main()

{

pthread_t tids[NUM_THREADS]; //线程id

for( int i = 0; i < NUM_THREADS; ++i )

{

count = i+1;

int ret = pthread_create( &tids[i], NULL, say_hello,NULL); //参数:创建的线程id,线程参数,线程运行函数的起始地址,运行函数的参数

if( ret != 0 ) //创建线程成功返回0

{

cout << "pthread_create error:error_code=" << ret << endl;

}

}

pthread_exit( NULL ); //等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态

}

如上代码,简单示意C++多线程。

在linux下使用g++直接编译该cpp文件会报错,报错信息如下:

会报 undefined reference to `pthread_create' 的错误信息,解决办法如下:

使用 g++ -o MultiThreadDump MultiThread.cpp -lpthread 编译,编译参数上带上-lpthread即可。

运行./MultiThreadDump

由于上面代码里在count等于5的时候,会delete一个未初始化的指针,肯定会coredump。

如上,gdb打开coredump文件,能看到5个线程LWP的信息。

如何,查看每个线程的堆栈信息呢?

首先,info threads查看所有线程正在运行的指令信息

thread apply all bt打开所有线程的堆栈信息

查看指定线程堆栈信息:threadapply threadID bt,如:

thread apply 5 bt

进入指定线程栈空间

thread threadID如下:

如上截图所示,可以跳转到指定的线程中,并查看所在线程的正在运行的堆栈信息和寄存器信息。

总结:

如上,简单介绍了3种不同情况下的gdb调试coredump文件的情况,基本涵盖了调试coredump问题时的大部分会用到的gdb命令。

gdb调试coredump,大部分时候还是只能从core文件找出core的直观原因,但是更根本的原因一般还是需要结合代码一起分析当时进程的运行上下文场景,才能推测出程序代码问题所在。

因此gdb调试coredump也是需要经验的积累,只有有一定的功底和对于基础知识的掌握才能在一堆二进制符号的core文件中找出问题的所在。

欢迎工作一到五年的Java工程师朋友们加入Java架构开发: 854393687

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

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

推荐阅读更多精彩内容

  • 一、C语言基础 1、struct 的内存对齐和填充问题其实只要记住一个概念和三个原则就可以了: 一个概念:自然对齐...
    XDgbh阅读 2,154评论 1 38
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 28,982评论 8 265
  • 我的父亲就是个普通的银匠,从小耳边最熟悉的就是父亲手里拿着铁锤叮叮当当的敲击声。 一开始的小店客人不是很多,因为做...
    大熊好阅读 276评论 0 1
  • 跟着铃铛子画的步骤一步步画,怕毁图,就没给背景上色。 又一副画完成啦!手工相册还剩几页纸就搞掂画画的部分...
    Trenxy阅读 244评论 0 1
  • 这两天,叶子见人就笑盈盈的。“美女,掉蜜罐里了吧?嘴巴都合不拢了哦”同事老张打趣道。“你说是,就是!”叶子专注地看...
    子叶淡然阅读 384评论 2 3