今天程序出现了如下的一条错误:

testrouter[17281]: segfault at 13a4 ip 0000003c0ac0920b sp 00007f1ebdd64bc0 error 4 in libc-2.15.so[3c0ac00000+20000]

查看错误类别是段错误,并且给出了堆栈指针指向的位置。产生段错误的原因一般有以下几点:

1、内存访问越界
        a) 由于使用错误的下标,导致数组访问越界
        b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
        c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2、多线程程序使用了线程不安全的函数。
        应该使用下面这些可重入的函数:
        asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n) ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c) getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c) fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c) getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3) getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n) nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3) getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c) getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c) getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)

3、多线程读写的数据未加锁保护。
           对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
4、非法指针
          a) 使用空指针
          b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.
5、堆栈溢出
       不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

Linux系统默认的情况下,是不产生段错误文件,可以通过下面的命令来查看系统默认段错误文件的大小:

ulimit -c
一般显示的结果是0。可以通过以下两条命令设置段错误文件大小为2048字节和大小不受限制:
ulimit -c 2048
ulimit -c unlimited
注意以上在终端中输入只是暂时的,若要永久生效需要把以上命令之一加入到/etc/profile或者/文件中。

core文件的作用如下:

      当我们的程序崩溃时,内核有可能把该程序当前内存映射到core文件里,方便程序员找到程序出现问题的地方。最常出现的,几乎所有C程序员都出现过的错误就是“段错误”。当一个程序崩溃时,在进程当前工作目录的core文件中复制了该进程的存储镜像。core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的。Linux/Unix收到下面的信号时候会产生core文件:

    SIGABRT     异常终止(abort)                         终止w/core
    SIGBUS         硬件故障                                     终止w/core
    SIGEMT        硬件故障                                      终止w/core
    SIGFPE          算术异常                                       终止w/core
    SIGILL           非法硬件指令                               终止w/core
   SIGIOT            硬件故障                                       终止w/core
   SIGQUIT         终端退出符                                   终止w/core
   SIGSEGV        无效存储访问                                终止w/core
   SIGSYS           无效系统调用                                 终止w/core
  SIGTRAP         硬件故障                                       终止w/core
  SIGXCPU        超过CPU限制(setrlimit)               终止w/core
  SIGXFSZ         超过文件长度限制(setrlimit)          终止w/core

下面对这几个信号做一个详细的说明:

• SIGABRT调用abort函数时产生此信号。进程异常终止。

• SIGBUS指示一个实现定义的硬件故障。

• SIGEMT指示一个实现定义的硬件故障。

EMT这一名字来自PDP-11的emulator trap 指令。

• SIGFPE此信号表示一个算术运算异常,例如除以0,浮点溢出等。

• SIGILL此信号指示进程已执行一条非法硬件指令。

4.3BSD由abort函数产生此信号。SIGABRT现在被用于此。

• SIGIOT这指示一个实现定义的硬件故障。

IOT这个名字来自于PDP-11对于输入/输出TRAP(input/output TRAP)指令的缩写。系统V的早期版本,由abort函数产生此信号。SIGABRT现在被用于此。

• SIGQUIT当用户在终端上按退出键(一般采用Ctrl-/)时,产生此信号,并送至前台进

程组中的所有进程。此信号不仅终止前台进程组(如SIGINT所做的那样),同时产生一个core文件。

• SIGSEGV指示进程进行了一次无效的存储访问。

名字SEGV表示“段违例(segmentation violation)”。

• SIGSYS指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,

但其指示系统调用类型的参数却是无效的。

• SIGTRAP指示一个实现定义的硬件故障。

此信号名来自于PDP-11的TRAP指令。

• SIGXCPUSVR4和4.3+BSD支持资源限制的概念。如果进程超过了其软C P U时间限制,则产生此信号。

• SIGXFSZ如果进程超过了其软文件长度限制,则SVR4和4.3+BSD产生此信号。

下面举例说明一个产生段错误的程序:

#include<stdio.h>

int main()
{

	 char *ptr="test";
	 strcpy(ptr,"TEST");
	  return 0;
}

gcc –g test.c -o core_test
编译完成后,运行程序./core_test,程序会中断,产生“段错误”这个提示。会看到当前目录下会出现一个core.15649的文件,下面我们利用这个文件进行错误的查找,利用调试工具gdb实现:

gdb ./core_test
显示出如下的信息:

GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6)
Copyright (C) 2010 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 "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/cyl/openwrt/switch/switch/test_core...done.
(gdb) 
调试的时候,我们在gdb下执行run

(gdb) run
Starting program: /home/cyl/openwrt/switch/switch/test_core 

Program received signal SIGSEGV, Segmentation fault.
0x00762f16 in __memcpy_ssse3 () from /lib/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686
从此信息中可以看出, 收到SIGSEGV信号,触发段错误,并提示地址0x00762f16、调用 _memcpy_ssse3()报的错,位于/lib/libc.so.6库中。

完成调试后,键入quit退出调试模式。

利用objdump反汇编查找段错误代码。

首先将反汇编的结果重定向到一个文件,执行下面的命令:

objdump -d ./test_core > segfault3Dump
然后我们查看segfault3Dump,查看产生段错误信息的位置。

由dmesg可以查看到产生段错误的地址和指令指针的地址还有栈的地址:

test_core[15649]: segfault at 80484c4 ip 00762f16 sp bfc9d7b8 error 7 in libc-2.12.so[62e000+191000]
然后正则匹配发生段错误的地方:

grep -n -A 10 -B 10 "80484c4" ./segfault3Dump 
结果如下:

[direwolf@direwolf switch]$ grep -n -A 10 -B 10 "80484c4" ./segfault3Dump 
121- 80483b9:	c7 04 24 80 95 04 08 	movl   $0x8049580,(%esp)
122- 80483c0:	ff d0                	call   *%eax
123- 80483c2:	c9                   	leave  
124- 80483c3:	c3                   	ret    
125-
126-080483c4 <main>:
127- 80483c4:	55                   	push   %ebp
128- 80483c5:	89 e5                	mov    %esp,%ebp
129- 80483c7:	83 e4 f0             	and    $0xfffffff0,%esp
130- 80483ca:	83 ec 20             	sub    $0x20,%esp
131: 80483cd:	c7 44 24 1c c4 84 04 	movl   $0x80484c4,0x1c(%esp)
132- 80483d4:	08 
133- 80483d5:	c7 44 24 08 05 00 00 	movl   $0x5,0x8(%esp)
134- 80483dc:	00 
135- 80483dd:	c7 44 24 04 c9 84 04 	movl   $0x80484c9,0x4(%esp)
136- 80483e4:	08 
137- 80483e5:	8b 44 24 1c          	mov    0x1c(%esp),%eax
138- 80483e9:	89 04 24             	mov    %eax,(%esp)
139- 80483ec:	e8 03 ff ff ff       	call   80482f4 <memcpy@plt>
140- 80483f1:	b8 00 00 00 00       	mov    $0x0,%eax
141- 80483f6:	c9    
可以看到段错误发生在main函数内,查看汇编代码可以知道, 对应的汇编指令是movl $0x80484c4,0x1c (%esp)。接下来对照c语言的源代码,我们使用下面的命令进行反汇编,先生成可调试文件,再反汇编。 gcc -g test_core.c

objdump -S a.out
可以得到下面的信息,我们这里仅仅找到main函数的入口:

080483c4 <main>:
 ************************************************************************/

#include<stdio.h>

int main()
{
 80483c4:	55                   	push   %ebp
 80483c5:	89 e5                	mov    %esp,%ebp
 80483c7:	83 e4 f0             	and    $0xfffffff0,%esp
 80483ca:	83 ec 20             	sub    $0x20,%esp

	 char *ptr="test";
 80483cd:	c7 44 24 1c c4 84 04 	movl   $0x80484c4,0x1c(%esp)
 80483d4:	08 
	 strcpy(ptr,"TEST");
 80483d5:	c7 44 24 08 05 00 00 	movl   $0x5,0x8(%esp)
 80483dc:	00 
 80483dd:	c7 44 24 04 c9 84 04 	movl   $0x80484c9,0x4(%esp)
 80483e4:	08 
 80483e5:	8b 44 24 1c          	mov    0x1c(%esp),%eax
 80483e9:	89 04 24             	mov    %eax,(%esp)
 80483ec:	e8 03 ff ff ff       	call   80482f4 <memcpy@plt>
	  return 0;
 80483f1:	b8 00 00 00 00       	mov    $0x0,%eax
}
找到 movl $0x80484c4,0x1c (%esp)这条汇编指令,可以知道,程序是在调用strcpy的时候发生错误!

以上两种方法就是查找段错误的常用的方法,出现段错误的时候,需要考虑一下因素:

1、出现段错误时,首先应该想到段错误的定义,从它出发考虑引发错误的原因。

2、在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为NULL。

3、在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等。

4、在访问变量时,注意变量所占地址空间是否已经被程序释放掉。

5、在处理变量时,注意变量的格式控制是否合理等。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐