掌握 Linux 调试技术Word文档下载推荐.docx
《掌握 Linux 调试技术Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《掌握 Linux 调试技术Word文档下载推荐.docx(16页珍藏版)》请在冰豆网上搜索。
使用调试器将使找出所有这些信息变得很简单。
如果没有调试器可用,您还可以使用其它的工具。
(请注意,产品环境中可能并不提供调试器,而且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<
stdlib.h>
stdio.h>
#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:
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
Address0x40028e00,size512
Normaldeallocationofthisblock
ERROR:
MultiplefreeingAt
freeofpointeralreadyfreed
WARNING:
Memoryleak
Totalmemoryleaks:
1unfreedallocationstotaling512bytes
***FinishedatTue...10:
07:
152002
Allocatedagrandtotalof1024bytes2allocations
Averageof512bytesperallocation
Maxbytesallocatedatonetime:
1024
24Kallocedinternally/12Kmappednow/8Kmax
Virtualprogramsizeis1416K
End.
YAMD显示我们已经释放了内存,而且存在内存泄漏。
让我们在清单4中另一个样本程序上试试YAMD。
清单4.内存代码(test2.c)
char*chptr;
inti=1;
chptr=(char*)malloc(512);
for(i;
i<
=512;
i++){
chptr[i]='
S'
;
}
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)
Startingrun:
/usr/src/test/test2/test2
Virtualprogramsizeis1380K
Address0x4002be00,size512
Crash
Triedtowriteaddress0x4002c000
Seemstobepartofthisblock:
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("
O_RDWR|O_LARGEFILE)=4
stat64("
{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
O_RDONLY|O_LARGEFILE)=5
ioctl(5,0x80041272,0xbfffe124)=-1EINVAL(Invalidargument)
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
/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