在Linux下编译块时检测到错误时出现段错误应该怎么调试

最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多、花费时间最长的问题就是著名的&段错误&(Segmentation Fault)。借此机会系统学习了一下,这里对Linux环境下的段错误做个小结,方便以后同类问题的排查与解决。
1. 段错误是什么
一句话来说,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。这里贴一个对于&段错误&的准确定义(参考Answers.com):
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 fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors.Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy.On Unix-like operating systems, a process that accesses invalid memory receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.
2. 段错误产生的原因
2.1 访问不存在的内存地址
#include&stdio.h&#include&stdlib.h&void main(){
int *ptr = NULL;
*ptr = 0;}
2.2 访问系统保护的内存地址
#include&stdio.h&#include&stdlib.h&void main(){
int *ptr = (int *)0;
*ptr = 100;}
2.3 访问只读的内存地址
#include&stdio.h&#include&stdlib.h&#include&string.h&void main(){
char *ptr = "test";
strcpy(ptr, "TEST");}
2.4 栈溢出
#include&stdio.h&#include&stdlib.h&void main(){
等等其他原因。
3. 段错误信息的获取
程序发生段错误时,提示信息很少,下面有几种查看段错误的发生信息的途径。
dmesg可以在应用程序crash掉时,显示内核中保存的相关信息。如下所示,通过dmesg命令可以查看发生段错误的程序名称、引起段错误发生的内存地址、指令指针地址、堆栈指针地址、错误代码、错误原因等。以程序2.3为例:
panfeng@ubuntu:~/segfault$ dmesg[ ] segfault3[2700]: segfault at 80484e0 ip 00d2906a sp bfbbec3c error 7 in libc-2.10.1.so[cb]
使用gcc编译程序的源码时,加上-g参数,这样可以使得生成的二进制文件中加入可以用于gdb调试的有用信息。以程序2.3为例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
使用nm命令列出二进制文件中的符号表,包括符号地址、符号类型、符号名等,这样可以帮助定位在哪里发生了段错误。以程序2.3为例:
panfeng@ubuntu:~/segfault$ nm segfault308049f20 d _DYNAMIC08049ff4 d _GLOBAL_OFFSET_TABLE_080484dc R _IO_stdin_used
w _Jv_RegisterClasses08049f10 d __CTOR_END__08049f0c d __CTOR_LIST__08049f18 D __DTOR_END__08049f14 d __DTOR_LIST__080484ec r __FRAME_END__08049f1c d __JCR_END__08049f1c d __JCR_LIST__ A __bss_start0804a00c D __data_start t __do_global_ctors_aux t __do_global_dtors_aux D __dso_handle
w __gmon_start__0804848a T __i686.get_pc_thunk.bx08049f0c d __init_array_end08049f0c d __init_array_start T __libc_csu_fini T __libc_csu_init
U __libc_start_main@@GLIBC_2.0 A _edata0804a01c A _end080484bc T _fini R _fp_hw080482bc T _init T _start b completed.69900804a00c W data_start b dtor_idx.6992 t frame_dummy T main
U memcpy@@GLIBC_2.0
使用ldd命令查看二进制程序的共享链接库依赖,包括库的名称、起始地址,这样可以确定段错误到底是发生在了自己的程序中还是依赖的共享库中。以程序2.3为例:
panfeng@ubuntu:~/segfault$ ldd ./segfault3
linux-gate.so.1 =&
(0x00e08000)
libc.so.6 =& /lib/tls/i686/cmov/libc.so.6 (0x)
/lib/ld-linux.so.2 (0x)
4. 段错误的调试方法
4.1 使用printf输出信息
这个是看似最简单但往往很多情况下十分有效的调试方式,也许可以说是程序员用的最多的调试方式。简单来说,就是在程序的重要代码附近加上像printf这类输出信息,这样可以跟踪并打印出段错误在代码中可能出现的位置。
为了方便使用这种方法,可以使用条件编译指令#ifdef DEBUG和#endif把printf函数包起来。这样在程序编译时,如果加上-DDEBUG参数就能查看调试信息;否则不加该参数就不会显示调试信息。
4.2 使用gcc和gdb
4.2.1 调试步骤
&1、为了能够使用gdb调试程序,在编译阶段加上-g参数,以程序2.3为例:
panfeng@ubuntu:~/segfault$ gcc -g -o segfault3 segfault3.c
2、使用gdb命令调试程序:
panfeng@ubuntu:~/segfault$ gdb ./segfault3 GNU gdb (GDB) 7.0-ubuntuCopyright (C) 2009 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 "i486-linux-gnu".For bug reporting instructions, please see:&http://www.gnu.org/software/gdb/bugs/&...Reading symbols from /home/panfeng/segfault/segfault3...done.(gdb)
3、进入gdb后,运行程序:
Starting program: /home/panfeng/segfault/segfault3 Program received signal SIGSEGV, Segmentation fault.0x001a306a in memcpy () from /lib/tls/i686/cmov/libc.so.6(gdb)
从输出看出,程序2.3收到SIGSEGV信号,触发段错误,并提示地址0x001a306a、调用memcpy报的错,位于/lib/tls/i686/cmov/libc.so.6库中。
4、完成调试后,输入quit命令退出gdb:
(gdb) quit
A debugging session is active.
Inferior 1 [process 3207] will be killed.Quit anyway? (y or n) y
4.2.2 适用场景
1、仅当能确定程序一定会发生段错误的情况下使用。
2、当程序的源码可以获得的情况下,使用-g参数编译程序。
3、一般用于测试阶段,生产环境下gdb会有副作用:使程序运行减慢,运行不够稳定,等等。
4、即使在测试阶段,如果程序过于复杂,gdb也不能处理。
4.3 使用core文件和gdb
在4.2节中提到段错误会触发SIGSEGV信号,通过man&7&signal,可以看到SIGSEGV默认的handler会打印段错误出错信息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。
4.3.1 调试步骤
1、在一些Linux版本下,默认是不产生core文件的,首先可以查看一下系统core文件的大小限制:
panfeng@ubuntu:~/segfault$ ulimit -c0
2、可以看到默认设置情况下,本机Linux环境下发生段错误时不会自动生成core文件,下面设置下core文件的大小限制(单位为KB):
panfeng@ubuntu:~/segfault$ ulimit -c 1024panfeng@ubuntu:~/segfault$ ulimit -c1024
3、运行程序2.3,发生段错误生成core文件:
panfeng@ubuntu:~/segfault$ ./segfault3段错误 (core dumped)
4、加载core文件,使用gdb工具进行调试:
panfeng@ubuntu:~/segfault$ gdb ./segfault3 ./core GNU gdb (GDB) 7.0-ubuntuCopyright (C) 2009 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 "i486-linux-gnu".For bug reporting instructions, please see:&http://www.gnu.org/software/gdb/bugs/&...Reading symbols from /home/panfeng/segfault/segfault3...done.warning: Can't read pathname for load map: 输入/输出错误.Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.Loaded symbols for /lib/tls/i686/cmov/libc.so.6Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.Loaded symbols for /lib/ld-linux.so.2Core was generated by `./segfault3'.Program terminated with signal 11, Segmentation fault.#0
0x0018506a in memcpy () from /lib/tls/i686/cmov/libc.6
从输出看出,同4.2.1中一样的段错误信息。
5、完成调试后,输入quit命令退出gdb:
(gdb) quit
4.3.2 适用场景
1、适合于在实际生成环境下调试程序的段错误(即在不用重新发生段错误的情况下重现段错误)。
2、当程序很复杂,core文件相当大时,该方法不可用。
4.4 使用objdump
4.4.1 调试步骤
1、使用dmesg命令,找到最近发生的段错误输出信息:
panfeng@ubuntu:~/segfault$ dmesg... ...[] segfault3[3320]: segfault at 80484e0 ip 0018506a sp bfc1cd6c error 7 in libc-2.10.1.so[e000]
其中,对我们接下来的调试过程有用的是发生段错误的地址:80484e0和指令指针地址:0018506a。
2、使用objdump生成二进制的相关信息,重定向到文件中:
panfeng@ubuntu:~/segfault$ objdump -d ./segfault3 & segfault3Dump
其中,生成的segfault3Dump文件中包含了二进制文件的segfault3的汇编代码。
3、在segfault3Dump文件中查找发生段错误的地址:
panfeng@ubuntu:~/segfault$ grep -n -A 10 -B 10 "80484e0" ./segfault3Dump 121- 80483df:
*%eax122- 80483e1:
123- 80483e2:
124- 80483e3:
nop125-126- &main&:127- 80483e4:
%ebp128- 80483e5:
%esp,%ebp129- 80483e7:
$0xfffffff0,%esp130- 80483ea:
$0x20,%esp131: 80483ed:
c7 44 24 1c e0 84 04
$0xx1c(%esp)132- 80483f4:
08 133- 80483f5:
b8 e5 84 04 08
$0x80484e5,%eax134- 80483fa:
c7 44 24 08 05 00 00
$0x5,0x8(%esp)135- 8048401:
00 136- 8048402:
89 44 24 04
%eax,0x4(%esp)137- 8048406:
8b 44 24 1c
0x1c(%esp),%eax138- 804840a:
%eax,(%esp)139- 804840d:
e8 0a ff ff ff
804831c &memcpy@plt&140- 8048412:
141- 8048413:
通过对以上汇编代码分析,得知段错误发生main函数,对应的汇编指令是movl $0xx1c(%esp),接下来打开程序的源码,找到汇编指令对应的源码,也就定位到段错误了。
4.4.2 适用场景
1、不需要-g参数编译,不需要借助于core文件,但需要有一定的汇编语言基础。
2、如果使用了gcc编译优化参数(-O1,-O2,-O3)的话,生成的汇编指令将会被优化,使得调试过程有些难度。
4.5 使用catchsegv
catchsegv命令专门用来扑获段错误,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。
panfeng@ubuntu:~/segfault$ catchsegv ./segfault3Segmentation fault (core dumped)*** Segmentation faultRegister dump: EAX:
EBX: 00fb3ff4
EBP: bfb7ad38
ESP: bfb7ad0c EIP: 00ee806a
SS: 007b Trap: 0000000e
ESP/signal: bfb7ad0c
CR2: Backtrace:/lib/libSegFault.so[0x3b606f]??:0(??)[0xc76400]/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xe89b56]/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8048351]Memory map:73000 r-xp :01 157 /lib/ld-2.10.1.so74000 r--p :01 157 /lib/ld-2.10.1.so75000 rw-p :01 157 /lib/ld-2.10.1.so003b0 r-xp :01 13105 /lib/libSegFault.so003b0 r--p :01 13105 /lib/libSegFault.so003b0 rw-p :01 13105 /lib/libSegFault.so00c00 r-xp :00 0 [vdso]00e0d000-00e29000 r-xp :01 4817 /lib/libgcc_s.so.100ea000 r--p :01 4817 /lib/libgcc_s.so.100e2a000-00e2b000 rw-p :01 4817 /lib/libgcc_s.so.100e00 r-xp :01 1800 /lib/tls/i686/cmov/libc-2.10.1.so00fb00 ---p :01 1800 /lib/tls/i686/cmov/libc-2.10.1.so00fb00 r--p :01 1800 /lib/tls/i686/cmov/libc-2.10.1.so00fb00 rw-p :01 1800 /lib/tls/i686/cmov/libc-2.10.1.so00fb00 rw-p :00 049000 r-xp :01 303895 /home/panfeng/segfault/segfault34a000 r--p :01 303895 /home/panfeng/segfault/segfault34b000 rw-p :01 303895 /home/panfeng/segfault/segfault357000 rw-p :00 0 [heap]b78cf000-b78d1000 rw-p :00 0b78df000-b78e1000 rw-p :00 0bfb67000-bfb7c000 rw-p :00 0 [stack]
5. 一些注意事项
1、出现段错误时,首先应该想到段错误的定义,从它出发考虑引发错误的原因。
2、在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为NULL。
3、在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等。
4、在访问变量时,注意变量所占地址空间是否已经被程序释放掉。
5、在处理变量时,注意变量的格式控制是否合理等。
6. 参考资料列表
1、http://www.docin.com/p-.html
2、http://blog.chinaunix.net/space.php?uid=317451&do=blog&id=92412
from:http://www.cnblogs.com/panfeng412/archive//2237857.html
阅读(...) 评论()&nbsp>&nbsp
&nbsp>&nbsp
&nbsp>&nbsp
Linux下段错误的原因以及调试方法
摘要:简而言之,产生段错误就是访问了错误的内存段。一、一般来说,段错误就是指访问的内存超出了系统分配给这个程序的内存空间,通常这个值是由gdtr来保存的,1)gdtr是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别。2)指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别、内存粒度等等的信息。一
简而言之,产生段错误就是 访问了错误的内存段 。
一、一般来说,段错误就是指访问的内存超出了系统分配给这个程序的内存空间,通常这个值是由gdtr来保存的,
1)gdtr是一个48位的寄存器,其中的32位是保 存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别。
2)指向的gdt是由以64位为一 个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别、内存粒度等等的信息。一旦一个程 序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.
二、在编程中以下几类做法容易导致段错误,基本是是 错误地使用指针引起的
1)访问系统数据区,尤其是往系统保护的内存地址写数据:最常见就是给一个指针以0地址 2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域
三、解决方法
1)、利用gdb逐步查找段错误: 这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上“-g -rdynamic”的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下: gcc -g -rdynamic d.c gdb ./a.out
注:中间省略了一此信息,最后会有如下信息
(gdb) r Starting program: /home/sdd/project/a.out
Program received signal SIGSEGV, Segmentation fault.0x0804860c in main () at main.c:11 11*p=1; 从上面我们可以看到出错位置在main.c文件的第11行,其实就是如此的简单。 从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7 signal),我们知道SIGSEGV默认handler的动作是打印”段错误”的出错信息,并产生Core文件,由此我们又产生了方法二。
2)、分析Core文件
Core文件是什么呢? Thedefault 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 memoryat 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文件的生成,使用ulimit命令开启,将系统的core文件的大小限制在512K大小,再试: :~/projectulimit--desktop:/project ulimit -c0:~/project ulimit -c 1000 :~/projectulimit--desktop:/project ulimit -c1000:~/project ./a.out 段错误 (core dumped) :~/project$ ls a.outcoremain.c
core文件终于产生了,用gdb调试一下看看吧:
:~/project$ gdb ./a.out core
结果也是一招命中,非常厉害!
3)、下面是别人写得一个程序,在发生了段错误时候自动进行调试,个人感觉非常不错,所以Copy过来了,以备后用。该程序的原理就是对段错误设置自己的处理函数,在发生段错误时,会发出SIGSEGV的信号,通过signal()函数将该信号和自己的处理函数关联。
void dump(int signo) { char buf[1024]; char cmd[1024]; FILE *
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] == ’ ‘) buf[strlen(buf) - 1] = ‘/0’; snprintf(cmd, sizeof(cmd), “gdb %s %d”, buf, getpid()); system(cmd);
void dummy_function (void) { unsigned char *ptr = 0x00; *ptr = 0x00; }
int main (void) { signal(SIGSEGV, &;dump); dummy_function ();
return 0; }
作者还给出了一种方法,在此不引用了。帖子的地址贴出来,以备以后查看。
http://edu.codepub.com/549.php
附:ulimit 命令的用法
功能说明:控制shell程序的资源。
语 法:ulimit [-aHS][-c
以上是的内容,更多
的内容,请您使用右上方搜索功能获取相关信息。
若你要投稿、删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内给你回复。
新用户大礼包!
现在注册,免费体验40+云产品,及域名优惠!
云服务器 ECS
可弹性伸缩、安全稳定、简单易用
&40.8元/月起
预测未发生的攻击
&24元/月起
你可能还喜欢
你可能感兴趣
阿里云教程中心为您免费提供
Linux下段错误的原因以及调试方法相关信息,包括
的信息,所有Linux下段错误的原因以及调试方法相关内容均不代表阿里云的意见!投稿删除文章请联系邮箱:zixun-group@service.aliyun.com,工作人员会在五个工作日内答复
售前咨询热线
支持与服务
资源和社区
关注阿里云
International博客访问: 1263386
博文数量: 435
博客积分: 918
博客等级: 准尉
技术积分: 3233
注册时间:
认证徽章:
你是不是暗恋我,那就给我发个消息呀,让我知道o(∩∩)o
原文地址: 作者:
1.查看当前linux是否打开core,方法是执行命令ulimit -a一般会出现如下的结果core file size&&&&&&& (blocks, -c) 00表示当前core被关闭&2.执行命令取消core限制ulimit -c unlimited3.重新查看,core是否已经打开ulimit -a一般会出现如下的结果core file size&&&&&&& (blocks, -c) unlimited命令结果里面core所在行的最后一个unlimited表示core已经无限制了&4.直接运行出现段错误的程序(如test),待程序异常退出后,当前目录下会生成一个core.xxxx的文件,其中xxxx一般为数字。&5.使用gdb查看出错时的堆栈,方法是:gdb test core.xxxx&6.最后在gdb里面敲击命令bt即可看出产生段错误时的堆栈情况如:#15 0x in proSNMPhost (arg=0x0) at discover.c:15087.通过分析堆栈,最终确定错误的位置
阅读(2470) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
请登录后评论。11:47 提问
return 0;出现段错误
Linux平台,编写函数时发现return 0;出现段错误!!!
不知道是什么情况,开始以为函数体过大,定义变量过长(有的数组达到2048字节),自己也尝试修改过系统允许的堆栈最大值,但没用!
而后将函数分开,变成几个小函数,然后段错误就消失了!!!
问题虽然解决了,但是不知道问题的原因,希望懂得说明一下return 0;出现段错误是什么情况。
按赞数排序
不可能是由于return 0导致的,问题的原因肯定在于你对数组之类局部变量的修改越界破坏了栈,导致栈里的返回地址错误,表现出来的情况就是return的时候程序跑飞了
函数内部有数组越界等,造成函数的栈桢空间被破坏了
听起来像是stack被你crash了,数组过大应该不是问题,2048自己的数字,你不会用静态数组吧?
但是没有代码,也只能猜猜了
准确详细的回答,更有利于被提问者采纳,从而获得C币。复制、灌水、广告等回答会被删除,是时候展现真正的技术了!
其他相关推荐学习编程第一步
零基础上手Python开发
阅读: 1983下载: 51
Java编程入门官方教程(第7版)
阅读: 3634下载: 44
Oracle 18c生产部署之单节点安装
阅读: 777下载: 20
Spring Boot实战 ,丁雪丰 (译者)
阅读: 197下载: 19
Oracle Database 12cR2多租户权威指南
阅读: 875下载: 9
阅读: 21290下载: 1906
阅读: 18281下载: 1578
阅读: 7583下载: 1355
阅读: 15538下载: 1006
阅读: 21395下载: 1001
Linux下的段错误产生的原因及调试方法
文件大小:187.25KBMB所需财富值:50
您当前剩余财富值:
Linux下的段错误产生的原因及调试方法
文件大小:187.25KBMB所需财富值:40
您当前剩余财富值:
网站帮助:
盛拓传媒: |

我要回帖

更多关于 编译块时检测错误 的文章

 

随机推荐