基于完整发行版搭建内核验证环境

0x01 虚拟机(VM)安装及启动

1. 选择一个发行版,下载其安装镜像

选择格式为 .iso 的安装镜像进行下载。

2. 制作根文件系统(rootfs)镜像

总体思路:启动一个 QEMU 虚拟机,以 rootfs 镜像文件为硬盘,以安装镜像 iso 文件为光盘。设置虚拟机从光盘启动,将系统安装至硬盘。

 1# create an empty rootfs image file
 2qemu-img create -f qcow2 rootfs.qcow2 128G
 3
 4# boot from CD-ROM, so that OS can be installed into the rootfs image
 5qemu-system-x86_64 \
 6	-m 4096 \
 7	-boot d \
 8	-enable-kvm \
 9	-cdrom ../ubuntu-16.04.2-server-amd64.iso \
10	-drive file=rootfs.qcow2,if=virtio \
11	-vnc :12 \
12	-daemonize

解释两个参数:

  • -vnc ,涉及 VNC 连接端口号:

    One can add the -vnc :X option to have QEMU redirect the VGA display to the VNC session. Substitute X for the number of the display (0 will then listen on 5900, 1 on 5901…).

  • -boot ,涉及启动方式:

    1-boot [order=drives][,once=drives][,menu=on|off][,splash=sp_name]
    2[,splash-time=sp_time][,reboot-timeout=rb_timeout][,strict=on|off]
    3     Specify boot order drives as a string of drive letters. Valid drive
    4     letters depend on the target achitecture. The x86 PC uses: a, b
    5     (floppy 1 and 2), c (first hard disk), d (first CD-ROM), n-p
    6     (Etherboot from network adapter 1-4), hard disk boot is the default.
    

3. 用安装好的 rootfs 镜像启动 VM 作为后续工作环境

虚拟机网络准备工作

由于后续在该环境上的多种操作(如下载代码,更新或安装软件包等)会涉及到网络,因此与上一步的启动方式不同,这里启动 QEMU 虚拟机需要配置网络。这里简要描述一下 QEMU 虚拟机的网络配置。如 QEMU 文档 所述,QEMU 虚拟机的网络配置分两部分:

  • 虚拟机内部的虚拟网络设备(Virtual Network Device),就是一张插在虚拟主板上的虚拟网卡。
  • 与虚拟网卡交互的网络后端(Network Backend),在主机侧与虚拟机的虚拟网卡对接,帮助处理虚拟网卡与外部网络以及主机上其他网络设备之间的关系。

QEMU 支持多种网络后端 。这里我们的思路如下:

  • 网络后端我们选择 TAP ,需要我们在主机上创建一个 TAP 设备;
  • 将该 TAP 设备与连接外网的网络设备以某种方式连接起来。这里分两种情况:
    • 若我们正在本机上操作,我们可以直接将两者加入同一个网桥中。
    • 若我们正在通过 SSH 远程操作一台服务器,则切不可随意操作正在与外网相连的网络设备,否则我们与服务器之间的 SSH 连接很可能会断掉,从而失去对服务器的操作能力!这种情况下,我们可以在主机上额外创建一个由虚拟设备组成的子网,让 TAP 设备接入该子网,并通过该子网的网关将 TAP 设备的流量路由到对外的网络设备上。

需要在主机上进行的相关操作总结如下:

1# 建立网桥
2brctl addbr br0
3# 给网桥设置虚拟机子网的网关 IP
4ip addr add 192.168.122.1/24 dev br0

(过时)

 1# 创建 TAP 设备 tap0(一个 VM 创建一个)
 2ip tuntap add dev tap0 mod tap
 3
 4# 建立网桥
 5brctl addbr br0
 6
 7# 将各 TAP 设备加入网桥:
 8brctl addif br0 tap0
 9
10# 确保所有设备都已开启
11ip link set br0 up
12ip link set tap0 up
13
14# 给网桥设置虚拟机子网的网关 IP
15ip addr add 192.168.122.1/24 dev br0

启动虚拟机

我们要做的是在启动 QEMU 虚拟机时创建一个 TAP 网络后端,并让它接入上述的网桥,让它们在数据链路层相连。通过查询 qemu-system-x86 --helpman qemu-system-x86(1),发现其实只要一个简单的配置即可:

1# 若网桥的名称刚好是 br0,则其实 br= 参数也不需要
2-nic bridge,br=br0

其不仅等同于下述一大串配置,简化了命令:

1# 创建一个 id 为 AAA 的 TAP 网络后端,并使其连接的 BBB 设备
2-netdev tap,id=AAA,ifname=BBB,br=br0
3# 创建一个类型为 CCC 虚拟网络设备,对应 AAA 网络后端
4-device CCC,netdev=AAA
5# AAA、BBB、CCC 及未指明的参数皆由 QEMU 自动配置

还可以用同一个命令启动多个虚拟机,而不必考虑多个网络后端有重名和重复 ID 的问题。最终完整的虚拟机启动命令:

 1#!/bin/bash
 2
 3VM_NAME=ubuntu-qemu-vm
 4DRIVE=rootfs.qcow2
 5NET_BRIDGE=br0
 6VNC_DISPLAY=:14
 7
 8sudo qemu-system-x86_64 \
 9	-name $VM_NAME \
10	-m 4096 \
11	-smp 32 \
12	-drive file=$DRIVE,if=virtio,index=0 \
13	-enable-kvm -k en-us \
14	-nic bridge,br=$NET_BRIDGE \
15	-vnc $VNC_DISPLAY \
16	-daemonize

启动完成后,需要在 VM 内部进行网络配置(或者根据不同的发行版,将与以下操作等效的命令固化在相关的配置文件中):

1# 给虚拟网卡配置子网 IP
2ip addr add 192.168.122.10/24 dev eth0
3
4# 配置默认路由,将所有流量导向子网网关
5ip route add default via 192.168.122.1 dev eth0

另外,有的发行版没有自带 SSH 服务器,需要在 VM 内另行安装,以便让外界能够通过 SSH 或 SFTP 连接 VM:

1sudo apt install openssh-server openssh-sftp-server

0x02 用新内核启动

  1. 将内核代码拷入虚拟机中

  2. 安装编译内核代码所需的开发环境

  3. /boot 中找到当前内核的 config,拷贝至 <kernel_src_dir>/.config

  4. make ARCH=... menuconfig 一下,直接保存退出(据说是可以自动修复某些 config 中的问题)

  5. 编译内核及模块:

    1make zImage -jxx  # 编内核
    2make modules -jxx # 编模块
    3
    4# 同时编译两者
    5make -j$(nproc)
    6
    7# 交叉编译时要配置 ARCH 和 CROSS_COMPILE 参数
    8make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -jxx
    
  6. 将新内核和模块安装到系统中:

    1sudo make modules_install # 安装模块
    2sudo make install # 安装内核
    3
    4# 与编译不同,make install 不包含 make modules_install
    
    • 遇到过这样一种情况:虚拟机镜像一开始分配的不够大导致 /boot 分区容量有限,make installmkinitramfs 报错:No space left on device。经查,发现 /boot/initrd.img-XXX 文件非常大。最后查明是生成 /boot/initrd.img-XXX 的时候会带上 /lib/modules/<kernel_version> 下的内核模块,而这些内核模块没有剥离调试信息从而导致的。可以在安装模块时自动剥离

      1sudo make INSTALL_MOD_STRIP=1 modules_install
      

      也可以安装后手动剥离

      1cd /lib/modules/<new_kernel>
      2find . -name *.ko -exec strip --strip-unneeded {} +
      
    • 内核其他安装项(来源 ):

      1sudo make headers_install INSTALL_HDR_PATH=/usr
      2sudo make firmware_install INSTALL_FW_PATH=/some/path
      3# For other options
      4make help
      
  7. 设置 grub/grub2,从新安装的内核启动。

    • 建议启用 grub 启动菜单并加入一定的等待时间(修改 /etc/grub2.cfgtimeout 变量)。这样,若新内核有问题启动失败,我们就仍有机会切换回旧内核启动,而不会陷入“重启→失败→重启→……”的循环。
    • CentOS 上可以使用 grubby 来设置默认内核版本,其他发行版可考虑 grub2-set-default 0
  8. 重启

0x03 参考资料


ELF 调试器示例代码
Linux 内核问题调试手段——QEMU 专题