天天看点

Linux环境下段错误的产生原因及调试方法小结1. 段错误是什么2. 段错误产生的原因3. 段错误信息的获取4. 段错误的调试方法5. 一些注意事项6. 参考资料列表

http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html

最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多、花费时间最长的问题就是著名的“段错误”(Segmentation Fault)。借此机会系统学习了一下,这里对Linux环境下的段错误做个小结,方便以后同类问题的排查与解决。

一句话来说,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。这里贴一个对于“段错误”的准确定义(参考Answers.com):

<a></a>

等等其他原因。

程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。

dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:

使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:

使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:

使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:

这个是看似最简单但往往很多情况下十分有效的调试方式,也许可以说是程序员用的最多的调试方式。简单来说,就是在程序的重要代码附近加上像printf这类输出信息,这样可以跟踪并打印出段错误在代码中可能出现的位置。

为了方便使用这种方法,可以使用条件编译指令#ifdef DEBUG和#endif把printf函数包起来。这样在程序编译时,如果加上-DDEBUG参数就能查看调试信息;否则不加该参数就不会显示调试信息。

 1、为了能够使用gdb调试程序,在编译阶段加上-g参数,以程序2.3为例:

2、使用gdb命令调试程序:

3、进入gdb后,运行程序:

从输出看出,程序2.3收到SIGSEGV信号,触发段错误,并提示地址0x001a306a、调用memcpy报的错,位于/lib/tls/i686/cmov/libc.so.6库中。

4、完成调试后,输入quit命令退出gdb:

1、仅当能确定程序一定会发生段错误的情况下使用。

2、当程序的源码可以获得的情况下,使用-g参数编译程序。

3、一般用于测试阶段,生产环境下gdb会有副作用:使程序运行减慢,运行不够稳定,等等。

4、即使在测试阶段,如果程序过于复杂,gdb也不能处理。

在4.2节中提到段错误会触发SIGSEGV信号,通过man 7 signal,可以看到SIGSEGV默认的handler会打印段错误出错信息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。

1、在一些Linux版本下,默认是不产生core文件的,首先可以查看一下系统core文件的大小限制:

2、可以看到默认设置情况下,本机Linux环境下发生段错误时不会自动生成core文件,下面设置下core文件的大小限制(单位为KB):

3、运行程序2.3,发生段错误生成core文件:

4、加载core文件,使用gdb工具进行调试:

从输出看出,同4.2.1中一样的段错误信息。

5、完成调试后,输入quit命令退出gdb:

1、适合于在实际生成环境下调试程序的段错误(即在不用重新发生段错误的情况下重现段错误)。

2、当程序很复杂,core文件相当大时,该方法不可用。

1、使用dmesg命令,找到最近发生的段错误输出信息:

其中,对我们接下来的调试过程有用的是发生段错误的地址:80484e0和指令指针地址:0018506a。

2、使用objdump生成二进制的相关信息,重定向到文件中:

其中,生成的segfault3Dump文件中包含了二进制文件的segfault3的汇编代码。

3、在segfault3Dump文件中查找发生段错误的地址:

通过对以上汇编代码分析,得知段错误发生main函数,对应的汇编指令是movl $0x80484e0,0x1c(%esp),接下来打开程序的源码,找到汇编指令对应的源码,也就定位到段错误了。

1、不需要-g参数编译,不需要借助于core文件,但需要有一定的汇编语言基础。

2、如果使用了gcc编译优化参数(-O1,-O2,-O3)的话,生成的汇编指令将会被优化,使得调试过程有些难度。

catchsegv命令专门用来扑获段错误,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。

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

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

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

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

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

1、http://www.docin.com/p-105923877.html

2、http://blog.chinaunix.net/space.php?uid=317451&amp;do=blog&amp;id=92412

继续阅读