600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Linux调试工具strace和gdb常用命令小结-转

Linux调试工具strace和gdb常用命令小结-转

时间:2020-02-02 02:28:22

相关推荐

Linux调试工具strace和gdb常用命令小结-转

Linux环境下段错误的产生原因及调试方法小结

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

1.段错误是什么

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

Asegmentationfault(oftenshortenedtosegfault)isaparticularerrorconditionthatcanoccurduringtheoperationofcomputersoftware.Inshort,asegmentationfaultoccurswhenaprogramattemptstoaccessamemorylocationthatitisnotallowedtoaccess,orattemptstoaccessamemorylocationinawaythatisnotallowed(e.g.,attemptstowritetoaread-onlylocation,ortooverwritepartoftheoperatingsystem).SystemsbasedonprocessorsliketheMotorola68000tendtorefertotheseeventsasAddressorBuserrors.

Segmentationisoneapproachtomemorymanagementandprotectionintheoperatingsystem.Ithasbeensupersededbypagingformostpurposes,butmuchoftheterminologyofsegmentationisstillused,"segmentationfault"beinganexample.Someoperatingsystemsstillhavesegmentationatsomelogicallevelalthoughpagingisusedasthemainmemorymanagementpolicy.

OnUnix-likeoperatingsystems,aprocessthataccessesinvalidmemoryreceivestheSIGSEGVsignal.OnMicrosoftWindows,aprocessthataccessesinvalidmemoryreceivestheSTATUS_ACCESS_VIOLATIONexception. 2.段错误产生的原因2.1访问不存在的内存地址 #include<stdio.h>

#include<stdlib.h>

voidmain()

{

int*ptr=NULL;

*ptr=0;

} 2.2访问系统保护的内存地址 #include<stdio.h>

#include<stdlib.h>

voidmain()

{

int*ptr=(int*)0;

*ptr=100;

} 2.3访问只读的内存地址 #include<stdio.h>

#include<stdlib.h>

#include<string.h>

voidmain()

{

char*ptr="test";

strcpy(ptr,"TEST");

} 2.4栈溢出 #include<stdio.h>

#include<stdlib.h>

voidmain()

{

main();

}

等等其他原因。

3.段错误信息的获取

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

3.1dmesg

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

panfeng@ubuntu:~/segfault$dmesg

[2329.479037]segfault3[2700]:segfaultat80484e0ip00d2906aspbfbbec3cerror7inlibc-2.10.1.so[cb4000+13e000] 3.2-g

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

panfeng@ubuntu:~/segfault$gcc-g-osegfault3segfault3.c 3.3nm

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

panfeng@ubuntu:~/segfault$nmsegfault3

08049f20d_DYNAMIC

08049ff4d_GLOBAL_OFFSET_TABLE_

080484dcR_IO_stdin_used

w_Jv_RegisterClasses

08049f10d__CTOR_END__

08049f0cd__CTOR_LIST__

08049f18D__DTOR_END__

08049f14d__DTOR_LIST__

080484ecr__FRAME_END__

08049f1cd__JCR_END__

08049f1cd__JCR_LIST__

0804a014A__bss_start

0804a00cD__data_start

08048490t__do_global_ctors_aux

08048360t__do_global_dtors_aux

0804a010D__dso_handle

w__gmon_start__

0804848aT__i686.get_pc_thunk.bx

08049f0cd__init_array_end

08049f0cd__init_array_start

08048420T__libc_csu_fini

08048430T__libc_csu_init

U__libc_start_main@@GLIBC_2.0

0804a014A_edata

0804a01cA_end

080484bcT_fini

080484d8R_fp_hw

080482bcT_init

08048330T_start

0804a014bcompleted.6990

0804a00cWdata_start

0804a018bdtor_idx.6992

080483c0tframe_dummy

080483e4Tmain

Umemcpy@@GLIBC_2.0 3.4ldd

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

panfeng@ubuntu:~/segfault$ldd./segfault3

linux-gate.so.1=>(0x00e08000)

libc.so.6=>/lib/tls/i686/cmov/libc.so.6(0x00675000)

/lib/ld-linux.so.2(0x00482000) 4.段错误的调试方法4.1使用printf输出信息

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

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

4.2使用gcc和gdb4.2.1调试步骤

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

panfeng@ubuntu:~/segfault$gcc-g-osegfault3segfault3.c

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

panfeng@ubuntu:~/segfault$gdb./segfault3

GNUgdb(GDB)7.0-ubuntu

Copyright(C)FreeSoftwareFoundation,Inc.

LicenseGPLv3+:GNUGPLversion3orlater</licenses/gpl.html>

Thisisfreesoftware:youarefreetochangeandredistributeit.

ThereisNOWARRANTY,totheextentpermittedbylaw.Type"showcopying"

and"showwarranty"fordetails.

ThisGDBwasconfiguredas"i486-linux-gnu".

Forbugreportinginstructions,pleasesee:

</software/gdb/bugs/>...

Readingsymbolsfrom/home/panfeng/segfault/segfault3...done.

(gdb)

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

(gdb)run

Startingprogram:/home/panfeng/segfault/segfault3

ProgramreceivedsignalSIGSEGV,Segmentationfault.

0x001a306ainmemcpy()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

Adebuggingsessionisactive.

Inferior1[process3207]willbekilled.

Quitanyway?(yorn)y 4.2.2适用场景

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

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

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

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

4.3使用core文件和gdb

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

4.3.1调试步骤

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

panfeng@ubuntu:~/segfault$ulimit-c

0

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

panfeng@ubuntu:~/segfault$ulimit-c1024

panfeng@ubuntu:~/segfault$ulimit-c

1024

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

panfeng@ubuntu:~/segfault$./segfault3

段错误(coredumped)

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

panfeng@ubuntu:~/segfault$gdb./segfault3./core

GNUgdb(GDB)7.0-ubuntu

Copyright(C)FreeSoftwareFoundation,Inc.

LicenseGPLv3+:GNUGPLversion3orlater</licenses/gpl.html>

Thisisfreesoftware:youarefreetochangeandredistributeit.

ThereisNOWARRANTY,totheextentpermittedbylaw.Type"showcopying"

and"showwarranty"fordetails.

ThisGDBwasconfiguredas"i486-linux-gnu".

Forbugreportinginstructions,pleasesee:

</software/gdb/bugs/>...

Readingsymbolsfrom/home/panfeng/segfault/segfault3...done.

warning:Can'treadpathnameforloadmap:输入/输出错误.

Readingsymbolsfrom/lib/tls/i686/cmov/libc.so.6...(nodebuggingsymbolsfound)...done.

Loadedsymbolsfor/lib/tls/i686/cmov/libc.so.6

Readingsymbolsfrom/lib/ld-linux.so.2...(nodebuggingsymbolsfound)...done.

Loadedsymbolsfor/lib/ld-linux.so.2

Corewasgeneratedby`./segfault3'.

Programterminatedwithsignal11,Segmentationfault.

#00x0018506ainmemcpy()from/lib/tls/i686/cmov/libc.6

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

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

(gdb)quit 4.3.2适用场景

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

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

4.4使用objdump4.4.1调试步骤

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

panfeng@ubuntu:~/segfault$dmesg

......

[17257.502808]segfault3[3320]:segfaultat80484e0ip0018506aspbfc1cd6cerror7inlibc-2.10.1.so[110000+13e000]

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

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

panfeng@ubuntu:~/segfault$objdump-d./segfault3>segfault3Dump

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

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

panfeng@ubuntu:~/segfault$grep-n-A10-B10"80484e0"./segfault3Dump

121-80483df:ffd0call*%eax

122-80483e1:c9leave

123-80483e2:c3ret

124-80483e3:90nop

125-

126-080483e4<main>:

127-80483e4:55push%ebp

128-80483e5:89e5mov%esp,%ebp

129-80483e7:83e4f0and$0xfffffff0,%esp

130-80483ea:83ec20sub$0x20,%esp

131:80483ed:c744241ce08404movl$0x80484e0,0x1c(%esp)

132-80483f4:08

133-80483f5:b8e5840408mov$0x80484e5,%eax

134-80483fa:c7442408050000movl$0x5,0x8(%esp)

135-8048401:00

136-8048402:89442404mov%eax,0x4(%esp)

137-8048406:8b44241cmov0x1c(%esp),%eax

138-804840a:890424mov%eax,(%esp)

139-804840d:e80affffffcall804831c<memcpy@plt>

140-8048412:c9leave

141-8048413:c3ret

通过对以上汇编代码分析,得知段错误发生main函数,对应的汇编指令是movl$0x80484e0,0x1c(%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./segfault3

Segmentationfault(coredumped)

***Segmentationfault

Registerdump:

EAX:00000000EBX:00fb3ff4ECX:00000002EDX:00000000

ESI:080484e5EDI:080484e0EBP:bfb7ad38ESP:bfb7ad0c

EIP:00ee806aEFLAGS:00010203

CS:0073DS:007bES:007bFS:0000GS:0033SS:007b

Trap:0000000eError:00000007OldMask:00000000

ESP/signal:bfb7ad0cCR2:080484e0

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]

Memorymap:

00258000-00273000r-xp0000000008:01157/lib/ld-2.10.1.so

00273000-00274000r--p0001a00008:01157/lib/ld-2.10.1.so

00274000-00275000rw-p0001b00008:01157/lib/ld-2.10.1.so

003b4000-003b7000r-xp0000000008:0113105/lib/libSegFault.so

003b7000-003b8000r--p0000200008:0113105/lib/libSegFault.so

003b8000-003b9000rw-p0000300008:0113105/lib/libSegFault.so

00c76000-00c77000r-xp0000000000:000[vdso]

00e0d000-00e29000r-xp0000000008:014817/lib/libgcc_s.so.1

00e29000-00e2a000r--p0001b00008:014817/lib/libgcc_s.so.1

00e2a000-00e2b000rw-p0001c00008:014817/lib/libgcc_s.so.1

00e73000-00fb1000r-xp0000000008:011800/lib/tls/i686/cmov/libc-2.10.1.so

00fb1000-00fb2000---p0013e00008:011800/lib/tls/i686/cmov/libc-2.10.1.so

00fb2000-00fb4000r--p0013e00008:011800/lib/tls/i686/cmov/libc-2.10.1.so

00fb4000-00fb5000rw-p0014000008:011800/lib/tls/i686/cmov/libc-2.10.1.so

00fb5000-00fb8000rw-p0000000000:000

08048000-08049000r-xp0000000008:01303895/home/panfeng/segfault/segfault3

08049000-0804a000r--p0000000008:01303895/home/panfeng/segfault/segfault3

0804a000-0804b000rw-p0000100008:01303895/home/panfeng/segfault/segfault3

09432000-09457000rw-p0000000000:000[heap]

b78cf000-b78d1000rw-p0000000000:000

b78df000-b78e1000rw-p0000000000:000

bfb67000-bfb7c000rw-p0000000000:000[stack] 5.一些注意事项

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

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

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

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

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

6.参考资料列表

1、/p-105923877.html

2、/space.php?uid=317451&do=blog&id=92412

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。