引言

    在《I/O的效率比较》中,我们在修改图1程序的BUF_SIZE为8388608时,运行程序出现崩溃,如下图1:
    

    图1. 段错误
    一般而言,导致程序段错误的原因如下:
  • 内存访问出错,这类问题的典型代表就是数组越界。
  • 非法内存访问,出现这类问题主要是程序试图访问内核段内存而产生的错误。
  • 栈溢出, Linux默认给一个进程分配的栈空间大小为8M,因此你的数组开得过大的话会出现这种问题。
    首先我们先看一下系统默认分配的资源:
$ ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7884
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) 7884
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

    可以看到默认分配的栈大小为8M。而刚好我们的代码里的栈大小调到了8M,因此出现了段错误。
    那么有没有一种更直接明了的方法来识别和分析应用程序崩溃产生的bug呢? 有,那就是通过程序崩溃后产生的core文件。

core文件

何为core文件.

    core dump又叫内核转储, 在Unix系统中,核心映像(core image)就是“进程”执行当时的内存内容,当进程发生错误或收到“信号”而终止执行时,系统会将核心映像写入一个文件,以作为调试之用,这就是所谓的核心转储(core dump)。而core文件一般产生在进程的当前工作目录下。
    所以core文件中只是程序的内存映像, 如果在编译时加入调试信息的话,那么还会有调试信息。

如何产生core文件

    我们运行了a.out程序出现了“段错误”,但没有产生core文件。这是因为系统默认core文件的大小为0,所以没有创建。可以用ulimit命令查看和修改core文件的大小。 
$ ulimit -c 0     <--------- c选项指定修改core文件的大小
$ ulimit -c 1000   <--------指定了core文件大小为1000KB, 如果设置的大小小于core文件,则对core文件截取
$ ulimit -c unlimited   <---------------对core文件的大小不做限制

    如果想让修改永久生效,则需要修改配置文件,如.bash_profile、/etc/profile或/etc/security/limits.conf
    我们回到上面的代码演示,把core文件的大小调成不限制,再执行a.out,就可以在当前目录看到core文件了。
    
    另外补充一些资料,说明一些情况也不会产生core文件。
  1. 进程是设置-用户-ID,而且当前用户并非程序文件的所有者;
  2. 进程是设置-组-ID,而且当前用户并非该程序文件的组所有者;
  3. 用户没有写当前工作目录的许可权;
  4. 文件太大。core文件的许可权(假定该文件在此之前并不存在)通常是用户读/写,组读和其他读。

为什么需要core文件

    关于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的文件名叫"core"
    /proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展
  • 文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.PID
  • 为0则表示生成的core文件统一命名为core.
    如何修改这个文件的内容?
$ echo "0" > /proc/sys/kernel/core_uses_pid

    /proc/sys/kernel/core_pattern文件用于定制core的文件名,一般使用%配合不同的字符:
  • %p  出core进程的PID
  • %u  出core进程的UID
  • %s  造成core的signal号
  • %t  出core的时间,从1970-01-0100:00:00开始的秒数
  • %e  出core进程对应的可执行文件名

如何阅读core文件

    产生了core文件之后,就是如何查看core文件,并确定问题所在,进行修复。为此,我们不妨先来看看core文件的格式,多了解一些core文件。
$ file core.4244 
core.4244: ELF 64-bit LSB  core file x86-64, version 1 (SYSV), SVR4-style, from '/home/fireway/study/temp/a.out'

    首先可以明确一点,core文件的格式ELF格式,通过使用readelf -h命令来查看更详细内容
$ readelf -h core.4244
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              CORE (Core 文件)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  入口点地址:               0x0
  程序头起点:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       56 (字节)
  Number of program headers:         19
  节头大小:         0 (字节)
  节头数量:         0
  字符串表索引节头: 0

    了解了这些之后,我们来看看如何阅读core文件,并从中追查BUG。在Linux下,一般读取core的命令为:

$ gdb exec_file core_file

    使用gdb,先从可执行文件中读取符号表信息,然后读取core文件。如果不与可执行文件搅合在一起可以吗?答案是不行,因为core文件中没有符号表信息,无法进行调试,可以使用如下命令来验证:
$ objdump -x core.4244 | tail
 26 load16        00001000  00007ffff7ffe000  0000000000000000  0003f000  2**12
                  CONTENTS, ALLOC, LOAD
 27 load17        00801000  00007fffff7fe000  0000000000000000  00040000  2**12
                  CONTENTS, ALLOC, LOAD
 28 load18        00001000  ffffffffff600000  0000000000000000  00841000  2**12
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
no symbols    <----------------- 表明当前的ELF格式文件中没有符号表信息

    结合上面知识点,我们分别编译带-g的目标可执行mycat_debug和不带-g的目标可执行mycat,会发现mycat_debug的文件大小稍微大一些。使用readelf命令得出的结果比较报告,详细见附件-readelf报告.html
    各自执行产生的core文件,再使用objdump命令得出的结果比较报告,详细见附件-objdump报告.html
    最后我们各自使用gdb读取core文件,得出的结果比较报告,详细见附件-gdb_core报告.html

如果我们强制使用gdb mycat, 接着是带有调试信息的core文件,gdb会有什么提示呢?

Reading symbols from mycat...(no debugging symbols found)...done.
warning: core file may not match specified executable file.
[New LWP 2037]
Core was generated by `./mycat_debug'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400957 in main ()

    接下来重点来看,为啥产生段错误?
    使用gdb mycat_debug core.2037可见:
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from mycat_debug...done.
[New LWP 2037]
Core was generated by `./mycat_debug'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  main () at io1.c:16
16    int n = 0;

可知程序段错误,代码是int n = 0;这一句,我们来看当前栈信息:
(gdb) info f
Stack level 0, frame at 0x7ffc4b59d670:
 rip = 0x400957 in main (io1.c:16); saved rip = 0x7fc5c0d5aec5
 source language c.
 Arglist at 0x7ffc4b59d660, args: 
 Locals at 0x7ffc4b59d660, Previous frame's sp is 0x7ffc4b59d670
 Saved registers:
  rbp at 0x7ffc4b59d660, rip at 0x7ffc4b59d668

    其中可见指令指针rip指向地址为0x400957, 我们用x命令来查看内存地址中的值。具体帮助查看gdb调试 - 查看内存一节

(gdb) x/5i 0x400957 或者x/5i $rip
=> 0x400957 <main+26>: movl   $0x0,-0x800014(%rbp)
   0x400961 <main+36>: lea    -0x800010(%rbp),%rax
   0x400968 <main+43>: mov    $0x800000,%edx
   0x40096d <main+48>: mov    $0x0,%esi
   0x400972 <main+53>: mov    %rax,%rdi

    这条movl指令要把立即数0送到-0x800014(%rbp)这个地址去,其中rbp存储的是帧指针,其地址是 0x7ffc4b59d660,而-0x800014显然是个负数,十进制是8388628,且栈空间是由高地址向低地址延伸,见图2,那么n的栈地址就是-0x800014(%rbp),也就是$rbp-8388628。当我们尝试访问此地址时


图2. 典型的存储空间安排
 

(gdb) x /b 0x7ffc4ad9d64c
0x7ffc4ad9d64c: Cannot access memory at address 0x7ffc4ad9d64c

可以看到无法访问此内存地址,这是因为它已经超过了OS允许的范围。

ulimit命令参数及用法

功能说明:控制shell程序的资源。
补充说明:ulimit为shell内建指令,可用来控制shell执行程序的资源。
参  数: 
  • -a   显示目前资源限制的设定。
  • -c   设定core文件的最大值,单位为KB。
  • -d    <数据节区大小> 程序数据节区的最大值,单位为KB。
  • -f     <文件大小> shell所能建立的最大文件,单位为区块。
  • -H  设定资源的硬性限制,也就是管理员所设下的限制。
  • -m    <内存大小> 指定可使用内存的上限,单位为KB。
  • -n     <文件数目> 指定同一时间最多可开启的文件数。
  • -p     <缓冲区大小> 指定管道缓冲区的大小,单位512字节。
  • -s     <堆叠大小> 指定堆叠的上限,单位为KB。
  • -S  设定资源的弹性限制。
  • -t   指定CPU使用时间的上限,单位为秒。
  • -u    <程序数目> 用户最多可开启的程序数目。
  • -v    <虚拟内存大小>  指定可使用的虚拟内存上限,单位为KB。

参考

附件列表

结合程序崩溃后的core文件分析bug的更多相关文章

  1. 让linux中的程序崩溃时生成core文件

    当我们的linux程序崩溃的时候,常常会有这样的提示:    Segmentation fault (core dumped)    段错误 (核心已转储)    提示说生成了core文件,但是此功能 ...

  2. 设置程序崩溃时产生 core 文件的配置

    /* 不限制 core 文件的大小 */ ulimit -c unlimited /* 使用 pid 进行命名 */ echo " > /proc/sys/kernel/core_us ...

  3. Linux C程序异常退出怎么办——core文件帮你忙

    Linux C程序异常退出怎么办——core文件帮你忙 http://blog.csdn.net/zhu2695/article/details/51512138

  4. linux 程序无缘无故推出 没有core文件 broken pipe Resource temporarily unavailable

    问题 1. linux socket 服务端程序 无缘无故退出 . 2. 客户端大量访问服务端后,出现  Resource temporarily unavailable错误 问题分析: 1. 是否有 ...

  5. Android将程序崩溃信息保存本地文件

    大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个调试,所以在程序发布出去之后,如果出现了 ...

  6. android双进程守护,让程序崩溃后一定可以重启

    由于我们做的是机器人上的软件,而机器人是24小时不间断服务的,这就要求我们的软件不能退出到系统桌面.当然最好是能够做到程序能够不卡顿,不崩溃,自己不退出.由于我们引用了很多第三方的开发包,也不能保证他 ...

  7. Android 程序崩溃后的处理

    在应用发布以后,由于安卓机型的千差万别 ,可能会出现各种各样的问题,这时候如果我们可以将这些信息收集起来,并进行修改就很不错了.下面就来讨论一下怎么处理程序崩溃以后,错误信息的手机. Java中已经提 ...

  8. android程序崩溃后重启

    有时候由于测试不充分或者程序潜在的问题而导致程序异常崩溃,这个是令人无法接受的,在android中怎样捕获程序的异常崩溃,然后进行一些必要的处理或重新启动 应用这个问题困恼了我很久,今天终于解决了该问 ...

  9. C++ 程序崩溃时生成Dump文件

    #include <DbgHelp.h> //生产DUMP文件 int GenerateMiniDump(HANDLE hFile, PEXCEPTION_POINTERS pExcept ...

随机推荐

  1. C# 使用NPOI 导出Excel

    NPOI可以在没有安装Office的情况下对Word或Excel文档进行读写操作 下面介绍下NPOI操作Excel的方法 首先我们需要下载NPOI的程序集 下载地址 http://npoi.codep ...

  2. Linux Redis集群搭建与集群客户端实现(Python)

    硬件环境 本文适用的硬件环境如下 Linux版本:CentOS release 6.7 (Final) Redis版本: Redis已经成功安装,安装路径为/home/idata/yangfan/lo ...

  3. python 解析xml

    在工作中很多时候都要用到xml,使用这个时候难免会设计到解析他,然后就研究了一下python解析xml问题,看了很多东西,python有很多解析xml的包,但是也折腾我好一段时间,最后选择了这个方法. ...

  4. Linux系列教程(二)——Linux系统安装(手把手学安装centos6.8)

    在上一篇博客我们简单的介绍了Linux系统的起源,这篇博客我们将通过图示一步一步教大家如何安装Linux系统.注意这里我们选择安装的Linux系统是其一种发行版本 CentOS,这里给大家普及一个概念 ...

  5. 【框架学习与探究之AOP--Castle DynamicProxy】

    声明 本文欢迎转载,原始地址:http://www.cnblogs.com/DjlNet/p/7603654.html 前言 先说一点废话,在此之前博主也在早期就接触了或者看了些许AOP相关的文章,然 ...

  6. win10 uwp iot

    这篇文章主要译: https://msdn.microsoft.com/magazine/mt694090 有很多都是胡说,随便喷,但我不会理. https://blogs.msdn.microsof ...

  7. JDBC基本开发

    JDBC基本开发步骤 一:注册驱动 方式一:DriverManager.registerDriver(new Driver()); //存在注册两次问题,性能较低,消耗资源 方式二:Class.for ...

  8. 【转】Linux设备驱动--块设备(一)之概念和框架

    原文地址:Linux设备驱动--块设备(一)之概念和框架 基本概念   块设备(blockdevice) --- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时 ...

  9. setjmp和longjmp用法

    本文转自:http://blog.csdn.net/wuhong40/article/details/6155838,感谢原文作者. 前不久在阅读Quake3源代码的时候,看到一个陌生的函数:setj ...

  10. [IR] Concept Search and LDA

    重要的是通过实践更深入地了解贝叶斯思想,先浅浅地了解下LDA. From: http://blog.csdn.net/huagong_adu/article/details/7937616/ 传统方法 ...