掌握 Linux 调试技术.docx

上传人:b****6 文档编号:8227884 上传时间:2023-01-30 格式:DOCX 页数:16 大小:27.98KB
下载 相关 举报
掌握 Linux 调试技术.docx_第1页
第1页 / 共16页
掌握 Linux 调试技术.docx_第2页
第2页 / 共16页
掌握 Linux 调试技术.docx_第3页
第3页 / 共16页
掌握 Linux 调试技术.docx_第4页
第4页 / 共16页
掌握 Linux 调试技术.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

掌握 Linux 调试技术.docx

《掌握 Linux 调试技术.docx》由会员分享,可在线阅读,更多相关《掌握 Linux 调试技术.docx(16页珍藏版)》请在冰豆网上搜索。

掌握 Linux 调试技术.docx

掌握Linux调试技术

掌握Linux调试技术

在Linux上找出并解决程序错误的主要方法

SteveBest(sbest@)

JFS核心小组成员,IBM

2002年8月

您可以用各种方法来监控运行着的用户空间程序:

可以为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序。

本文描述了几种可以用来调试在Linux上运行的程序的方法。

我们将回顾四种调试问题的情况,这些问题包括段错误,内存溢出和泄漏,还有挂起。

本文讨论了四种调试Linux程序的情况。

在第1种情况中,我们使用了两个有内存分配问题的样本程序,使用MEMWATCH和YetAnotherMallocDebugger(YAMD)工具来调试它们。

在第2种情况中,我们使用了Linux中的strace实用程序,它能够跟踪系统调用和信号,从而找出程序发生错误的地方。

在第3种情况中,我们使用Linux内核的Oops功能来解决程序的段错误,并向您展示如何设置内核源代码级调试器(kernelsourceleveldebugger,kgdb),以使用GNU调试器(GNUdebugger,gdb)来解决相同的问题;kgdb程序是使用串行连接的Linux内核远程gdb。

在第4种情况中,我们使用Linux上提供的魔术键控顺序(magickeysequence)来显示引发挂起问题的组件的信息。

常见调试方法

当您的程序中包含错误时,很可能在代码中某处有一个条件,您认为它为真(true),但实际上是假(false)。

找出错误的过程也就是在找出错误后推翻以前一直确信为真的某个条件过程。

以下几个示例是您可能确信成立的条件的一些类型:

∙在源代码中的某处,某变量有特定的值。

∙在给定的地方,某个结构已被正确设置。

∙对于给定的if-then-else语句,if部分就是被执行的路径。

∙当子例程被调用时,该例程正确地接收到了它的参数。

找出错误也就是要确定上述所有情况是否存在。

如果您确信在子例程被调用时某变量应该有特定的值,那么就检查一下情况是否如此。

如果您相信if结构会被执行,那么也检查一下情况是否如此。

通常,您的假设都会是正确的,但最终您会找到与假设不符的情况。

结果,您就会找出发生错误的地方。

调试是您无法逃避的任务。

进行调试有很多种方法,比如将消息打印到屏幕上、使用调试器,或只是考虑程序执行的情况并仔细地揣摩问题所在。

在修正问题之前,您必须找出它的源头。

举例来说,对于段错误,您需要了解段错误发生在代码的哪一行。

一旦您发现了代码中出错的行,请确定该方法中变量的值、方法被调用的方式以及关于错误如何发生的详细情况。

使用调试器将使找出所有这些信息变得很简单。

如果没有调试器可用,您还可以使用其它的工具。

(请注意,产品环境中可能并不提供调试器,而且Linux内核没有内建的调试器。

实用的内存和内核工具

您可以使用Linux上的调试工具,通过各种方式跟踪用户空间和内核问题。

请使用下面的工具和技术来构建和调试您的源代码:

用户空间工具:

∙内存工具:

MEMWATCH和YAMD

∙strace

∙GNU调试器(gdb)

∙魔术键控顺序

内核工具:

∙内核源代码级调试器(kgdb)

∙内建内核调试器(kdb)

∙Oops

本文将讨论一类通过人工检查代码不容易找到的问题,而且此类问题只在很少见的情况下存在。

内存错误通常在多种情况同时存在时出现,而且您有时只能在部署程序之后才能发现内存错误。

第1种情况:

内存调试工具

C语言作为Linux系统上标准的编程语言给予了我们对动态内存分配很大的控制权。

然而,这种自由可能会导致严重的内存管理问题,而这些问题可能导致程序崩溃或随时间的推移导致性能降级。

内存泄漏(即malloc()内存在对应的free()调用执行后永不被释放)和缓冲区溢出(例如对以前分配到某数组的内存进行写操作)是一些常见的问题,它们可能很难检测到。

这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。

MEMWATCH

MEMWATCH由JohanLindh编写,是一个开放源代码C语言内存错误检测工具,您可以自己下载它(请参阅本文后面部分的参考资料)。

只要在代码中添加一个头文件并在gcc语句中定义了MEMWATCH之后,您就可以跟踪程序中的内存泄漏和错误了。

MEMWATCH支持ANSIC,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneousfree)、没有释放的内存(unfreedmemory)、溢出和下溢等等。

清单1.内存样本(test1.c)

#include

#include

#include"memwatch.h"

intmain(void)

{

char*ptr1;

char*ptr2;

ptr1=malloc(512);

ptr2=malloc(512);

ptr2=ptr1;

free(ptr2);

free(ptr1);

}

清单1中的代码将分配两个512字节的内存块,然后指向第一个内存块的指针被设定为指向第二个内存块。

结果,第二个内存块的地址丢失,从而产生了内存泄漏。

现在我们编译清单1的memwatch.c。

下面是一个makefile示例:

test1

gcc-DMEMWATCH-DMW_STDIOtest1.cmemwatch

c-otest1

当您运行test1程序后,它会生成一个关于泄漏的内存的报告。

清单2展示了示例memwatch.log输出文件。

清单2.test1memwatch.log文件

MEMWATCH2.67Copyright(C)1992-1999JohanLindh

...

double-free:

<4>test1.c(15),0x80517b4wasfreedfromtest1.c(14)

...

unfreed:

<2>test1.c(11),512bytesat0x80519e4

{FEFEFEFEFEFEFEFEFEFEFEFE..............}

Memoryusagestatistics(global):

N)umberofallocationsmade:

2

L)argestmemoryusage:

1024

T)otalofallalloc()calls:

1024

U)nfreedbytestotals:

512

MEMWATCH为您显示真正导致问题的行。

如果您释放一个已经释放过的指针,它会告诉您。

对于没有释放的内存也一样。

日志结尾部分显示统计信息,包括泄漏了多少内存,使用了多少内存,以及总共分配了多少内存。

YAMD

YAMD软件包由NateEldredge编写,可以查找C和C++中动态的、与内存分配有关的问题。

在撰写本文时,YAMD的最新版本为0.32。

请下载yamd-0.32.tar.gz(请参阅参考资料)。

执行make命令来构建程序;然后执行makeinstall命令安装程序并设置工具。

一旦您下载了YAMD之后,请在test1.c上使用它。

请删除#includememwatch.h并对makefile进行如下小小的修改:

使用YAMD的test1

gcc-gtest1.c-otest1

清单3展示了来自test1上的YAMD的输出。

清单3.使用YAMD的test1输出

YAMDversion0.32

Executable:

/usr/src/test/yamd-0.32/test1

...

INFO:

Normalallocationofthisblock

Address0x40025e00,size512

...

INFO:

Normalallocationofthisblock

Address0x40028e00,size512

...

INFO:

Normaldeallocationofthisblock

Address0x40025e00,size512

...

ERROR:

MultiplefreeingAt

freeofpointeralreadyfreed

Address0x40025e00,size512

...

WARNING:

Memoryleak

Address0x40028e00,size512

WARNING:

Totalmemoryleaks:

1unfreedallocationstotaling512bytes

***FinishedatTue...10:

07:

152002

Allocatedagrandtotalof1024bytes2allocations

Averageof512bytesperallocation

Maxbytesallocatedatonetime:

1024

24Kallocedinternally/12Kmappednow/8Kmax

Virtualprogramsizeis1416K

End.

YAMD显示我们已经释放了内存,而且存在内存泄漏。

让我们在清单4中另一个样本程序上试试YAMD。

清单4.内存代码(test2.c)

#include

#include

intmain(void)

{

char*ptr1;

char*ptr2;

char*chptr;

inti=1;

ptr1=malloc(512);

ptr2=malloc(512);

chptr=(char*)malloc(512);

for(i;i<=512;i++){

chptr[i]='S';

}

ptr2=ptr1;

free(ptr2);

free(ptr1);

free(chptr);

}

您可以使用下面的命令来启动YAMD:

./run-yamd/usr/src/test/test2/test2

清单5显示了在样本程序test2上使用YAMD得到的输出。

YAMD告诉我们在for循环中有“越界(out-of-bounds)”的情况。

清单5.使用YAMD的test2输出

Running/usr/src/test/test2/test2

Tempoutputto/tmp/yamd-out.1243

*********

./run-yamd:

line101:

1248Segmentationfault(coredumped)

YAMDversion0.32

Startingrun:

/usr/src/test/test2/test2

Executable:

/usr/src/test/test2/test2

Virtualprogramsizeis1380K

...

INFO:

Normalallocationofthisblock

Address0x40025e00,size512

...

INFO:

Normalallocationofthisblock

Address0x40028e00,size512

...

INFO:

Normalallocationofthisblock

Address0x4002be00,size512

ERROR:

Crash

...

Triedtowriteaddress0x4002c000

Seemstobepartofthisblock:

Address0x4002be00,size512

...

Addressinquestionisatoffset512(outofbounds)

Willdumpcoreaftercheckingheap.

Done.

MEMWATCH和YAMD都是很有用的调试工具,它们的使用方法有所不同。

对于MEMWATCH,您需要添加包含文件memwatch.h并打开两个编译时间标记。

对于链接(link)语句,YAMD只需要-g选项。

ElectricFence

多数Linux分发版包含一个ElectricFence包,不过您也可以选择下载它。

ElectricFence是一个由BrucePerens编写的malloc()调试库。

它就在您分配内存后分配受保护的内存。

如果存在fencepost错误(超过数组末尾运行),程序就会产生保护错误,并立即结束。

通过结合ElectricFence和gdb,您可以精确地跟踪到哪一行试图访问受保护内存。

ElectricFence的另一个功能就是能够检测内存泄漏。

第2种情况:

使用strace

strace命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。

strace显示这些调用的参数并返回符号形式的值。

strace从内核接收信息,而且不需要以任何特殊的方式来构建内核。

将跟踪信息发送到应用程序及内核开发者都很有用。

在清单6中,分区的一种格式有错误,清单显示了strace的开头部分,内容是关于调出创建文件系统操作(mkfs)的。

strace确定哪个调用导致问题出现。

清单6.mkfs上strace的开头部分

execve("/sbin/mkfs.jfs",["mkfs.jfs","-f","/dev/test1"],&

...

open("/dev/test1",O_RDWR|O_LARGEFILE)=4

stat64("/dev/test1",{st_mode=&,st_rdev=makedev(63,255),...})=0

ioctl(4,0x40041271,0xbfffe128)=-1EINVAL(Invalidargument)

write(2,"mkfs.jfs:

warning-cannotsetb"...,98mkfs.jfs:

warning-

cannotsetblocksizeonblockdevice/dev/test1:

Invalidargument)

=98

stat64("/dev/test1",{st_mode=&,st_rdev=makedev(63,255),...})=0

open("/dev/test1",O_RDONLY|O_LARGEFILE)=5

ioctl(5,0x80041272,0xbfffe124)=-1EINVAL(Invalidargument)

write(2,"mkfs.jfs:

can\'tdeterminedevice"...,..._exit

(1)

=?

清单6显示ioctl调用导致用来格式化分区的mkfs程序失败。

ioctlBLKGETSIZE64失败。

(BLKGET-SIZE64在调用ioctl的源代码中定义。

)BLKGETSIZE64ioctl将被添加到Linux中所有的设备,而在这里,逻辑卷管理器还不支持它。

因此,如果BLKGETSIZE64ioctl调用失败,mkfs代码将改为调用较早的ioctl调用;这使得mkfs适用于逻辑卷管理器。

第3种情况:

使用gdb和Oops

您可以从命令行使用gdb程序(FreeSoftwareFoundation的调试器)来找出错误,也可以从诸如DataDisplayDebugger(DDD)这样的几个图形工具之一使用gdb程序来找出错误。

您可以使用gdb来调试用户空间程序或Linux内核。

这一部分只讨论从命令行运行gdb的情况。

使用gdbprogramname命令启动gdb。

gdb将载入可执行程序符号并显示输入提示符,让您可以开始使用调试器。

您可以通过三种方式用gdb查看进程:

∙使用attach命令开始查看一个已经运行的进程;attach将停止进程。

∙使用run命令执行程序并从头开始调试程序。

∙查看已有的核心文件来确定进程终止时的状态。

要查看核心文件,请用下面的命令启动gdb。

gdbprogramnamecorefilename

要用核心文件进行调试,您不仅需要程序的可执行文件和源文件,还需要核心文件本身。

要用核心文件启动gdb,请使用-c选项:

gdb-ccoreprogramname

gdb显示哪行代码导致程序发生核心转储。

在运行程序或连接到已经运行的程序之前,请列出您觉得有错误的源代码,设置断点,然后开始调试程序。

您可以使用help命令查看全面的gdb在线帮助和详细的教程。

kgdb

kgdb程序(使用gdb的远程主机Linux内核调试器)提供了一种使用gdb调试Linux内核的机制。

kgdb程序是内核的扩展,它让您能够在远程主机上运行gdb时连接到运行用kgdb扩展的内核机器。

您可以接着深入到内核中、设置断点、检查数据并进行其它操作(类似于您在应用程序上使用gdb的方式)。

这个补丁的主要特点之一就是运行gdb的主机在引导过程中连接到目标机器(运行要被调试的内核)。

这让您能够尽早开始调试。

请注意,补丁为Linux内核添加了功能,所以gdb可以用来调试Linux内核。

使用kgdb需要两台机器:

一台是开发机器,另一台是测试机器。

一条串行线(空调制解调器电缆)将通过机器的串口连接它们。

您希望调试的内核在测试机器上运行;gdb在开发机器上运行。

gdb使用串行线与您要调试的内核通信。

请遵循下面的步骤来设置kgdb调试环境:

1.下载您的Linux内核版本适用的补丁。

2.将组件构建到内核,因为这是使用kgdb最简单的方法。

(请注意,有两种方法可以构建多数内核组件,比如作为模块或直接构建到内核中。

举例来说,日志纪录文件系统(JournaledFileSystem,JFS)可以作为模块构建,或直接构建到内核中。

通过使用gdb补丁,我们就可以将JFS直接构建到内核中。

3.应用内核补丁并重新构建内核。

4.创建一个名为.gdbinit的文件,并将其保存在内核源文件子目录中(换句话说就是/usr/src/linux)。

文件.gdbinit中有下面四行代码:

osetremotebaud115200

osymbol-filevmlinux

otargetremote/dev/ttyS0

osetoutput-radix16

5.将append=gdb这一行添加到lilo,lilo是用来在引导内核时选择使用哪个内核的引导载入程序。

oimage=/boot/bzImage-2.4.17

olabel=gdb2417

oread-only

oroot=/dev/sda8

oappend="gdbgdbttyS=1gdb-baud=115200nmi_watchdog=0"

清单7是一个脚本示例,它将您在开发机器上构建的内核和模块引入测试机器。

您需要修改下面几项:

∙best@sfb:

用户标识和机器名。

∙/usr/src/linux-2.4.17:

内核源代码树的目录。

∙bzImage-2.4.17:

测试机器上将引导的内核名。

∙rcp和rsync:

必须允许它在构建内核的机器上运行。

清单7.引入测试机器的内核和模块的脚本

set-x

rcpbest@sfb:

/usr/src/linux-2.4.17/arch/i386/boot/bzImage/boot/bzImage-2.4.17

rcpbest@sfb:

/usr/src/linux-2.4.17/System.map/boot/System.map-2.4.17

rm-rf/lib/modules/2.4.17

rsync-abest@sfb:

/lib/modules/2.4.17/lib/modules

chown-Rroot/lib/modules/2.4.17

lilo

现在我们可以通过改为使用内核源代码树开始的目录来启动开发机器上的gdb程序了。

在本示例中,内核源代码树位于/usr/src/linux-2.4.17。

输入gdb启动程序。

如果一切正常,测试机器将在启动过程中停止。

输入gdb命令cont以继续启动过程。

一个常见的问题是,空调制解调器电缆可能会被连接到错误的串口。

如果gdb不启动,将端口改为第二个串口,这会使gdb启动。

使用kgdb调试内核问题

清单8列出了jfs_mount.c文件的源代码中被修改过的代码,我们在代码中创建了一个空指针异常,从而使代码在第109行产生段错误。

清单8.修改过后的jfs_mount.c代码

intjfs_mount(structsuper_block*sb)

{

...

intptr;/*line1added*/

jFYI(1,("\nMountJFS\n"));

/*

*read/validatesuperblock

*(initializemountinodefromthesuperblock)

*/

if((rc=chkSuper(sb))){

gotoerrout20;

}

108ptr=0;/*line2added*/

109printk("%d\n",*ptr);/*line3added*/

清单9在向文件系统发出mount命令之后显示一个gdb异常。

kgdb提供了几条命令,如显示数据结构和变量值以及显示系统中的所有任务处于什么状态、它们驻留在何处、它们在哪些地方使用了CPU等等。

清单9将显示回溯跟踪为该问题提供的信息;where命令用来执行反跟踪,它将告诉被执行的调用在代码中的什么地方停止。

清单9.gdb异常和反跟踪

mount-tjfs/dev/sdb/jfs

ProgramreceivedsignalSIGSEGV,Segmentationfault.

jfs_mount(sb=0xf78a3800)atjf

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 法律文书 > 起诉状

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1