一、内核调试的原理
传统调试内核需要配置两台Linux主机,其中一台主机称为开发机,另一台主机称为目标机。开发机中除了有一个可运行的Linux环境,还应包含必要的开发环境(gcc、gdb、make等)以及内核源码,目标机则需要能够启动内核并挂载一个有效的文件系统,可以将运行在目标机中待调试内核称为目标内核,目标内核在开发机中编译。为了使后续内核调试过程能够顺利进行,在编译内核前需要对其做如下配置选项的选择:
-
打开调试信息选项,这样在编译出的目标内核中会包含符号表等调试信息。
-
早期内核需要为源码打kgdb补丁,现在kgdb已经合并到2.6.26版之后的内核中,所以不再需要打这个补丁,但编译前仍需将kdgb的配置选项打开。
-
目标机网卡驱动程序、TCP/IP协议栈及相关的通信软件(ftp, ssh等)。
-
串行口驱动程序。
开发机与目标机之间通常需要通过网络连接,这样开发机中编译出的内核可执行文件以及其他在内核调试过程中可能会用到的支撑文件(initrd、内核模块、应用程序)都可以通过网络传送到目标机中。目标机启动后,目标内核中的kgdb会接管异常处理工作。kgdb中包含一个gdb的stub,当kgdb发现内核中某些异常事件(遇到调试断点、非法的内存访问等)发生时,需要将这些情况通知开发机中的gdb;反之,gdb的调试命令(设置断点、单步执行等)也需要传递到目标内核的gdb stub中,再由stub完成这些规定的调试功能。所以,通常开发机和目标机需要通过串行线连接,开发机中的gdb与目标机中的stub就可以通过该串行线进行通信。由此不难看出,采用传统的内核调试方法,搭建调试环境是很繁琐的,而且要求的硬件设备也比较多。
Qemu是一个虚拟机运行环境,它可以在一台实际的计算机中模拟出一台虚拟机,实际的计算机称为宿主机,而模拟出的计算机称为客户机。利用Qemu可以简化内核调试环境的搭建工作。Qemu提供了一个运行在虚拟机中gdb stub,这个stub具有与kgdb的stub类似的功能,利用它可以实现对在客户机中运行内核的调试。利用Qemu来搭建内核调试环境相对比较简单易行,而且对硬件设备的要求也比较少。总体的思路:首先在一台Linux宿主机中安装Qemu、gcc、gdb、make以及目标内核源代码。然后,在宿主机中编译目标内核源码并用Qemu将编译出的目标内核可执行文件在一台客户机中启动。启动后,宿主机就作为开发机,而客户机则作为目标机。最后,用开发机中的gdb对目标机中运行的目标内核做调试。
二、开发机的配置
我在开发机中安装的是Ubuntu 10.10,需要在其中安装开发环境的目标内核源码,目标内核源码采用vanilla Linux 2.6.34。
1. 安装开发环境所使用的各种软件包
sudo apt-get install gcc
sudo apt-get install make
sudo apt-get install gdb
2. 安装Qemu:由于ubuntu 10.10提供的Qemu软件包版本较低,所以考虑以直接编译Qemu源码的方式安装该软件。此外,在Qemu需要依赖zlib、glib和SDL软件包,所以在编译Qemu之前首先需要安装着几个软件包。
sudo apt-get install zlib1g-dev
sudo apt-get install libglib2.0-dev
但是,对于ubuntu 10.10提供的SDL版本较低,Qemu运行时会出现问题,所以没有采用ubuntu 10.10提供的版本,而是从SDL最新版的源码编译安装。
wget
tar zxvf SDL-1.2.15.tar.gz; cd SDL-1.2.15
./configure; make; make install
接下来就可以安装Qemu了
wget
tar jxvf qemu-0.15.1.tar.gz; cd qemu-0.15.1
./configure --target-list="i386-softmmu i386-linux-user"
make; make install
注意,在编译前配置选项中可以指定需要Qemu模拟的目标体系结构,由于我只需要模拟IA-32,所以就指定了i386-softmmu和i386-linux-user。如果这里不做指定,那么默认会编译对所有目标体系结构支持的代码,这样的话编译时间会非常漫长。
3. 编译目标内核
首先安装ncurses软件包以支持后面的内核配置
sudo apt-get install libncurses5-dev
然后下载内核源码
cd /usr/src
wget
tar jxvf linux-2.6.34.tar.bz2
ln -s linux-2.6.34 linux
cd linux
目标内核在编译前需要做配置,可以将开发机的配置文件(.config文件)复制到目标内核源码树的顶层目录中。然后执行
make menuconfig
为了能够进行源码调试,需要打开调试信息内核选项
kernel hacking
--> compile kernel with debug info
当然,为了加快内核的编译速度,也可以考虑将内核中大量不必要的特性取消,尤其是众多的驱动程序都可以不选。接下来就可以编译了。
make
编译生成的vmlinux是带调试信息的内核可执行文件,开发机中的gdb将加载该文件以获得内核符号的信息。arch/x86/boot/bzImage则是内核可执行文件压缩过之后产生的文件,该文件将由Qemu加载并在目标机中启动。在开发机中执行下面的命令测试内核是否可以启动。
qemu -kernel arch/x86/boot/bzImage
三、目标机的配置
目标机实际上是一台虚拟机,在其中除了要运行Qemu加载的目标内核外,通常还需要挂载一个根文件系统。利用Qemu提供的工具可以为目标机创建一个虚拟硬盘,然后在启动Qemu时将该虚拟硬盘同时加载。但是这种做法比较麻烦,因为这个虚拟硬盘还需要利用其它Qemu的虚拟机对其格式化并安装必要的启动脚本和一些基本应用软件。所以,可以考虑利用busybox制作一个initramfs镜像文件,然后在Qemu启动时与内核一同加载,这样在目标内核启动后就可以将其挂载为根文件系统了。当然,为了使目标内核能够正常加载initramfs镜像文件,需要在编译目标内核时选择下面的内核选项:
Device Drivers
--> Block devices
--> RAM block device support
这个initramfs镜像可以考虑包含如下内容:
-
init程序。这是目标内核启动后执行的第一个程序,可以将busybox提供的linuxrc改名为init即可获得。
-
如果需要调试内核模块,那么需要包含几个用于处理内核模块的程序(insmod、rmmod等)。同时待调试的内核模块在开发机中编译好之后,也一并包含在initramfs镜像中。
-
如果需要调试系统调用,那么还要包含用于触发系统调用的应用程序。这个应用程序也是在开发机中编译好之后被放入initramfs镜像中。
具体制作initramfs的方法可以参考《Qemu虚拟机中挂载NFS》。假设利用busybox创建的initramfs镜像文件名为image.cpio.gz,并将其放在内核源码的顶层目录中,那么在开发机中用下面的命令就可以在启动目标内核的同时加载initramfs镜像文件:
qemu -kernel arch/x86/boot/bzImage -initrd image.cpio.gz
四、调试目标内核
目标内核启动后,在开发机中就可以利用gdb对其进行调试。但这里还有一个开发机与目标机通信的问题。一方面,前面提到过Qemu提供了一个gdb stub,用于控制目标内核的运行。另一方面,开发机中运行的gdb可以通过一个指定的TCP端口与该stub联系,从而向stub发出调试命令同时接收stub向其返回的目标内核运行状态。Qemu提供的-gdb选项就是用于指定这样一个TCP端口。
qemu -kernel arch/x86/boot/bzImage -initrd image.cpio.gz -gdb tcp::1234 &
上面的命令指定TCP端口号1234用于gdb与stub之间的通信。此时qemu在后台运行,接下来就可以在命令行上启动gdb对目标内核做调试了。
gdb vmlinux
(gdb) target remote localhost:1234
在gdb中会看到它在一个断点处接管了目标内核的运行,此时可以利用gdb的各种命令来完成查看变量、设置断点、单步运行、让目标内核继续运行等调试操作。
实际上,上面启动Qemu的命令还可以这样写:
qemu -kernel arch/x86/boot/bzImage -initrd image.cpio.gz -s &
其中的-s选项与“-gdb tcp::1234”是完全等价的。
五、开发机与目标机共享文件系统
在内核调试过程中,经常需要将开发机中生成的文件(例如,内核模块、应用程序)传送到目标机中,所以需要在开发机和目标机之间共享文件。如果采用网络传送(SSH或FTP等)或U盘拷贝固然可行,但比较麻烦并且效率很低,所以可以考虑利用NFS实现两机共享同一个文件系统。具体的思路是:在开发机中搭建一个NFS服务器,并将开发机的根文件系统导出。然后在目标机中挂载开发机导出的根文件系统,这样就可以在目标机中直接存取开发机中生成的文件了。
为了实现上述想法,首先要解决开发机和目标机之间的通信问题。QEMU提供了四种网络通信模式:TAP、user、Sockets和VDE,利用user模式可以实现虚拟机和宿主机之间的通信且较为简单易行。在这种通信模式中,虚拟机处于10.0.2.*网段中,该网段通过一个NAT服务器与外界通信,NAT服务器的地址是10.0.2.2,同时该NAT服务器还提供了DHCP服务,虚拟机的IP地址从10.0.2.15开始分配。
首先在开发机中安装并启动NFS服务器:
apt-get install nfs-kernel-server
/etc/init.d/nfs-kernel-server start
如果系统中运行了防火墙,最好将其关闭。在NFS服务的配置文件/etc/exports中添加:
/ *(rw,no_root_squash,insecure)
将开发机的整个根文件系统导出。注意:为了使目标机在NAT中能够连接开发机的NFS服务,需要加上insecure选项。在开发机中重新启动NFS服务:
/etc/init.d/nfs-kernel-server restart
此外,为了使目标内核能够支持NFS客户端,需要打开如下目标内核选项:
File system
--> Network File System
--> NFS client support
然后,在开发机中启动QEMU:
qemu -kernel arch/x86/boot/bzImage -initrd image.cpio.gz \
-net user -net nic,model=pcnet -s
这里在目标机中启用了AMD PCnet32网卡,所以应注意在目标内核中编入对应的网卡驱动程序。最后,在目标机中配置网络、挂载NFS文件系统、切换根文件系统:
ifconfig eth0 10.0.2.15 up
route add default gw 10.0.2.2
mount -t nfs -o nolock 192.168.0.24:/ /mnt
mount -t proc proc /mnt/proc
mount -t sysfs sysfs /mnt/sysfs
chroot /mnt /bin/bash --login
此时,目标机的根文件系统就是开发机的根文件系统,在目标机中可以直接存取开发机中的文件了。
阅读(5393) | 评论(1) | 转发(0) |