您好,欢迎来到化拓教育网。
搜索
您的当前位置:首页Linux上CoreDump文件的形成和分析

Linux上CoreDump文件的形成和分析

来源:化拓教育网
Linux上CoreDump⽂件的形成和分析

原⽂:

http://baidutech.blog.51cto.com/4114344/904419

Core,⼜称之为Core Dump⽂件,是Unix/Linux操作系统的⼀种机制,对于线上服务⽽⾔,Core令⼈闻之⾊变,因为出Core的过程意味着服务暂时不能正常响应,需要恢复,并且随着吐Core进程的内存空间越⼤,此过程可能持续很长⼀段时间(例如当进程占⽤60G+以上内存时,完整Core⽂件需要15分钟才能完全写到磁盘上),这期间产⽣的流量损失,不可估量。

凡事皆有两⾯性,OS在出Core的同时,虽然会终⽌掉当前进程,但是也会保留下第⼀⼿的现场数据,OS仿佛是⼀架被按下快门的相机,⽽照⽚就是产出的Core⽂件。⾥⾯含有当进程被终⽌时内存、CPU寄存器等信息,可以供后续开发⼈员进⾏调试。

关于Core产⽣的原因很多,⽐如过去⼀些Unix的版本不⽀持现代Linux上这种GDB直接附着到进程上进⾏调试的机制,需要先向进程发送终⽌信号,然后⽤⼯具阅读core⽂件。在Linux上,我们就可以使⽤kill向⼀个指定的进程发送信号或者使⽤gcore命令来使其主动出Core并退出。如果从浅层次的原因上来讲,出Core意味着当前进程存在BUG,需要程序员修复。从深层次的原因上讲,是当前进程触犯了某些OS层级的保护机制,逼迫OS向当前进程发送诸如SIGSEGV(即signal 11)之类的信号, 例如访问空指针或数组越界出Core,实际上是触犯了OS的内存管理,访问了⾮当前进程的内存空间,OS需要通过出Core来进⾏警⽰,这就好像⼀个⼈⾝体内存在病毒,免疫系统就会通过发热来警⽰,并导致⼈体发烧是⼀个道理(有意思的是,并不是每次数组越界都会出Core,这和OS的内存管理中虚拟页⾯分配⼤⼩和边界有关,即使不出Core,也很有可能读到脏数据,引起后续程序⾏为紊乱,这是⼀种很难追查的BUG)。

说了这些,似乎感觉Core很强势,让⼈感觉缺乏控制⼒,其实不然。控制Core产⽣的⾏为和⽅式,有两个途径:

1.修改/proc/sys/kernel/core_pattern⽂件,此⽂件⽤于控制Core⽂件产⽣的⽂件名,默认情况下,此⽂件内容只有⼀⾏内容:“core”,此⽂件⽀持定制,⼀般使⽤%配合不同的字符,这⾥罗列⼏种:

%p 出Core进程的PID%u 出Core进程的UID%s 造成Core的signal号

%t 出Core的时间,从1970-01-0100:00:00开始的秒数%e 出Core进程对应的可执⾏⽂件名

2.Ulimit –C命令,此命令可以显⽰当前OS对于Core⽂件⼤⼩的,如果为0,则表⽰不允许产⽣Core⽂件。如果想进⾏修改,可以使⽤:Ulimit –cn

其中n为数字,表⽰允许Core⽂件体积的最⼤值,单位为Kb,如果想设为⽆限⼤,可以执⾏:Ulimit -cunlimited

产⽣了Core⽂件之后,就是如何查看Core⽂件,并确定问题所在,进⾏修复。为此,我们不妨先来看看Core⽂件的格式,多了解⼀些Core⽂件。

⾸先可以明确⼀点,Core⽂件的格式ELF格式,这⼀点可以通过使⽤readelf -h命令来证实,如下图:

从读出来的ELF头信息可以看到,此⽂件类型为Core⽂件,那么readelf是如何得知的呢?可以从下⾯的数据结构中窥得⼀⼆:

其中当值为4的时候,表⽰当前⽂件为Core⽂件。如此,整个过程就很清楚了。

了解了这些之后,我们来看看如何阅读Core⽂件,并从中追查BUG。在Linux下,⼀般读取Core的命令为:gdb exec_file core_file

使⽤GDB,先从可执⾏⽂件中读取符号表信息,然后读取Core⽂件。如果不与可执⾏⽂件搅合在⼀起可以吗?答案是不⾏,因为Core⽂件中没有符号表信息,⽆法进⾏调试,可以使⽤如下命令来验证:Objdump –x core_file | tail我们看到如下两⾏信息:SYMBOL TABLE:no symbols

表明当前的ELF格式⽂件中没有符号表信息。

为了解释如何看Core中信息,我们来举⼀个简单的例⼦:#include “stdio.h”int main(){

int stack_of[100000000];int b=1;int* a;*a=b;}

这段程序使⽤gcc –g a.c –o a进⾏编译,运⾏后直接会Core掉,使⽤gdb a core_file查看栈信息,可见其Core在了这⾏代码:int stack_of[100000000];

原因很明显,直接在栈上申请如此⼤的数组,导致栈空间溢出,触犯了OS对于栈空间⼤⼩的,所以出Core(这⾥是否出Core还和OS对栈空间的⼤⼩配置有关,⼀般为8M)。但是这⾥要明确⼀点,真正出Core的代码不是分配栈空间的int stack_of[100000000], ⽽是后⾯这句int b=1, 为何?出Core的⼀种原因是因为对内存的⾮法访问,在上⾯的代码中分配数组stack_of时并未访问它,但是在其后声明变量并赋值,就相当于进⾏了越界访问,继⽽出Core。为了解释得更详细些,让我们使⽤gdb来看⼀下出Core的地⽅,使⽤命令gdb a core_file可见:

可知程序出现了段错误“Segmentation fault”, 代码是int b=1这句。我们来查看⼀下当前的栈信息:

其中可见指令指针rip指向地址为0×400473, 我们来看下当前的指令是什么:

这条movl指令要把⽴即数1送到0xffffffffe8287bfc(%rbp)这个地址去,其中rbp存储的是帧指针,⽽0xffffffffe8287bfc很明显是⼀个负数,结果计算为-400000004。这就可以解释了:其中我们申请的int stack_of[100000000]占⽤400000000字节,b是int类型,占⽤4个字节,且栈空间是由⾼地址向低地址延伸,那么b的栈地址就是0xffffffffe8287bfc(%rbp),也就是$rbp-400000004。当我们尝试访问此地址时:

可以看到⽆法访问此内存地址,这是因为它已经超过了OS允许的范围。下⾯我们把程序进⾏改进:#include “stdio.h”int main(){

int* stack_of = malloc(sizeof(int)*100000000);int b=1;int* a;*a=b;}

使⽤gcc –O3 –g a.c –o a进⾏编译,运⾏后会再次Core掉,使⽤gdb查看栈信息,请见下图:

可见BUG出在第7⾏,也就是*a=b这句,这时我们尝试打印b的值,却发现符号表中找不到b的信息。为何?原因在于gcc使⽤了-O3参数,此参数可以对程序进⾏优化,⼀个负⾯效应是优化过程中会舍弃部分局部变量,导致调试时出现困难。在我们的代码中,b声明时即赋值,随后⽤于为*a赋值。优化后,此变量不再需要,直接为*a赋值为1即可,如果汇编级代码上讲,此优化可以减少⼀条MOV语句,节省⼀个寄存器。

此时我们的调试信息已经出现了⼀些扭曲,为此我们重新编译源程序,去掉-O3参数(这就解释了为何⼀些⼤型软件都会有debug版本存在,因为debug是未经优化的版本,包含了完整的符号表信息,易于调试),并重新运⾏,得到新的core并查看,如下图:

这次就⽐较明显了,b中的值没有问题,有问题的是a,其指向的地址是⾮法区域,也就是a没有分配内存导致的Core。当然,本例中的问题其实⾮常明显,⼏乎⼀眼就能看出来,但不妨碍它成为⼀个例⼦,⽤来解释在看Core过程中,需要注意的⼀些问题。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo9.cn 版权所有 赣ICP备2023008801号-1

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务