收藏官网首页
查看: 10419|回复: 0

Linux下程序产生“段错误”的原因及其解决办法

56

主题

144

帖子

1066

积分

金牌会员

Rank: 6Rank: 6

积分
1066
QQ
跳转到指定楼层
楼主
发表于 2017-2-17 10:46:39 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
注册成为机智云开发者,手机加虚拟设备快速开发
参考原文:http://blog.csdn.net/lxjames833539/article/details/6876716
产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址。
一 般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,它是一个48位的寄存器,其中的32位是保 存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序在cpu中的运行级别,指向的gdt是以64位为一个单 位的表,在这张表中就保存着程序运行的代码段、数据段的起始地址、与此相应的段限和页面交换、程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越 界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.

在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的:

1)访问系统数据区,尤其是往系统保护的内存地址写数据,最常见的就是给一个指针以0地址;
2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域。
另 外,缓存溢出也可能引起“段错误”,对于这种while(1) {do}的程序,这个问题最容易发生,多此sprintf或着strcat有可能将某个buff填满,溢出,所以每次使用前,最好memset一下,不过 要是一开始就是段错误,而不是运行了一会儿出现的,缓存溢出的可能性就比较小。
3 多线程程序使用了线程不安全的函数。
4多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
5  随 意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的 指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时 就很容易因为bus error而core dump.
6 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误

解决方法

1.利用gdb逐步查找段错误:


这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上“-g -rdynamic"的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下:

xiaosuo@gentux test $ gcc -g -rdynamic d.c
xiaosuo@gentux test $ gdb ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) r
Starting program: /home/xiaosuo/test/a.out

Program received signal SIGSEGV, Segmentation fault.
0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;
(gdb)

  好像不用一步步调试我们就找到了出错位置d.c文件的第4行,其实就是如此的简单。

从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7 signal),我们知道SIGSEGV默认的处理动作是打印“段错误”的出错信息,并产生Core文件,由此我们又产生了方法二。

2.分析Core文件:
发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行.
gdb [exec file] [core file]


Core文件是什么呢?

The default action of certain signals is to cause a process to terminate and produce a core dump file, a disk file containing an image of the process's memory at the time of termination. A list of the signals which cause a process to dump core can be found in signal(7).

 以上资料摘自man page(man 5 core)。不过奇怪了,我的系统上并没有找到core文件。后来,忆起为了减少系统上的拉圾文件的数量(本人有些洁癖,这也是我喜欢Gentoo的原因 之一),禁止了core文件的生成,查看了以下果真如此,将系统的core文件的大小限制在512K大小,再试:
xiaosuo@gentux test $ ulimit -c
0
xiaosuo@gentux test $ ulimit -c 1000
xiaosuo@gentux test $ ulimit -c
1000
xiaosuo@gentux test $ ./a.out
段错误 (core dumped)
xiaosuo@gentux test $ ls
a.out core d.c f.c g.c pango.c test_iconv.c test_regex.c

配置操作系统使其产生core文件

首 先通过ulimit命令查看一下系统是否配置支持了dump core的功能。通过ulimit -c或ulimit -a,可以查看core file大小的配置情况,如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。若发生了段错误,但没有core dump,是由于系统禁止core文件的生成。

解决方法:
$ulimit -c unlimited  (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)

# ulimit -c

unlimited


$ ulimit -a
time(seconds)        unlimited
file(blocks)         unlimited
data(kb)             unlimited
stack(kb)            8192
coredump(blocks)     unlimited
memory(kb)           unlimited
locked memory(kb)    64
process              1394
nofiles              1024
vmemory(kb)          unlimited
locks                unlimited


  core文件终于产生了,用gdb调试一下看看吧:
xiaosuo@gentux test $ gdb ./a.out core
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".


warning: Can't read pathname for load map: 输入/输出错误.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;

  还是一步就定位到了错误所在地,佩服一下Linux/Unix系统的此类设计。
接着考虑下去,以前用windows系统下的ie的时侯,有时打开某些网页,会出现“运行时错误”,这个时侯如果恰好你的机器上又装有windows的编译器的话,他会弹出来一个对话框,问你是否进行调试,如果你选择是,编译器将被打开,并进入调试状态,开始调试。

  Linux下如何做到这些呢?我的大脑飞速地旋转着,有了,让它在SIGSEGV的handler中调用gdb,于是第三个方法又诞生了:

3.段错误时启动调试:

#include
#include
#include
#include

void dump(int signo)
{
char buf[1024];
char cmd[1024];
FILE *fh;

snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
if(!(fh = fopen(buf, "r")))
exit(0);
if(!fgets(buf, sizeof(buf), fh))
exit(0);
fclose(fh);
if(buf[strlen(buf) - 1] == 'n')
buf[strlen(buf) - 1] = '';
snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
system(cmd);

exit(0);
}

void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}

int
main (void)
{
signal(SIGSEGV, &dump);//SIGSEGV是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号
dummy_function ();

return 0;
}

  编译运行效果如下:
xiaosuo@gentux test $ gcc -g -rdynamic f.c
xiaosuo@gentux test $ ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

Attaching to program: /home/xiaosuo/test/a.out, process 9563
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xffffe410 in __kernel_vsyscall ()
(gdb) bt
#0 0xffffe410 in __kernel_vsyscall ()
#1 0xb7ee4b53 in waitpid () from /lib/libc.so.6
#2 0xb7e925c9 in strtold_l () from /lib/libc.so.6
原文http://blog.csdn.net/ccccdddxxx/article/details/6308381
信号是异步的,它会在程序的任何地方发生。由此程序正常的执行路径被打破,去执行信号处理函数。
一般情况下 ,进 程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的。但慢速系统调用除外,如读写终端、网络、磁盘,以及wait和pause。这些系统 调用都会返回-1,errno置为EINTR当系统调用被中断时,我们可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART)。
现在说说对上面话的理解:
我认为函数或进程的运行最终都回归结尾系统调用,(呵呵,非官方,自己理解)
那么 “进程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的”,就是说大多数进程在运行期间是阻塞信号的,系统调用完再处理,
但是(以下引用APUE):如果在进程执行一个低速系统而阻塞期间捕捉到一个信号,该系统调用被终端不再继续执行
When a system call is slow and a signal arrives while it was blocked,waiting for something, the call is aborted and returns -EINTR ,so that the library function will return -1 and set errno to EINTR . Just before the system call returns, the user program'ssignal handler is called.
(So, what is "slow"? Mostly those calls that can block forever waitingfor external events; read and write to terminal devices, but notread and write to disk devices, wait , pause .)
This means that a system call can return an error while nothing waswrong. Usually one will want to redo the system call. That can beautomated by installing the signal handler using a call to sigaction with the SA_RESTART flag set.The effect is that upon an interrupt the system call is aborted,the user program's signal handler is called, and afterwardsthe system call is restarted from the beginning.
我们可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART),
这是是低速系统调用被信号中断的解决办法,1循环2SA——RESTART
好啦,实验代码:
#include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9
10 void int_handler (int signum)
11 {
12         printf ("int handler %d/n",signum);
13 }
14
15 int main(int argc, char **argv)
16 {
17         char buf[100];
18         ssize_t ret;
19         struct sigaction oldact;
20         struct sigaction act;
21
22         act.sa_handler = int_handler;
23         act.sa_flags=0;
24         act.sa_flags |= SA_RESTART;
25         sigemptyset(&act.sa_mask);
26         if (-1 == sigaction(SIGINT,&act,&oldact))
27         {
28                 printf("sigaction failed!/n");
29                 return -1;
30         }
31
32         bzero(buf,100);
33
34         ret = read(STDIN_FILENO,buf,10);
35         if (ret == -1)
36         {
37                 printf ("read error %s/n", strerror(errn    o));
38         }
39         printf ("read %d bytes, content is %s/n",ret,buf    );
40         sleep (10);
41         return 0;
42 }
这里我们就看第二种解决办法SA—restart
运行看结果:
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
hgfd
read 5 bytes, content is hgfd

^Cint handler 2
原文http://blog.csdn.net/ccccdddxxx/article/details/6308381
信号是异步的,它会在程序的任何地方发生。由此程序正常的执行路径被打破,去执行信号处理函数。
一般情况下 ,进 程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的。但慢速系统调用除外,如读写终端、网络、磁盘,以及wait和pause。这些系统 调用都会返回-1,errno置为EINTR当系统调用被中断时,我们可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART)。
现在说说对上面话的理解:
我认为函数或进程的运行最终都回归结尾系统调用,(呵呵,非官方,自己理解)
那么 “进程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的”,就是说大多数进程在运行期间是阻塞信号的,系统调用完再处理,
但是(以下引用APUE):如果在进程执行一个低速系统而阻塞期间捕捉到一个信号,该系统调用被终端不再继续执行
When a system call is slow and a signal arrives while it was blocked,waiting for something, the call is aborted and returns -EINTR ,so that the library function will return -1 and set errno to EINTR . Just before the system call returns, the user program'ssignal handler is called.
(So, what is "slow"? Mostly those calls that can block forever waitingfor external events; read and write to terminal devices, but notread and write to disk devices, wait , pause .)
This means that a system call can return an error while nothing waswrong. Usually one will want to redo the system call. That can beautomated by installing the signal handler using a call to sigaction with the SA_RESTART flag set.The effect is that upon an interrupt the system call is aborted,the user program's signal handler is called, and afterwardsthe system call is restarted from the beginning.
我们可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART),
这是是低速系统调用被信号中断的解决办法,1循环2SA——RESTART
好啦,实验代码:
#include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9
10 void int_handler (int signum)
11 {
12         printf ("int handler %d/n",signum);
13 }
14
15 int main(int argc, char **argv)
16 {
17         char buf[100];
18         ssize_t ret;
19         struct sigaction oldact;
20         struct sigaction act;
21
22         act.sa_handler = int_handler;
23         act.sa_flags=0;
24         act.sa_flags |= SA_RESTART;
25         sigemptyset(&act.sa_mask);
26         if (-1 == sigaction(SIGINT,&act,&oldact))
27         {
28                 printf("sigaction failed!/n");
29                 return -1;
30         }
31
32         bzero(buf,100);
33
34         ret = read(STDIN_FILENO,buf,10);
35         if (ret == -1)
36         {
37                 printf ("read error %s/n", strerror(errn    o));
38         }
39         printf ("read %d bytes, content is %s/n",ret,buf    );
40         sleep (10);
41         return 0;
42 }
这里我们就看第二种解决办法SA—restart
运行看结果:
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
^Cint handler 2
hgfd
read 5 bytes, content is hgfd

^Cint handler 2
在程序read之前不要输入,ctrl+c这样不会中断read输入后主程序就向下运行啦,这样就不在低速的系统调用即read中啦,所以再次ctrl+c结束;
下面改程序:把程序24行注释掉:结果
^Cint handler 2
read error Interrupted system call
read -1 bytes, content is
程序立即结束啦,
但我们和第一次相比也观察到很奇怪的结果
根据结果 比较,其实是第二次运行是把ctrl+c读入,而第一次就是运行啦信号处理函数,不把ctrl+c作为READ的读入,

在程序read之前不要输入,ctrl+c这样不会中断read输入后主程序就向下运行啦,这样就不在低速的系统调用即read中啦,所以再次ctrl+c结束;
下面改程序:把程序24行注释掉:结果
^Cint handler 2
read error Interrupted system call
read -1 bytes, content is
程序立即结束啦,
但我们和第一次相比也观察到很奇怪的结果
根据结果 比较,其实是第二次运行是把ctrl+c读入,而第一次就是运行啦信号处理函数,不把ctrl+c作为READ的读入,

#3 0x08048830 in dump (signo=11) at f.c:22
#4
#5 0x0804884c in dummy_function () at f.c:31
#6 0x08048886 in main () at f.c:38

  怎么样?是不是依旧很酷?
 以上方法都是在系统上有gdb的前提下进行的,如果没有呢?其实glibc为我们提供了此类能够dump栈内容的函数簇,详见 /usr/include/execinfo.h(这些函数都没有提供man page,难怪我们找不到),另外你也可以通过gnu的手册进行学习。
4.利用backtrace和objdump进行分析:
  重写的代码如下:
#include
#include
#include
#include

/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}

void dump(int signo)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames.n", size);

for (i = 0; i < size; i++)
printf ("%sn", strings);

free (strings);

exit(0);
}

int
main (void)
{
signal(SIGSEGV, &dump);//SIGSEGV是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号
dummy_function ();

return 0;
}

  编译运行结果如下:
xiaosuo@gentux test $ gcc -g -rdynamic g.c
xiaosuo@gentux test $ ./a.out
Obtained 5 stack frames.
./a.out(dump+0x19) [0x80486c2]
[0xffffe420]
./a.out(main+0x35) [0x804876f]
/lib/libc.so.6(__libc_start_main+0xe6) [0xb7e02866]
./a.out [0x8048601]

  这次你可能有些失望,似乎没能给出足够的信息来标示错误,不急,先看看能分析出来什么吧,用objdump反汇编程序,找到地址0x804876f对应的代码位置:
xiaosuo@gentux test $ objdump -d a.out

8048765: e8 02 fe ff ff call 804856c

  我们还是找到了在哪个函数(dummy_function)中出错的,信息已然不是很完整,不过有总比没有好的啊!


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump. (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)
///////////////////
3. 段错误信息的获取
程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。
3.1 dmesg
dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:
panfeng@ubuntu:~/segfault$ dmesg
[ 2329.479037] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb4000+13e000]
IP:instruction pointer.指令指针寄存器。IP寄存器是CPU内部的一个寄存器,用来存储将要执行的下一条指令的偏移量
SP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。


3.2 -g
使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
3.3 nm
使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:
[url=]Linux下段错误的原因和调试方式">[/url]panfeng@ubuntu:~/segfault$ nm segfault33.4 ldd
使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:
panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 => (0x00e08000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00675000)
/lib/ld-linux.so.2 (0x00482000)
4.5 使用catchsegv
catchsegv命令专门用来扑获段错误,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。
[url=]Linux下段错误的原因和调试方式">[/url]
panfeng@ubuntu:~/segfault$ catchsegv ./segfault3



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////官方linux内核里
//文件目录在kernel/tools/perf/util/util.c
#ifdef BACKTRACE_SUPPORT
#include
#endif
/* Obtain a backtrace and print it to stdout. */
#ifdef BACKTRACE_SUPPORT
void dump_stack(void)
{
    void *array[16];
    size_t size = backtrace(array, ARRAY_SIZE(array));
    char **strings = backtrace_symbols(array, size);
    size_t i;

    printf("Obtained %zd stack frames.\n", size);

    for (i = 0; i < size; i++)
        printf("%s\n", strings);

    free(strings);
}
#else
void dump_stack(void) {}
#endif
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

加入Q群 返回顶部

版权与免责声明 © 2006-2024 Gizwits IoT Technology Co., Ltd. ( 粤ICP备11090211号 )

快速回复 返回顶部 返回列表