《内存调试工具valg》word版.docx
《《内存调试工具valg》word版.docx》由会员分享,可在线阅读,更多相关《《内存调试工具valg》word版.docx(11页珍藏版)》请在冰豆网上搜索。
《内存调试工具valg》word版
内存调试工具valgrind
来源:
:
linux下面用c++写代码,在所难免会遇到segmentationfault(段错误)。
个人在编写ns扩展模块时候,遇到过很多段错误,虽然运行时刻经常由程序抛出段错误,但是段错误的发生的程序级别的原因多种多样,不过归结到系统级别上,段错误都是由于内存原因引起的(个人总结)。
会造成内存错误的程序级别的原因,也就是我们程序员所经常犯的错误大致可以分为以下几个方面:
1,使用未初始化的指针-这是必然的,指针指空的东西,必然出错。
2,重复删除一个指针-必然,再次删除就会删除一个已经分配给别的程序的数据或者其他。
3,内存冲突-由于程序声明的两块数据区域重叠,造成混乱。
4,混杂的指针错误-只能具体问题具体分析,情况比较复杂。
对于一位刚开始用c++在linux编程的人来说,最常遇到的应该的就是1与2了。
当工程规模比较大,程序由小组完成而后整合等的情况下,很容易出现2,3,4的情况。
这时候的调试比较麻烦,也需要很多耐心。
我在做的wimaxmesh的项目就是这样。
对于一个timer的使用,没有初始化,造成的段错误,一目了然。
工程进展非常顺利。
当工程做到50%时候(11.08号),遇到了一个段错误,结果调试到12.02号才调出来!
我就来说一下我的调试历程吧!
真是一波三折阿!
开始的时候以为是1或者2的情况,反复检查,不是这样。
然后怀疑3或者4,结果由于没有使用任何工具,只是在代码中加打印信息,这时候只能把错误定位到transmit(p)这个函数上。
但是这个函数我只写了一行,就是
transmit(p)
{
downtarget_-recv(p,(Handle*)NULL);
}
程序在这个地方出错实在让人摸不到头绪,因为再往下执行就不是我代码的问题了,而是下层已有代码甚至是系统代码的问题阿!
非常困扰!
然后开始用gdb调试,gdb是一个很好的很强大的调试工具,我用的命令行的,所能完成的功能和vc下的调试工具差不多,只是需要看什么变量就是要用print×来看罢了,不过功能决不比它差。
但是我用了gdb只能把错误定位在:
(gdb)bt
#00x082d16bainCheckChannelErrors()
#10x082d43a5inTcl_Write()
#20x081cdb52inBaseTrace:
namdump(this=0x90e09d0)attrace/basetrace.cc:
109
#30x08137aa4inCMUTrace:
nam_format(this=0x90e10b0,p=0x90fb860,offset=64)attrace/cmu-trace.cc:
1123
#40x081384dbinCMUTrace:
format(this=0x90e10b0,p=0x90fb860,why=0x833ec34"---")attrace/cmu-trace.cc:
1137
#50x08138751inCMUTrace:
recv(this=0x90e10b0,p=0x90fb860,h=0x0)attrace/cmu-trace.cc:
1239
#60x0821e9e7inMac802_16:
mac_log(this=0x90d1f08,p=0x90fb860)atwimax/mac802_16.h:
909
#70x082268e6inMac802_16MSS:
receive(this=0x90d1f08)atwimax/mac802_16MSS.cc:
660
#80x08228b08inWimaxRxTimer:
handle(this=0x90d29a4,e=0x90d29b4)atwimax/mac802_16timer.cc:
98
#90x08054332inScheduler:
dispatch(this=0x8eb6a10,p=0x90d29b4,t=10.017116268588)atcommon/scheduler.cc:
150
#100x0805457einScheduler:
run(this=0x8eb6a10)atcommon/scheduler.cc:
129
#110x0805485dinScheduler:
command(this=0x8eb6a10,argc=2,argv=0xbfa20730)atcommon/scheduler.cc:
198
#120x0828b7aeinTclClass:
dispatch_cmd()
#130x082903d0inOTclDispatch(cd=valueoptimizedout,in=0x8e97250,argc=3,argv=0xbfa2077c)atotcl.c:
434
#140x0829712einTclInvokeStringCommand()
#150x08298c4binTclEvalObjvInternal()
#160x082c2ef1inTclExecuteByteCode()
#170x082c6d1cinTclCompEvalObj()
#180x082c2fcainTclExecuteByteCode()
#190x082c6d1cinTclCompEvalObj()
#200x082eec76inTclObjInterpProc()
#210x082ef092inTclProcInterpProc()
#220x08290520inOTclDispatch(cd=valueoptimizedout,in=0x8e97250,argc=2,argv=0xbfa2131c)atotcl.c:
477
#230x0829712einTclInvokeStringCommand()
#240x08298c4binTclEvalObjvInternal()
#250x082c2ef1inTclExecuteByteCode()
#260x082c6d1cinTclCompEvalObj()
#270x082eec76inTclObjInterpProc()
#280x082ef092inTclProcInterpProc()
#290x082903d0inOTclDispatch(cd=valueoptimizedout,in=0x8e97250,argc=2,argv=0xbfa21c2c)atotcl.c:
434
#300x0829712einTclInvokeStringCommand()
#310x08298c4binTclEvalObjvInternal()
#320x08299207inTcl_EvalEx()
#330x082ded12inTcl_FSEvalFile()
#340x082e2307inTcl_Main()
#350x0804cafcinmain(argc=2747804,argv=0x178b62)atcommon/tclAppInit.cc:
67
这让我更加不解,为什么会在调用ns原有代码的地方出问题呢?
后来决定再换一个工具。
用valgrind
在这里我先贴一下valgrind的用法,我自己搜到的,很详细的。
姑且就不自己写了。
=
ValgrindValgrind已经在Linux应用程序开发社区中广泛用来调试应用程序。
它尤其擅长发现内存管理的问题。
它可以检查程序运行时的内存泄漏问题。
这个工具目前正由JulianSeward进行开发,并由PaulMackerras移植到了Power架构上。
要安装Valgrind,请从Valgrind的Web站点上下载源代码(参阅参考资料)。
切换到Valgrind目录,并执行下面的命令:
#make
#makecheck
#makeinstall
堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。
Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。
Valgrind安装
1、到www.valgrind.org下载最新版valgrind-3.2.3.tar.bz22、解压安装包:
tar–jxvfvalgrind-3.2.3.tar.bz23、解压后生成目录valgrind-3.2.34、cdvalgrind-3.2.35、./configure6、Make;makeinstallValgrind使用
用法:
valgrind[options]prog-and-args[options]:
常用选项,适用于所有Valgrind工具
-tool=name最常用的选项。
运行valgrind中名为toolname的工具。
默认memcheck。
h–help显示帮助信息。
-version显示valgrind内核的版本,每个工具都有各自的版本。
q–quiet安静地运行,只打印错误信息。
v–verbose更详细的信息,增加错误数统计。
-trace-children=no|yes跟踪子线程?
[no]-track-fds=no|yes跟踪打开的文件描述?
[no]-time-stamp=no|yes增加时间戳到LOG信息?
[no]-log-fd=number输出LOG到描述符文件[2=stderr]-log-file=file将输出的信息写入到filename.PID的文件里,PID是运行程序的进行ID-log-file-exactly=file输出LOG信息到file-log-file-qualifier=VAR取得环境变量的值来做为输出信息的文件名。
[none]-log-socket=ipaddr:
port输出LOG到socket,ipaddr:
portLOG信息输出
-xml=yes将信息以xml格式输出,只有memcheck可用-num-callers=numbershownumbercallersinstacktraces[12]-error-limit=no|yes如果太多错误,则停止显示新错误?
[yes]-error-exitcode=number如果发现错误则返回错误代码[0=disable]-db-attach=no|yes当出现错误,valgrind会自动启动调试器gdb。
[no]-db-command=command启动调试器的命令行选项[gdb-nw%f%p]适用于Memcheck工具的相关选项:
-leak-check=no|summary|full要求对leak给出详细信息?
[summary]-leak-resolution=low|med|highhowmuchbtmerginginleakcheck[low]-show-reachable=no|yesshowreachableblocksinleakcheck?
[no]
Valgrind的错误报告
Valgrind的输出格式如下:
清单1.Valgrind的输出消息
#valgrinddu–x–s
.
.
==29404==Address0x1189AD84is0bytesafterablockofsize12alloc'd
==29404==at0xFFB9964:
malloc(vg_replace_malloc.c:
130)
==29404==by0xFEE1AD0:
strdup(in/lib/tls/libc.so.6)
==29404==by0xFE94D30:
setlocale(in/lib/tls/libc.so.6)
==29404==by0x10001414:
main(in/usr/bin/du)
==29404==是进程的ID。
消息Address0x1189AD84is0bytesafterablockofsize12alloc'd说明在这个12字节的数组后面没有存储空间了。
第二行以及后续几行说明内存是在130行(vg_replace_malloc.c)的strdup()程序中进行分配的。
strdup()是在libc.so.6库的setlocale()中调用的;main()调用了setlocale()。
未初始化的内存
最为常见的一个bug是程序使用了未初始化的内存。
未初始化的数据可能来源于:
未经初始化的变量malloc函数所分配的数据,在写入值之前使用了
下面这个例子使用了一个未初始化的数组:
清单2.使用未初始化的内存
2{
3inti[5];
45if(i[0]==0)
6i[1]=1;
7return0;
8}
在这个例子中,整数数组i[5]没有进行初始化;因此,i[0]包含的是一个随机数。
因此使用i[0]的值来判断一个条件分支就会导致不可预期的问题。
Valgrind可以很容易捕获这种错误条件。
当您使用Valgrind运行这个程序时,就会接收到下面的消息:
清单3.Valgrind的输出消息
#gcc–g–otest1test1.c
#valgrind./test1
.
.
==31363==
==31363==Conditionaljumpormovedependsonuninitialisedvalue(s)
==31363==at0x1000041C:
main(test1.c:
5)
==31363==
==31363==ERRORSUMMARY:
1errorsfrom1contexts(suppressed:
7from1)
==31363==malloc/free:
inuseatexit:
0bytesin0blocks.
==31363==malloc/free:
0allocs,0frees,0bytesallocated.
==31363==Forcountsofdetectederrors,rerunwith:
-v
==31363==Nomalloc'dblocks--noleaksarepossible.
Valgrind的输出说明,有一个条件分支依赖于文件test1.c中第5行中的一个未初始化的变量。
内存泄漏
内存泄漏是另外一个常见的问题,也是很多程序中最难判断的问题。
内存泄漏的主要表现为:
当程序连续运行时,与程序相关的内存(或堆)变得越来越大。
结果是,当这个程序所消耗的内存达到系统的上限时,就会自己崩溃;或者会出现更严重的情况:
挂起或导致系统崩溃。
下面是一个有内存泄漏bug的示例程序:
清单4.内存泄漏示例
1intmain(void)
2{
3char*p1;
4char*p2;
56p1=(char*)malloc(512);
7p2=(char*)malloc(512);
89p1=p2;
1011free(p1);
12free(p2);
13}
上面的代码分别给字符指针p1和p2分配了两个512字节的内存块,然后将指向第一个内存块的指针设置为指向第二个内存块。
结果是,第二个内存块的地址丢失了,并导致内存泄漏。
在使用Valgrind运行这个程序时,会返回如下的消息:
清单5.Valgrind的输出消息
#gcc–g–otest2test2.c
#valgrind./test2
.
.
==31468==Invalidfree()/delete/delete
==31468==at0xFFB9FF0:
free(vg_replace_malloc.c:
152)
==31468==by0x100004B0:
main(test2.c:
12)
==31468==Address0x11899258is0bytesinsideablockofsize512free'd
==31468==at0xFFB9FF0:
free(vg_replace_malloc.c:
152)
==31468==by0x100004A4:
main(test2.c:
11)
==31468==
==31468==ERRORSUMMARY:
1errorsfrom1contexts(suppressed:
7from1)
==31468==malloc/free:
inuseatexit:
512bytesin1blocks.
==31468==malloc/free:
2allocs,2frees,1024bytesallocated.
==31468==Forcountsofdetectederrors,rerunwith:
-v
==31468==searchingforpointersto1not-freedblocks.
==31468==checked167936bytes.
==31468==
==31468==LEAKSUMMARY:
==31468==definitelylost:
512bytesin1blocks.
==31468==possiblylost:
0bytesin0blocks.
==31468==stillreachable:
0bytesin0blocks.
==31468==suppressed:
0bytesin0blocks.
==31468==Use--leak-check=fulltoseedetailsofleakedmemory.
正如您可以看到的一样,Valgrind报告说这个程序中有512字节的内存丢失了。
非法写/读
这种情况发生在程序试图对一个不属于程序本身的内存地址进行读写时。
在有些系统上,在发生这种错误时,程序会异常结束,并产生一个段错误。
下面这个例子就是一个常见的bug,它试图读写一个超出数组边界的元素。
清单6.非法读写
1intmain(){
2inti,*iw,*ir;
34iw=(int*)malloc(10*sizeof(int));
5ir=(int*)malloc(10*sizeof(int));
67
8for(i=0;i11;i++)
9iw[i]=i;
1011for(i=0;i11;i++)
12ir[i]=iw[i];
1314free(iw);
15free(ir);
16}
从这个程序中我们可以看出,对于iw[10]和ir[10]的访问都是非法的,因为iw和ir都只有10个元素,分别是从0到9。
请注意intiw[10]和iw=(int*)malloc(10*sizeof(int))是等效的--它们都是用来给一个整数数组iw分配10个元素。
当您使用Valgrind运行这个程序时,会返回如下的消息:
清单7.Valgrind的输出消息
#gcc–g–otest3test3.c
#valgrind./test3
.
.
==31522==Invalidwriteofsize4
==31522==at0x100004C0:
main(test3.c:
9)
==31522==Address0x11899050is0bytesafterablockofsize40alloc'd
==31522==at0xFFB9964:
malloc(vg_replace_malloc.c:
130)
==31522==by0x10000474:
main(test10.c:
4)
==31522==
==31522==Invalidreadofsize4
==31522==at0x1000050C:
main(test3.c:
12)
==31522==Address0x11899050is0bytesafterablockofsize40alloc'd
==31522==at0xFFB9964:
malloc(vg_replace_malloc.c:
130)
==31522==by0x10000474:
main(test10.c:
4)
==31522==
==31522==ERRORSUMMARY:
2errorsfrom2contexts(suppressed:
7from1)
==31522==malloc/free:
inuseatexit:
0bytesin0blocks.
==31522==malloc/free:
2allocs,2frees,84bytesallocated.
==31522==Forcountsofdetectederrors,rerunwith:
-v
==31522==Nomalloc'dblocks--noleaksarepossible.
在test3.c的第9行发现一个非法的4字节写操作,在第12行发现一个非法的4字节读操作。
Valgrind也可以帮助判断内存误用的问题,例如:
读/写已经释放的内存C++环境中错误地使用malloc/new与free/delete的配对
下面这个列表介绍了POWER架构上Valgrind的状态:
memcheck和addrcheck工具都可以很好地工作。
然而,其他工具还没有进行大量的测试。
另外,Helgrind(一个数据竞争的检测程序)在POWER上尚不能使用。
所有的32位PowerPC?
用户模式的指令都可以支持,除了两条非常少用的指令:
lswx和stswx。
具体来说,所有的浮点和Altivec(VMX)指令都可以支持。
Valgrind可以在32位或64位PowerPC/Linux内核上工作,但是只能用于32位的可执行程序。
有关Valgrind内存调试的更多信息,请访问ValgrindHOWTO站点。
还可以参阅SteveBest的"DebuggingMemoryProblems"(LinuxMagazine,2003年5月)。
参考资料中有它们的链接
除了Valgrind之外,还可以使用其他几个内存调试工具;例如,Memwatch和ElectricFence。
=
上述的例子足以应付我们日常遇到的内存错误。
那么我来说说我是怎么用valgrind调试我的程序的。
用命令:
[tengda@localhosttcl]$valgrind--tool=memcheck--leak-check=full--db-attach=yesnssss.tcl
==26507==Memcheck,amemoryerrordetector.
==26507==Copyright(C)2002-2007,andGNUGPL'd,byJulianSewardetal.
==26507==UsingLibVEXrev1732,alibraryfordynamicbinarytranslation.
==26507==Copyright(C)2004-2007,andGNUGPL'd,byOpenWorksLLP.
==26507==Usingvalgrind-3.2.3,adynami