用gdb配合内核转储文件瞬间定位段错误

前几天在写一个使用Huffman算法的文本压缩程序时被“段错误”折磨了好长时间。因为自己向来对内存的使用保持着“克勤克俭”的作风,所以总是被此类错误折磨的焦头难额。C语言的内存管理本来就是一个繁琐的工作,写代码时略有不慎便会出现诸如“段错误(吐核)”的运行时崩溃。

其实段错误是操作系统的一个内存保护机制,一般情况下某程序尝试访问其许可范围之外的内存空间时便会触发内核的“一般保护性异常”,内核便会向程序发送一个SIGSEGV(11)信号(无效的内存引用),而SIGSEGV信号默认handler的动作便是在终端上打印出名为“段错误”的出错信息,并产生Core(内核转储)文件,最后结束掉当前犯错的程序。

段错误的成因大致有以下几种:

  1. 程序访问了系统数据区,尤其是往系统保护的内存地址写数据。比如尝试对NULL指针进行解引用或者对其指向的内存写入数据(但是不见得所有的指针越界都会触发“段错误”);
  2. 内存访问越界(数组越界等);
  3. 无限的递归(导致栈溢出);
  4. 对malloc / calloc申请的堆内存二次释放(可能与glibc库版本有关);
  5. 由于操作系统的段保护机制,如果由于缓冲区溢出等错误导致对某段内存的非法访问也会触发;

另外还有一些大家平时不大注意的地方会导致段错误,例如使用标准库函数fclose对一个打开的文件关闭了多次也会导致段错误,同时终端可能会输出很多关于运行时库错误的信息。因为对使用malloc族函数申请的堆内存释放第二次的时候会触发段错误,所以我猜测fclose触发段错误的原因可能是对文件指针FILE *指向的内存二次释放时触发的段错误。而Valgrind检测的结果基本上证明了我的猜测,fclose引发了堆异常,错误被定位到了free函数。

==5715== Invalid free() / delete / delete[] / realloc()
==5715== at 0x4006D3D: free (vg_replace_malloc.c:446) ==5715== by 0x42C62853: fclose@@GLIBC_2.1 (in /lib/libc-2.14.90.so)
==5715== by 0x42C146B2: (below main) (in /lib/libc-2.14.90.so)
==5715== Address 0x4031028 is 0 bytes inside a block of size 352 free'd
==5715== at 0x4006D3D: free (vg_replace_malloc.c:446)
==5715== by 0x42C62853: fclose@@GLIBC_2.1 (in /lib/libc-2.14.90.so)
==5715== by 0x42C146B2: (below main) (in /lib/libc-2.14.90.so)
==5715==
==5715== HEAP SUMMARY:
==5715== in use at exit: 0 bytes in 0 blocks
==5715== total heap usage: 1 allocs, 2 frees, 352 bytes allocated
==5715==
==5715== All heap blocks were freed -- no leaks are possible
 

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。遗憾的是它只能检测到堆里的内存泄漏和越界访问,对于栈里的内存访问错误爱莫能助(如果你对这里堆栈等概念有疑问,请参阅百度百科词条“堆栈”,至于为什么不推荐维基百科…因为关于这个词条,百度百科的资料更全一些)。关于Valgrind的具体使用方法超出了本文讨论范围,有兴趣的读者请自行Google。另外,关于“段错误”的介绍不再赘述,毕竟我们现在讨论的重点不是“段错误”的前世今生。

言归正传,我们前面提到,当一个程序出现内存异常访问后会触发内核的“一般保护性异常”,内核会向程序发送一个SIGSEGV(11)信号(无效的内存引用),而SIGSEGV信号的默认handler的动作便是在终端上打印出名为“段错误”的出错信息,并产生Core(内核转储)文件,最后结束掉当前犯错的程序。重点在这里,那个所谓的Core(内核转储)文件是什么东西呢?通过查阅man文档(man 5 core)我们得知了在程序崩溃时,它一般会在目录下生成一个core文件。core文件是该程序在内存中的映象(同时还会有一些调试信息包含在内)。而某些系统默认设置是不生成core文件的,我们可以在终端下输入ulimit -a 命令查看设置。

hurley@hurley-fedora ~$ ulimit -a
core file size (blocks, -c) 0
 

可以看到我的当前设置(fedora 16)把core文件大小被限制为0了(不生成core文件)。我们可以在终端下执行ulimit -c 1024修改限制(我的系统在重启后该设置又被重置为0,所以每次调试前都要设置,不知道为什么…)。

设置好了以后我们来制造一个会触发“段错误”的程序吧…

代码很简单:

 #include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    char *p = (char *)0xB800;

    *p = 'a';

    return EXIT_SUCCESS;
}
 

很显然,编译运行后“段错误(吐核)”

 hurley@hurley-fedora segment-test$ gcc test.c -o test
hurley@hurley-fedora segment-test$ ./test
段错误(吐核)
 

我们使用gcc重新编译,这次要加上 -g 和 -rdynamic参数,-g我们都知道是加入调试信息,那 -rdynamic呢?它的作用是用来通知链接器将所有符号添加到动态符号表中(具体请查阅 man文档,关于链接这块的知识除了经典的《Linkers and loaders》之外,国产的《程序员的自我修养——链接、装载与库》也值得一读)。

hurley@hurley-fedora segment-test$ gcc -Wall -g -rdynamic test.c -o test
hurley@hurley-fedora segment-test$ ./test
段错误(吐核)
hurley@hurley-fedora segment-test$ ls
core.6864 test test.c
 

我们看到了程序所在目录下生成了一个名为core.6864的内核转储文件,就是它了。如果你没有找到这个文件,那么请往上翻找找我关于ulimit的说明。

接下来我们用gdb开始调试,命令行如下,注意最后要加上那个内核转储文件。

hurley@hurley-fedora segment-test$ gdb ./test core.6864
GNU gdb (GDB) Fedora (7.3.50.20110722-16.fc16)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
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 "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/hurley/segment-test/test...done.
[New LWP 6864]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048524 in main (argc=1, argv=0xbfb126b4) at test.c:26
26 *p = 'a';
Missing separate debuginfos, use: debuginfo-install glibc-2.14.90-24.fc16.9.i686
 

我们什么也没有做,gdb就定位到了这个*p = ‘a’; 触发了异常,并且明确的告诉了我们这行代码位于test.c的第26行,main函数里,同时gdb告诉我们程序接收到了11号信号退出,原因是段错误。

难不成就这么简单吗?是的,就这么简单。实际的使用中我发现有时候不需要内核转储文件gdb也能直接定位到错误点,这一点比起vc那个调试器来毫不逊色。

这篇文章到这里就告一段落了,貌似关键内容就这么一点。好吧,其实段错误的处理并不复杂,之前的纠结完全是因为自己没有掌握方法罢了…

如果你也被数不尽的“段错误”所纠结着 ,希望这篇文章能帮到你。如果你觉得这篇文章太水了……好吧,我承认,它确实很水…

时间: 2024-11-08 18:59:27

用gdb配合内核转储文件瞬间定位段错误的相关文章

嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误

    嵌入式 linux下利用backtrace追踪函数调用堆栈以及定位段错误 2015-05-27 14:19 184人阅读 评论(0) 收藏 举报  分类:   嵌入式(928)  一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈.   [cpp] vie

linux下利用backtrace追踪函数调用堆栈以及定位段错误

一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈.   [cpp] view plain copy    print? int backtrace(void **buffer,int size)   该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buf

如何使用crash工具分析Linux内核崩溃转储文件

本文首先介绍了 crash 的基本概念和安装方法,其次详细介绍了如何使用 crash 工具分析内核崩溃转储文件,包括各种常用调试命令的使用方法,最后以几个实际工作中遇到的真实案例向读者展示了 crash 的强大功能.在这篇文章中,既有详细的工具使用方法,又有丰富的实际http://www.aliyun.com/zixun/aggregation/7734.html">案例分析,相信您读过以后定会受益匪浅. 什么是 crash 如前文所述,当 linux 系统内核发生崩溃的时候,可以通过 k

Linux下调试段错误的方法[Segmentation Fault]--GDB

原文 1.段错误是什么? 段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址.访问了系统保护的内存地址.访问了只读的内存地址等等情况. A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation

GDB调试之core文件(如何定位到Segment fault)

core dump又叫核心转储,当程序运行过程中发生异常,程序异常退出时,由操作系统把程序当前的内存状况存储在一个core文件中,叫core dump.(内部实现是:linux系统中内存越界会收到SIGEGV信号,然后就会core dump) 在程序运行过程中,有的时候我们会遇到Segment fault(段错误)这样的错误.调速起来无从下手,因为没有任何的栈,trace信息输出.该种类型的错误往往与指针操作相关,往往可以通过这样的方式进行定位. 一 造成segment fault,产生core

Linux内核的文件预读详解

  Linux文件预读算法磁盘I/O性能的发展远远滞后于CPU和内存,因而成为现代计算机系统的一个主要瓶颈.预读可以有效的减少磁盘的寻道次数和应用程序的I/O等待时间,是改进磁盘读I/O性能的重要优化手段之一.本文作者是中国科学技术大学自动化系的博士生,他在1998年开始学习Linux,为了优化服务器的性能,他开始尝试改进Linux kernel,并最终重写了内核的文件预读部分,这些改进被收录到Linux Kernel 2.6.23及其后续版本中. 从寄存器.L1/L2高速缓存.内存.闪存,到磁

使用Eclipse Memory Analyzer进行堆转储文件分析

简介: Eclipse Memory Analyzer(MAT)是著名的跨平台集成开发环境 Eclipse Galileo 版本的 33 个组成项目中之一,它是一个功能丰富的 JAVA 堆 转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗.本文主要介绍如 何安装配置 Memory Analyzer,并结合一个实例,介绍如何利用 MAT 来进行堆转 储文件分析,找到内存泄露的根源. 概述 对于大型 JAVA 应用程序来说,再精细的测试也难以堵住所有的漏洞,即便我 们在测试阶段进行了大量卓有成

linux内核initrd文件自定义方法

  linux内核initrd文件自定义方法 重新编译内核后,可能加入了自定义的模块,就有可能需要修改init文件,而init文件就在initrd中,这里记录下操作步骤,以防遗忘. 1.  cp  /boot/initrd-3.2.img  /tmp/mylinux/initrd-3.2.img.gz     这里之所以进行改名,是因为initrd-3.2.img是经过gzip压缩过的,所以需要对其解压,但是gzip对解压的文件的文件后缀名又有要求,所以就先进行改名. 2.   gunzip  

怎么获得JVM的堆转储文件?

问题描述 我遇到一问题,就是在Eclipse上运行程序内存泄露了,使用的是自己安装的JVM(不是Eclipse自带的). 我现在正采用Eclipse Memory Analyzer 来分析内存泄露的原因,但是在分析前,必须要得到内存泄露一瞬间的堆转储文件. 现在问题是: 1. 怎么获得堆转储文件? 2.生成的文件在哪个目录? 3. 怎么设置JVM的参数,在哪里设置? 4.一个发生内存泄露时,JVM会保存堆转储文件吗?如果保存,会在哪个目录下呢? 问题补充:引用 解决方案 eclipse -> r