VMware虚拟机是比较占用资源的,尤其是象上面那样在Windows中使用两台虚拟机。因此,最好为系统配备512M以上的内存,每台虚拟机至少分配128M的内存。这样的硬件要求,对目前主流配置的PC而言并不是过高的要求。出于系统性能的考虑,在VMware中尽量使用字符界面进行调试工作。同时,Linux系统默认情况下开启了sshd服务,建议使用SecureCRT登陆到Linux进行操作,这样可以有较好的用户使用界面。
对于在Linux下面使用VMware虚拟机的情况,笔者没有做过实际的探索。从原理上而言,只需要在Linux下只要创建一台虚拟机作为target机,开发机的工作可以在实际的Linux环境中进行,搭建调试环境的过程与上面所述的过程类似。由于只需要创建一台虚拟机,所以使用Linux下的虚拟机搭建kgdb调试环境对系统性能的要求较低。(vmware已经推出了Linux下的版本)还可以在development机上配合使用一些其他的调试工具,例如功能更强大的cgdb、图形界面的DDD调试器等,以方便内核的调试工作。
2.4kgdb的一些特点和不足
使用kgdb作为内核调试环境最大的不足在于对kgdb硬件环境的要求较高,必须使用两台计算机分别作为target和development机。尽管使用虚拟机的方法可以只用一台PC即能搭建调试环境,但是对系统其他方面的性能也提出了一定的要求,同时也增加了搭建调试环境时复杂程度。另外,kgdb内核的编译、配置也比较复杂,需要一定的技巧,笔者当时做的时候也是费了很多周折。当调试过程结束后时,还需要重新制作所要发布的内核。使用kgdb并不能进行全程调试,也就是说kgdb并不能用于调试系统一开始的初始化引导过程。
不过,kgdb是一个不错的内核调试工具,使用它可以进行对内核的全面调试,甚至可以调试内核的中断处理程序。如果在一些图形化的开发工具的帮助下,对内核的调试将更方便。
回页首
3.使用SkyEye构建Linux内核调试环境
SkyEye是一个开源软件项目(OPenSourceSoftware),SkyEye项目的目标是在通用的Linux和Windows平台上模拟常见的嵌入式计算机系统。SkyEye实现了一个指令级的硬件模拟平台,可以模拟多种嵌入式开发板,支持多种CPU指令集。SkyEye的核心是GNU的gdb项目,它把gdb和ARMSimulator很好地结合在了一起。加入ARMulator的功能之后,它就可以来仿真嵌入式开发板,在它上面不仅可以调试硬件驱动,还可以调试操作系统。Skyeye项目目前已经在嵌入式系统开发领域得到了很大的推广。
3.1SkyEye的安装和μcLinux内核编译
3.1.1SkyEye的安装
SkyEye的安装不是本文要介绍的重点,目前已经有大量的资料对此进行了介绍。有关SkyEye的安装与使用的内容请查阅参考资料[11]。由于skyeye面目主要用于嵌入式系统领域,所以在skyeye上经常使用的是μcLinux系统,当然使用Linux作为skyeye上运行的系统也是可以的。由于介绍μcLinux2.6在skyeye上编译的相关资料并不多,所以下面进行详细介绍。
3.1.2μcLinux2.6.x的编译
要在SkyEye中调试操作系统内核,首先必须使被调试内核能在SkyEye所模拟的开发板上正确运行。因此,正确编译待调试操作系统内核并配置SkyEye是进行内核调试的第一步。下面我们以SkyEye模拟基于AtmelAT91X40的开发板,并运行μcLinux2.6为例介绍SkyEye的具体调试方法。
I、安装交叉编译环境
先安装交叉编译器。尽管在一些资料中说明使用工具链arm-elf-tools-20040427.sh,但是由于arm-elf-xxx与arm-linux-xxx对宏及链接处理的不同,经验证明使用arm-elf-xxx工具链在链接vmlinux的最后阶段将会出错。所以这里我们使用的交叉编译工具链是:arm-uclinux-tools-base-gcc3.4.0-20040713.sh,关于该交叉编译工具链的下载地址请参见[6]。注意以下步骤最好用root用户来执行。
[root@lisltmp]#chmod+xarm-uclinux-tools-base-gcc3.4.0-20040713.sh[root@lisltmp]#./arm-uclinux-tools-base-gcc3.4.0-20040713.sh
安装交叉编译工具链之后,请确保工具链安装路径存在于系统PATH变量中。
II、制作μcLinux内核
得到μcLinux发布包的一个最容易的方法是直接访问uClinux.org站点[7]。该站点发布的内核版本可能不是最新的,但你能找到一个最新的μcLinux补丁以及找一个对应的Linux内核版本来制作一个最新的μcLinux内核。这里,将使用这种方法来制作最新的μcLinux内核。目前(笔者记录编写此文章时),所能得到的发布包的最新版本是uClinux-dist.20041215.tar.gz。
下载uClinux-dist.20041215.tar.gz,文件的下载地址请参见[7]。
下载linux-2.6.9-hsc0.patch.gz,文件的下载地址请参见[8]。
下载linux-2.6.9.tar.bz2,文件的下载地址请参见[9]。
现在我们得到了整个的linux-2.6.9源代码,以及所需的内核补丁。请准备一个有2GB空间的目录里来完成以下制作μcLinux内核的过程。
[root@lisltmp]#tar-jxvfuClinux-dist-20041215.tar.bz2[root@lisluClinux-dist]#tar-jxvflinux-2.6.9.tar.bz2[root@lisluClinux-dist]#gzip-dclinux-2.6.9-hsc0.patch.gz|patch-p0
或者使用:
[root@lisluClinux-dist]#gunziplinux-2.6.9-hsc0.patch.gz[root@lisluClinux-dist]patch-p0<linux-2.6.9-hsc0.patch
执行以上过程后,将在linux-2.6.9/arch目录下生成一个补丁目录-armnommu。删除原来μcLinux目录里的linux-2.6.x(即那个linux-2.6.9-uc0),并将我们打好补丁的Linux内核目录更名为linux-2.6.x。
[root@lisluClinux-dist]#rm-rflinux-2.6.x/[root@lisluClinux-dist]#mvlinux-2.6.9linux-2.6.x
III、配置和编译μcLinux内核
因为只是出于调试μcLinux内核的目的,这里没有生成uClibc库文件及romfs.img文件。在发布μcLinux时,已经预置了某些常用嵌入式开发板的配置文件,因此这里直接使用这些配置文件,过程如下:
[root@lisluClinux-dist]#cdlinux-2.6.x[root@lisllinux-2.6.x]#makeARCH=armnommuCROSS_COMPILE=arm-uclinux-atmel_deconfig
atmel_deconfig文件是μcLinux发布时提供的一个配置文件,存放于目录linux-2.6.x/arch/armnommu/configs/中。
[root@lisllinux-2.6.x]#makeARCH=armnommuCROSS_COMPILE=arm-uclinux-oldconfig
下面编译配置好的内核:
[root@lisllinux-2.6.x]#makeARCH=armnommuCROSS_COMPILE=arm-uclinux-v=1
一般情况下,编译将顺利结束并在Linux-2.6.x/目录下生成未经压缩的μcLinux内核文件vmlinux。需要注意的是为了调试μcLinux内核,需要打开内核编译的调试选项-g,使编译后的内核带有调试信息。打开编译选项的方法可以选择:
"Kerneldebugging->Compilethekernelwithdebuginfo"后将自动打开调试选项。也可以直接修改linux-2.6.x目录下的Makefile文件,为其打开调试开关。方法如下:。
CFLAGS+=-g
最容易出现的问题是找不到arm-uclinux-gcc命令的错误,主要原因是PATH变量中没有包含arm-uclinux-gcc命令所在目录。在arm-linux-gcc的缺省安装情况下,它的安装目录是/root/bin/arm-linux-tool/,使用以下命令将路径加到PATH环境变量中。
ExportPATH=$PATH:/root/bin/arm-linux-tool/bin
IV、根文件系统的制作
Linux内核在启动的时的最后操作之一是加载根文件系统。根文件系统中存放了嵌入式系统使用的所有应用程序、库文件及其他一些需要用到的服务。出于文章篇幅的考虑,这里不打算介绍根文件系统的制作方法,读者可以查阅一些其他的相关资料。值得注意的是,由配置文件skyeye.conf指定了装载到内核中的根文件系统。
3.2使用SkyEye调试
编译完μcLinux内核后,就可以在SkyEye中调试该ELF执行文件格式的内核了。前面已经说过利用SkyEye调试内核与使用gdb调试运用程序的方法相同。
需要提醒读者的是,SkyEye的配置文件-skyeye.conf记录了模拟的硬件配置和模拟执行行为。该配置文件是SkyEye系统中一个及其重要的文件,很多错误和异常情况的发生都和该文件有关。在安装配置SkyEye出错时,请首先检查该配置文件然后再进行其他的工作。此时,所有的准备工作已经完成,就可以进行内核的调试工作了。
3.3使用SkyEye调试内核的特点和不足
在SkyEye中可以进行对Linux系统内核的全程调试。由于SkyEye目前主要支持基于ARM内核的CPU,因此一般而言需要使用交叉编译工具编译待调试的Linux系统内核。另外,制作SkyEye中使用的内核编译、配置过程比较复杂、繁琐。不过,当调试过程结束后无需重新制作所要发布的内核。
SkyEye只是对系统硬件进行了一定程度上的模拟,所以在SkyEye与真实硬件环境相比较而言还是有一定的差距,这对一些与硬件紧密相关的调试可能会有一定的影响,例如驱动程序的调试。不过对于大部分软件的调试,SkyEye已经提供了精度足够的模拟了。
SkyEye的下一个目标是和eclipse结合,有了图形界面,能为调试和查看源码提供一些方便。
回页首
4.使用UML调试Linux内核
User-modeLinux(UML)简单说来就是在Linux内运行的Linux。该项目是使Linux内核成为一个运行在Linux系统之上单独的、用户空间的进程。UML并不是运行在某种新的硬件体系结构之上,而是运行在基于Linux系统调用接口所实现的虚拟机。正是由于UML是一个将Linux作为用户空间进程运行的特性,可以使用UML来进行操作系统内核的调试。有关UML的介绍请查阅参考资料[10]、[12]。
4.1UML的安装与调试
UML的安装需要一台运行Linux2.2.15以上,或者2.3.22以上的I386机器。对于2.6.8及其以前版本的UML,采用两种形式发布:一种是以RPM包的形式发布,一种是以源代码的形式提供UML的安装。按照UML的说明,以RPM形式提供的安装包比较陈旧且会有许多问题。以二进制形式发布的UML包并不包含所需要的调试信息,这些代码在发布时已经做了程度不同的优化。所以,要想利用UML调试Linux系统内核,需要使用最新的UMLpatch代码和对应版本的Linux内核编译、安装UML。完成UML的补丁之后,会在arch目录下产生一个um目录,主要的UML代码都放在该目录下。
从2.6.9版本之后(包含2.6.9版本的Linux),User-ModeLinux已经随Linux内核源代码树一起发布,它存放于arch/um目录下。
编译好UML的内核之后,直接使用gdb运行已经编译好的内核即可进行调试。
4.2使用UML调试系统内核的特点和不足
目前,用户模式Linux虚拟机也存在一定的局限性。由于UML虚拟机是基于Linux系统调用接口的方式实现的虚拟机,所以用户模式内核不能访问主机系统上的硬件设备。因此,UML并不适合于调试那些处理实际硬件的驱动程序。不过,如果所编写的内核程序不是硬件驱动,例如Linux文件系统、协议栈等情况,使用UML作为调试工具还是一个不错的选择。
回页首
5.内核调试配置选项
为了方便调试和测试代码,内核提供了许多与内核调试相关的配置选项。这些选项大部分都在内核配置编辑器的内核开发(kernelhacking)菜单项中。在内核配置目录树菜单的其他地方也还有一些可配置的调试选项,下面将对他们作一定的介绍。
Pageallocdebugging:CONFIG_DEBUG_PAGEALLOC:
不使用该选项时,释放的内存页将从内核地址空间中移出。使用该选项后,内核推迟移出内存页的过程,因此能够发现内存泄漏的错误。
Debugmemoryallocations:CONFIG_DEBUG_SLAB:
该打开该选项时,在内核执行内存分配之前将执行多种类型检查,通过这些类型检查可以发现诸如内核过量分配或者未初始化等错误。内核将会在每次分配内存前后时设置一些警戒值,如果这些值发生了变化那么内核就会知道内存已经被操作过并给出明确的提示,从而使各种隐晦的错误变得容易被跟踪。
Spinlockdebugging:CONFIG_DEBUG_SPINLOCK:
打开此选项时,内核将能够发现spinlock未初始化及各种其他的错误,能用于排除一些死锁引起的错误。
Sleep-inside-spinlockchecking:CONFIG_DEBUG_SPINLOCK_SLEEP:
打开该选项时,当spinlock的持有者要睡眠时会执行相应的检查。实际上即使调用者目前没有睡眠,而只是存在睡眠的可能性时也会给出提示。
Compilethekernelwithdebuginfo:CONFIG_DEBUG_INFO:
打开该选项时,编译出的内核将会包含全部的调试信息,使用gdb时需要这些调试信息。
Stackutilizationinstrumentation:CONFIG_DEBUG_STACK_USAGE:
该选项用于跟踪内核栈的溢出错误,一个内核栈溢出错误的明显的现象是产生oops错误却没有列出系统的调用栈信息。该选项将使内核进行栈溢出检查,并使内核进行栈使用的统计。
DriverCoreverbosedebugmessages:CONFIG_DEBUG_DRIVER:
该选项位于"Devicedrivers->GenericDriverOptions"下,打开该选项使得内核驱动核心产生大量的调试信息,并将他们记录到系统日志中。
VerboseSCSIerrorreporting(kernelsize+=12K):CONFIG_SCSI_CONSTANTS:
该选项位于"Devicedrivers/SCSIdevicesupport"下。当SCSI设备出错时内核将给出详细的出错信息。
Eventdebugging:CONFIG_INPUT_EVBUG:
打开该选项时,会将输入子系统的错误及所有事件都输出到系统日志中。该选项在产生了详细的输入报告的同时,也会导致一定的安全问题。
以上内核编译选项需要读者根据自己所进行的内核编程的实际情况,灵活选取。在使用以上介绍的三种源代码级的内核调试工具时,一般需要选取CONFIG_DEBUG_INFO选项,以使编译的内核包含调试信息。
回页首
6.总结
上面介绍了一些调试Linux内核的方法,特别是详细介绍了三种源代码级的内核调试工具,以及搭建这些内核调试环境的方法,读者可以根据自己的情况从中作出选择。
调试工具(例如gdb)的运行都需要操作系统的支持,而此时内核由于一些错误的代码而不能正确执行对系统的管理功能,所以对内核的调试必须采取一些特殊的方法进行。以上介绍的三种源代码级的调试方法,可以归纳为以下两种策略:
I、为内核增加调试Stub,利用调试Stub进行远程调试,这种调试策略需要target及development机器才能完成调试任务。
II、将虚拟机技术与调试工具相结合,使Linux内核在虚拟机中运行从而利用调试器对内核进行调试。这种策略需要制作适合在虚拟机中运行的系统内核。
由不同的调试策略决定了进行调试时不同的工作原理,同时也形成了各种调试方法不同的软硬件需求和各自的特点。
另外,需要说明的是内核调试能力的掌握很大程度上取决于经验和对整个操作系统的深入理解。对系统内核的全面深入的理解,将能在很大程度上加快对Linux系统内核的开发和调试。
对系统内核的调试技术和方法绝不止上面介绍所涉及的内容,这里只是介绍了一些经常看到和听到方法。在Linux内核向前发展的同时,内核的调试技术也在不断的进步。希望以上介绍的一些方法能对读者开发和学习Linux有所帮助。