可恶,这次我一定要编译出Linux内核
前言
博客很久没有更新了,其实也写了很多 IOT 研究的文章,但因为保密不能公开。而本文写的是最近几天在搭建 QEMU 下 ARM Linux 运行环境的过程,这方面网上的文章比较多,没有什么保密性可言。
本文记录了交叉编译 Linux 内核、BusyBox 以及制作虚拟存储介质的过程。旨在根据不同设备的内核版本,搭建出一套匹配内核版本的 QEMU 仿真环境。之前研究几款路由器设备都是 2.6x 版本的 ARM 内核。使用 18.04 上安装的交叉编译工具链去编译的程序,在这种 2.6x 老内核中运行会报错 kernel too old
。如果想编译一套相应的研究工具(如 gdbserver
、ptrace
、ltrace
、小型反弹木马等等)会很麻烦,包括一些测试场景,可能会因为内核版本差异较大,产生一些异常。
因为网上有很多相关文章(大部分都是搞嵌入式的老哥写的),本文只是对自己学习这部分做一个记录和总结,帮助自己之后可以根据不同的研究需求去搭建特定环境。之前搞 IOT 仿真的时候,用的都是这个 编译好的配套环境,看完本文后,应该就能自己搞一套 QEMU 环境了😋。
本文记录了下面部分内容。
- 配置交叉编译环境
- 交叉编译 ARM Linux 4.1x 内核
- 交叉编译 busybox
- 构建磁盘文件镜像
- 将 01-03 的产物成功用 QEMU 运行起虚拟机
- 实现 QEMU 虚拟机的网络通信
补充:虽然在摸索的过程中遇到了一些报错并解决。但为了不影响文章的逻辑和连贯性,在正文只顺序记录所有正确的操作步骤,在文末会单独记录踩的坑。
软件版本
工具(虚拟机) | 版本 |
---|---|
qemu-system-arm | 7.2.0 |
Ubuntu | 18.04 |
arm-linux-gnueabihf-gcc | 4.9.4 |
busybox | 1.30.1 |
编译QEMU 2.6x内核环境-失败
其实最开始搭建的是这个 2.6.36.4 ARM 内核环境,最后算失败的。尽管 QEMU 可以运行起来内核,但问题是无法保存文件系统中的任何修改。具体原因应该是磁盘镜像文件没有被内核识别到,为了避免看完文章,发现到头来搭建的环境用不了。我打算先记录搭建成功的 4.1.15 的 ARM 内核环境,而 2.6 上磁盘文件挂载不上的问题,至少在写完这篇文章时,依然没有解决。
补充:
- 最开始实验的时候,内核启动经常崩溃。当时是关掉 shell 或者把进程 kill 掉,这样比较麻烦。可以用
ctrl+a
再按x
退出 QEMU 比较方便。 - 编译 2.6.36.4 内核,交叉编译工具链版本最好使用 4.3.2 版本的 arm-none-linux-gnueabi-gcc。并且 busybox 使用的版本最好为 1.20.0,编译内核时,我选择的开发板是 versatile。
配置交叉编译环境
我的机器上原本有一套交叉编译环境,是 7.5.0 的 arm-linux-gnueabi-gcc。如果内核版本和交叉编译工具链的版本差太多的话,会出问题。因此我重新下载了一套 4.9.4 的 arm-linux-gnueabihf-gcc 交叉编译工具。
交叉编译工具链下载 地址
这个解压后就能直接使用,不需要把路径添加到环境变量里面。后面设置交叉编译工具链时指定具体路径即可,并不会和原本的 arm-linux-gnueabi-gcc 冲突。
交叉编译内核
下载 linux-4.1.15 内核源码。
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.1.15.tar.xz |
解压源码。
tar xvf linux-4.1.15.tar.xz |
生成默认内核配置文件。
make ARCH=arm vexpress_defconfig |
指定编译 ARM 架构内核,并设置交叉编译工具链前缀。然后大概编译五分钟左右就结束了。
make ARCH=arm CROSS_COMPILE=/home/zikh/Desktop/gcc-linaro-4.9-2016.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- all |
正常编译后,产物应该有 zImage 和 vexpress-v2p-ca9.dtb。
交叉编译busybox
下载 1.30.1 版本的 busybox,交叉编译出文件系统的一些必要文件。
wget https://busybox.net/downloads/busybox-1.30.1.tar.bz2 |
解压文件
tar -xjf busybox-1.30.1.tar.bz2 |
通过图形化界面配置 busybox,这些环境变量可以直接用 export
设置在本次 shell 中生效,也可以每次运行命令的时候,作用在本次命令。
make ARCH=arm menuconfig |
使用所有的默认配置即可,不需要做任何修改。不要勾选 Build static binary (no shared libs) 和 Build Shared libbusybox选项。退出时保存默认配置即可。
然后用同样的命令编译 busybox,大概两分钟就结束了。
make ARCH=arm CROSS_COMPILE=/home/zikh/Desktop/gcc-linaro-4.9-2016.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- all |
再执行下面命令进行安装,如果不指定安装目录的话,默认会将 busybox 安装到当前的 _install 目录。
make ARCH=arm CROSS_COMPILE=/home/zikh/Desktop/gcc-linaro-4.9-2016.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- install |
创建文件系统
现在的文件系统 _install 并不完整,缺少了很多目录。下面补充一些缺少的必要目录和文件。
注意:下面要将交叉编译工具链里面的 lib 目录拷贝到当前的 _install 目录,而交叉编译工具链下面有好几个 lib 目录。如果不能确定的话,就用命令 find xxx -name "ld-linux-armhf.so.3"
搜索,有这个 ld 的文件就是要拷贝的 lib 目录。
mkdir lib |
创建 etc/inittab 文件,写入下面内容。
# 系统启动时 |
创建 etc/init.d/rcS 文件,写入下面内容。并且给 etc/init.d/rcS 文件 777 的权限
#!/bin/sh |
创建 etc/fstab 文件,写入下面内容。
# <file system> <mount point> <type> <options> <dump> <pass> |
创建 etc/profile 文件,写入下面内容。
export HOSTNAME=zy |
创建 dev 目录
mkdir dev |
创建终端文件
sudo mknod dev/console c 5 1 |
创建其他目录
mkdir mnt proc tmp sys root |
现在的 _install 目录已经像是一个正常的文件系统了。
构建磁盘文件镜像
用 dd
创建一个 512M 的空白镜像,再用 mkfs.ext3
命令在 rootfs.ext3.img 文件中生成一个 ext3 格式的文件系统。通过挂载的方式,访问 rootfs.ext3.img 文件,将之前的 _install 目录下文件拷贝至挂载点 mnt_tmpfs,最后取消挂载。至此一个装有文件系统的磁盘镜像制作完毕。
mkdir mnt_tmpfs |
启动 QEMU
将之前准备的产物拷贝到一个新的目录。
cp /home/zikh/Desktop/busybox-1.30.1/rootfs.ext3.img ./ |
QEMU 启动脚本
qemu-system-arm -M vexpress-a9 \ |
网络脚本
#!/bin/bash |
执行完网络脚本后,执行 QEMU 启动脚本。
目前 QEMU 可以正常启动,但里面的机器还没有配置 IP。
给 eth0 网卡分一个 IP(取决于宿主机中的 virbr0 在哪个网段),再配置一下网关。
ip add add 192.168.122.130/24 dev eth0 |
现在 IP 分配出来了,并且可以 ping
通网关。ping
不通百度说明 DNS 解析还没有配置。
创建一个 /etc/resolv.conf 文件,里面写入 nameserver 8.8.8.8,至此虚拟机网络通信正常。
磁盘也是正常的(2.6x 的内核这里识别不到磁盘设备),如果有时候创建完文件,重启后文件消失了,可能是 QEMU 还没有将数据同步到磁盘上,就退出了系统。可以在退出 QEMU 前执行一下 sync
命令,将所有缓存数据写入磁盘。
报错与解决
由于我没有在每一次遇到问题时,都记录了相关的日志。所以有很多操作的一些小错误,我并没有记录,只记录了几个印象深刻的错误。并且下面的报错记录,并不是按照遇到顺序记录的。只希望可以帮助遇到同样错误的朋友做一个参考。
报错1
新增了网络配置时, QEMU 启动脚本是这样。
qemu-system-arm -M vexpress-a9 \ |
有如下报错。因为vexpress-a9
并不支持 PCI 总线。将替换 virtio-net-pci
为 virtio-net-device
,可以避免使用 PCI 总线并解决这个错误。
qemu-system-arm: -device virtio-net-pci,netdev=net0: No 'PCI' bus found for device 'virtio-net-pci' |
修改后脚本如下
qemu-system-arm -M vexpress-a9 \ |
报错2
编译 busybox 时报错日志如下
Output of: |
因为在执行 make ARCH=arm menuconfig
配置 busybox 时开启了 Build Shared libbusybox 选项,关闭即可。
报错3
在 QEMU 启动内核时,有如下报错。
Starting init: /sbin/init exists but couldn't execute it (error -8) |
这里是因为当时 busybox 没有设置好架构,编译成了 x64 从而导致架构不匹配报错 error -8
。指定好环境变量 ARCH 为 arm 即可。
报错4
依旧是 QEMU 启动内核时报错,这个意思是指文件系统的 /sbin/init 文件不存在或无法运行。
Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance. |
我将磁盘文件挂载后查看确实存在 /sbin/init 文件,但是发现其依赖 ld-linux-armhf.so.3 文件。我查看了 /lib 目录,发现并没有该文件。因此认为是动态库出了问题,使 /sbin/init 执行失败。
后面检查发现,错把 gcc-linaro-4.9-2016.02-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib目录当成了文件系统的 /lib目录。实际要拷贝的目录应该是 gcc-linaro-4.9-2016.02-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib 目录。