您的位置:首页 > 运维架构 > Linux

[转] Linux C语言 段错误bug的调试

2011-12-29 13:41 337 查看
原来看过一次,后来当自己有段错误的时候,才想起来这个很有用.如果不用他的方法,段错误很恶心的,不好找出来的.

下面就是原文,不过最初出处不得而知

=======================

复制存在问题,格式不正确...先给链接吧.

http://blog.sina.com.cn/s/blog_606c49090100eohs.html

core文件的资料:http://blog.sina.com.cn/s/blog_489c2413010080ml.html

Linux系统下的多线程编程入门http://blog.sina.com.cn/s/blog_489c241301008nco.html这个应该是IBM上文章,之前我看过.

1)访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以

0地址

2)内存越界(数组越界,变量类型不一致等)访问到不属于你的内存区域
解决方法

我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。

下面将就以下的一个存在段错误的程序介绍几种调试方法:

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

intmain(void)
{
dummy_function();

return0;
}



作为一个熟练的C/C++程序员,以上代码的bug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。我们尝试编译运行它:
xiaosuo@gentuxtest$./a.out
段错误
果然不出所料,它出错并退出了。
1.利用gdb逐步查找段错误:
这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上“-g-rdynamic"的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下:


xiaosuo@gentuxtest$gcc-g-rdynamicd.c
xiaosuo@gentuxtest$gdb./a.out
GNUgdb6.5
Copyright(C)2006FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i686-pc-linux-gnu"...Usinghostlibthread_dblibrary"/lib/libthread_db.so.1".

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

ProgramreceivedsignalSIGSEGV,Segmentationfault.
0x08048524indummy_function()atd.c:4
4*ptr=0x00;
(gdb)



哦?!好像不用一步步调试我们就找到了出错位置d.c文件的第4行,其实就是如此的简单。
从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man7signal),我们知道SIGSEGV默认handler的动作是打印”段错误"的出错信息,并产生Core文件,由此我们又产生了方法二。
2.分析Core文件:
Core文件是什么呢?
Thedefaultactionofcertainsignalsistocauseaprocesstoterminateandproduceacoredumpfile,adiskfilecontaininganimageoftheprocess'smemoryatthetimeoftermination.Alistofthesignalswhichcauseaprocesstodumpcorecanbefoundinsignal(7).
以上资料摘自manpage(man5core)。不过奇怪了,我的系统上并没有找到core文件。后来,忆起为了渐少系统上的拉圾文件的数量(本人有些洁癖,这也是我喜欢Gentoo的原因之一),禁止了core文件的生成,查看了以下果真如此,将系统的core文件的大小限制在512K大小,再试:


xiaosuo@gentuxtest$ulimit-c
0
xiaosuo@gentuxtest$ulimit-c1000
xiaosuo@gentuxtest$ulimit-c
1000
xiaosuo@gentuxtest$./a.out
段错误(coredumped)
xiaosuo@gentuxtest$ls
a.outcored.cf.cg.cpango.ctest_iconv.ctest_regex.c



core文件终于产生了,用gdb调试一下看看吧:


xiaosuo@gentuxtest$gdb./a.outcore
GNUgdb6.5
Copyright(C)2006FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i686-pc-linux-gnu"...Usinghostlibthread_dblibrary"/lib/libthread_db.so.1".

warning:Can'treadpathnameforloadmap:输入/输出错误.
Readingsymbolsfrom/lib/libc.so.6...done.
Loadedsymbolsfor/lib/libc.so.6
Readingsymbolsfrom/lib/ld-linux.so.2...done.
Loadedsymbolsfor/lib/ld-linux.so.2
Corewasgeneratedby`./a.out'.
Programterminatedwithsignal11,Segmentationfault.
#00x08048524indummy_function()atd.c:4
4*ptr=0x00;



哇,好历害,还是一步就定位到了错误所在地,佩服一下Linux/Unix系统的此类设计。
接着考虑下去,以前用windows系统下的ie的时侯,有时打开某些网页,会出现“运行时错误”,这个时侯如果恰好你的机器上又装有windows的编译器的话,他会弹出来一个对话框,问你是否进行调试,如果你选择是,编译器将被打开,并进入调试状态,开始调试。
Linux下如何做到这些呢?我的大脑飞速地旋转着,有了,让它在SIGSEGV的handler中调用gdb,于是第三个方法又诞生了:
3.段错误时启动调试:


#include
#include
#include
#include

voiddump(intsigno)
{
charbuf[1024];
charcmd[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]='\0';
snprintf(cmd,sizeof(cmd),"gdb%s%d",buf,getpid());
system(cmd);

exit(0);
}

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

int
main(void)
{
signal(SIGSEGV,&dump);
dummy_function();

return0;
}



编译运行效果如下:


xiaosuo@gentuxtest$gcc-g-rdynamicf.c
xiaosuo@gentuxtest$./a.out
GNUgdb6.5
Copyright(C)2006FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i686-pc-linux-gnu"...Usinghostlibthread_dblibrary"/lib/libthread_db.so.1".

Attachingtoprogram:/home/xiaosuo/test/a.out,process9563
Readingsymbolsfrom/lib/libc.so.6...done.
Loadedsymbolsfor/lib/libc.so.6
Readingsymbolsfrom/lib/ld-linux.so.2...done.
Loadedsymbolsfor/lib/ld-linux.so.2
0xffffe410in__kernel_vsyscall()
(gdb)bt
#00xffffe410in__kernel_vsyscall()
#10xb7ee4b53inwaitpid()from/lib/libc.so.6
#20xb7e925c9instrtold_l()from/lib/libc.so.6
#30x08048830indump(signo=11)atf.c:22
#4
#50x0804884cindummy_function()atf.c:31
#60x08048886inmain()atf.c:38



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


编译运行结果如下:
xiaosuo@gentuxtest$gcc-g-rdynamicg.c
xiaosuo@gentuxtest$./a.out
Obtained5stackframes.
./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@gentuxtest$objdump-da.out

8048765:e802feffffcall804856c
804876a:e825ffffffcall8048694
804876f:b800000000mov$0x0,%eax
8048774:c9leave
我们还是找到了在哪个函数(dummy_function)中出错的,信息已然不是很完整,不过有总比没有好的啊!
后记:
本文给出了分析"段错误"的几种方法,不要认为这是与孔乙己先生的"回"字四种写法一样的哦,因为每种方法都有其自身的适用范围和适用环境,请酌情使用,或遵医嘱。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: