通过GDB和QEMU调试Linux内核已经有很多介绍了,但基本都是制作简单的根文件系统。有时候需要调试的模块或者场景需要用到发行版的Linux,因此本文介绍调试CentOS内核的步骤。
GDB调试Linux内核关键步骤
- 首先需要有一个可以启动运行Cent OS 7的虚拟机
- 准备一个qcow2格式的磁盘镜像文件centos.img.qcow2,通过CentOS的光盘iso将系统安装到这个磁盘镜像中。
- 虚拟机需要连接外部网络(例如mount nfs文件系统、调试内核的网络代码等),所以我们要为QEMU虚拟机选择合适的网络方案
- 通过将tap和物理网卡加入到bridge
- GDB的调试需要源代码和编译生成的原始文件vmlinux,因此我们要下载Linux内核源代码,完成patch、configure和编译等工作;
- 编译Linux内核的最终产品为bzImage,因此要能够更新虚拟机中的内核bzImage以及GRUB配置;
- QEMU内置一个gdbserver,通过”-s -S”选项来开启并等待GDB的连接,默认端口为1234
- -S freeze CPU at startup (use 'c' to start execution)
- -s shorthand for -gdb tcp::1234
- 也可以在QEMU命令行中通过gdbserver tcp::1234来指定gdbserver的端口号
安装CentOS 7.1到虚拟磁盘
从CentOS网站上下载CentOS 7的安装包CentOS-7-x86_64-DVD-1503-01.iso
qemu-img create centos.img.qcow2 -f qcow2 40G
qemu-system-x86_64 -cdrom CentOS-7-x86_64-DVD-1503-01.iso -hda centos.img.qcow2 -boot d -net nic -net user -m 4096 -vnc 0.0.0.0:1 -enable-kvm -smp 2
- 通过VNC View连接ip:5901进入虚拟机的控制台,在控制台中完成CentOS 7.1的安装过程
- 安装完成后得到虚拟机的磁盘镜像centos.img.qcow2
- 重新启动QEMU虚拟机:
qemu-system-x86_64 -m 4096 -vnc 0.0.0.0:1 centos.img.qcow2
通过VNC Viewer连接到虚拟机的控制台,确认CentOS 7正常启动
QEMU虚拟机网络配置
QEMU支持多种方式的网络配置,对于需要访问外部网络的虚拟机,最合适的方式是tap桥接模式,即将虚拟机网卡对应的tap网卡和Host的物理网卡加入到同一个bridge中
- 创建bridge并将物理网卡加入到该bridge中
/home/gj/virt/qemu-kernel/net_config.sh
#!/bin/sh
ifconfig enp2s0 0.0.0.0
brctl addbr br0
brctl addif br0 enp2s0
dhclient br0
- QEMU创建虚拟机时指定-net nic -net tap,script=/etc/qemu-ifup1,其中qemu-ifup1由QEMU初始化虚拟机网卡时调用,它负责将tap网卡加入到bridge中
/etc/qemu-ifup1
#! /bin/sh
# TAP interface will be passed in $1
ifconfig $1 up
brctl addif br0 $1
虚拟机启动之后可以看到其已经通过DHCP的方式自动获取到ip
编译Linux内核源代码
下载Linux内核的源代码可以在Linux内核网站上下载“干净”的版本,但是需要自己来配置内核,更简单的办法是在CentOS网站上下载对应版本的rpm源码包,这样不需要关心patch和内核配置,比如CentOS 7.1对应的源码包kernel-3.10.0-229.1.2.el7.src.rpm
- 安装源码包
rpm –ivh kernel-3.10.0-229.1.2.el7.src.rpm
- 通过rpmbuild -bp完成源码的patch和config工作(-bp表示prepare)
rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec
完成之后我们就得到了一套准备好的待编译的源代码,此时完全可以将这一套源代码打包拷贝到别处重复使用。rpmbuild -bb可以完成代码的prepare和编译打包的全部工作,直接生成新内核的rpm安装包。
- 进入Linux内核源码目录
cd ~/rpmbuild/BUILD/kernel-3.10.0-229.1.2.el7/linux-3.10.0-229.1.2.el7.centos.x86_64/
make bzImage && make modules
生成gdb需要的vmlinux和待安装的新内核bzImage(arch/x86_64/boot/bzImage)
安装新内核
编译完成需要将生成的内核(bzImage)和modules安装到虚拟机中去,一种方式是制作rpm包然后拷贝到虚拟机中去安装:
make binrpm-pkg INSTALL_MOD_STRIP=1
另外一种方式:在host中将内核源码通过nfs共享给虚拟机,由虚拟机自己来完成install的工作
- 进入源码目录,修改代码后编译
cd ~/rpmbuild/BUILD/kernel-3.10.0-229.1.2.el7/linux-3.10.0-229.1.2.el7.centos.x86_64
make bzImage
make modules (第一次编译安装新内核时需要)
- QEMU启动虚拟机
qemu-system-x86_64 -m 4096 -vnc 0.0.0.0:1 centos.img.qcow2 -net nic -net tap,script=/etc/qemu-ifup1 -enable-kvm -smp 2
*注意此时不需要’-s -S’选项,同时可以新增’-enable-kvm’和’-smp 2’来提高虚拟机的性能;
- 在虚拟机中mount Host导出的源码目录
showmount -e 192.168.10.115
Export list for 192.168.10.115:
/root/rpmbuild/BUILD 192.168.10.0/24
mount -t nfs 192.168.10.115:/root/rpmbuild/BUILD /mnt/nfs/
- 进入源码目录安装新编译的内核
make modules_install INSTALL_MOD_STRIP=1 (第一次编译安装新内核时需要)
make install
安装成功后重启虚拟机后通过uname -a确认内核更新成功
开始调试内核
- 检查网络配置
- 在会话1中通过QEMU创建虚拟机
cd /home/gj/virt/qemu-kernel
qemu-system-x86_64 -m 4096 -vnc 0.0.0.0:1 centos.img.qcow2 -net nic -net tap,script=/etc/qemu-ifup1 -s -S
使用VNC viewer连接ip:5901
- 会话2中进入源码目录并gdb
cd ~/rpmbuild/BUILD/kernel-3.10.0-229.1.2.el7/linux-3.10.0-229.1.2.el7.centos.x86_64
gdb vmlinux
- 在gdb中连接QEMU的gdbserver并设置断点
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000000000000000 in irq_stack_union ()
(gdb) break yfs_mount
Breakpoint 1 at 0xffffffff812b259f: file fs/yfs/super.c, line 777.
(gdb) c
Continuing.
此时通过VNC Viewer连接到虚拟机的控制台,应该可以看到启动的输出
- 在虚拟机中执行调试代码的操作,然后会话2中会被break住,此时像普通程序一样进行gdb调试;
附一:gdb编译
GDB在调试64位Linux内核时会出错:
Remote ‘g’ packet reply is too long
解决办法是下载GDB的源代码,如下所示修改remote.c
if (buf_len > 2 * rsa->sizeof_g_packet)
error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
修改为:
if (buf_len > 2 * rsa->sizeof_g_packet) {
//error (_("Remote 'g' packet reply is too long: %s"), rs->buf);
rsa->sizeof_g_packet = buf_len ;
for (i = 0; i < gdbarch_num_regs (gdbarch); i++) {
if (rsa->regs->pnum == -1)
continue;
if (rsa->regs->offset >= rsa->sizeof_g_packet)
rsa->regs->in_g_packet = 0;
else
rsa->regs->in_g_packet = 1;
}
}
重新编译并安装GDB
./configure
make && make install
附二:qemu编译
Redhat/CentOS不推荐直接使用qemu-kvm,而是需要通过libvirt的接口来创建虚拟机,因此要使用QEMU推荐下载QEMU源代码编译安装,编译安装也很简单:
./configure --target-list=x86_64-softmmu
make && make install
附三:虚拟机组网
桥接
上文介绍中QEMU虚拟机是通过桥接方式连接外部网络,其将虚拟机对应的tap网卡和物理网卡加入到同一个bridge中,将物理网卡的ip分配到bridge上,这样从逻辑上看虚拟机的报文都是通过bridge进出
桥接方式的好处是每个VM都可以获取到物理机同网段的ip,因此也可以访问物理机所属的网络
NAT
有时虚拟机不需要访问物理机所属的网络(比如受ip冲突影响),虚拟机只需要能够互相访问同时可以访问外网,这时NAT方式是比较适合的。其将所有VM对应的tap网卡加入到同一个bridge中,这个bridge作为这个私有网段的网关,每个VM启动后配置一个同网段的ip;然后物理网卡为这个bridge做NAT。
配置方式如下:
- Host网络初始化脚本
#!/bin/sh
# create bridge
brctl addbr br0-qemu
ifconfig br0-qemu 192.168.125.1
iptables -t nat -A POSTROUTING -o enp2s0 -j MASQUERADE
iptables -A FORWARD -i enp2s0 -o br0-qemu -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i br0-qemu -o enp2s0 -j ACCEPT
echo 1 > /proc/sys/net/ipv4/ip_forward
- qemu初始化脚本 /etc/qemu-ifup-nat
#! /bin/sh
# TAP interface will be passed in $1
ifconfig $1 promisc up
brctl addif br0 $1
上述方式中所有VM的ip, gateway和DNS都需要配置成固定的。如果要实现VM自动分配私有ip那么还需要在host上安装一个本地的DHCP Server