所学,所得,所知
分类: LINUX
2017-03-03 13:34:00
原文地址:Linux下程序产生“段错误”的原因及其解决办法 作者:文峰聊书斋
参考原文: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逐步查找段错误:
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) |
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). |
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
$ 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
|
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; |
#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 现在说说对上面话的理解: 我认为函数或进程的运行最终都回归结尾系统调用,(呵呵,非官方,自己理解) 那么 “进程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的”,就是说大多数进程在运行期间是阻塞信号的,系统调用完再处理, 但是(以下引用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
38 } 这里我们就看第二种解决办法SA—restart 运行看结果:
^Cint handler 2
原文http://blog.csdn.net/ccccdddxxx/article/details/6308381 现在说说对上面话的理解: 我认为函数或进程的运行最终都回归结尾系统调用,(呵呵,非官方,自己理解) 那么 “进程正在执行某个系统调用,那么在该系统调用返回前信号是不会被递送的”,就是说大多数进程在运行期间是阻塞信号的,系统调用完再处理, 但是(以下引用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
38 } 这里我们就看第二种解决办法SA—restart 运行看结果:
^Cint handler 2 下面改程序:把程序24行注释掉:结果
^Cint handler 2
但我们和第一次相比也观察到很奇怪的结果 根据结果 比较,其实是第二次运行是把ctrl+c读入,而第一次就是运行啦信号处理函数,不把ctrl+c作为READ的读入, 在程序read之前不要输入,ctrl+c这样不会中断read输入后主程序就向下运行啦,这样就不在低速的系统调用即read中啦,所以再次ctrl+c结束; 下面改程序:把程序24行注释掉:结果
^Cint handler 2
但我们和第一次相比也观察到很奇怪的结果 根据结果 比较,其实是第二次运行是把ctrl+c读入,而第一次就是运行啦信号处理函数,不把ctrl+c作为READ的读入, #4 #5 0x0804884c in dummy_function () at f.c:31 #6 0x08048886 in main () at f.c:38 |
#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[i]); 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] |
xiaosuo@gentux test $ objdump -d a.out |
8048765: e8 02 fe ff ff call 804856c |
程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。
dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:
IP:instruction pointer.指令指针寄存器。IP寄存器是CPU内部的一个寄存器,用来存储将要执行的下一条指令的偏移量
SP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。
使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:
使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:
使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:
catchsegv命令专门用来扑获段错误,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。
panfeng@ubuntu:~/segfault$ catchsegv ./segfault3