开发环境:Ubuntu 20.04.5 LTS
,推荐修改阿里云的apt源,遇到编译依赖方便安装。
环境准备:在Windows上基于WSL2搭建Linux开发环境
本文用到的软件选用的是截至当前(2022-11-19)官网发布的最新的release版本,详细如下:
软件 | 版本 | 官网 | 发布日期 | 说明 |
---|---|---|---|---|
Linux | 6.0.9 | https://kernel.org | 2022-11-16 | Linux内核 |
BusyBox | 1.35.0 | https://busybox.net | 2021-11-26 | 集成工具集,方便制作根文件系统 |
QEMU | 7.2.0-rc1 | https://www.qemu.org | 2022-11-15 | 仿真ARM64开发板 |
新建一个工作目录,编译准备所有所需软件,最终所需文件如下:
├── busybox-1.35.0
├── busybox-1.35.0.tar.bz2
├── linux-6.0.9
├── linux-6.0.9.tar.xz
├── make_initrd.sh
├── qemu-7.2.0-rc1
├── qemu-7.2.0-rc1.tar.xz
└── qemu.sh说明1:make_initrd.sh 是为了自动化制作根文件系统
说明2:qemu.sh 是使用QEMU拉起Linux的shell脚本
本文后面将详细介绍如何编译和使用每一个软件,以及上面提到的两个脚本的内容和功能。
官网下载当前最新的release版本:
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.0.9.tar.xz
tar xvf linux-5.19.11.tar.xz
使用menuconfig勾选RAM disks支持,并调整大小为: 65536 kb:
make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build menuconfig -j32
Device Drivers > Block devices
<*> RAM block device support
(16) Default number of RAM disks (NEW)
(65536) Default RAM disk size (kbytes)
说明:
O=build
表示编译的文件输出到build目录,不跟源码混在一起;-j32
表示开启32线程编译,注意线程太多可能会偶现时序导致的编译错误;交叉编译ARM64 Linux内核,编译完毕后,对应目录会有生成的内核镜像:
make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build -j32
file build/arch/arm64/boot/Image
使用AMD 5800电脑,大概5min就可以编译完毕。
官网下载最新的release版本:
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar xvf busybox-1.35.0.tar.bz2
使用menuconfig修改为静态链接:
make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 menuconfig -j32
Settings
[*] Build static binary (no shared libs)
编译BusyBox可执行文件,并输出到_install目录:
make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 install -j32
file _install/bin/busybox
制作initrd步骤略多。这里make_initrd.sh
就是用来自动制作initrd的脚本,内容:
#!/bin/bash
MOUNT_DIR=mnt
CURR_DIR=`pwd`
rm initrd.ext4
dd if=/dev/zero of=initrd.ext4 bs=1M count=32
mkfs.ext4 initrd.ext4
mkdir -p $MOUNT_DIR
mount initrd.ext4 $MOUNT_DIR
cp -arf busybox-1.35.0/_install/* $MOUNT_DIR
cd $MOUNT_DIR
mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/
echo "proc /proc proc defaults 0 0" > etc/fstab
echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab
echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -a" >> etc/init.d/rcS
echo "mount -o remount,rw /" >> etc/init.d/rcS
echo "echo -e \"Welcome to ARM64 Linux\"" >> etc/init.d/rcS
chmod 755 etc/init.d/rcS
echo "::sysinit:/etc/init.d/rcS" > etc/inittab
echo "::respawn:-/bin/sh" >> etc/inittab
echo "::askfirst:-/bin/sh" >> etc/inittab
chmod 755 etc/inittab
cd dev
mknod console c 5 1
mknod null c 1 3
mknod tty1 c 4 1
cd $CURR_DIR
umount $MOUNT_DIR
echo "make initrd ok!"
介绍一下这个脚本的实现和功能:
dd
和 initrd.ext4
制作一个空的32M的ext4格式的文件系统;mount
这个文件系统,然后拷贝busybox编译的文件进去;这片文章也挺不错:制作嵌入式根文件系统(常见问题详解)
官网下载最新的release版本:
wget https://download.qemu.org/qemu-7.2.0-rc1.tar.xz
tar xvf busybox-1.35.0.tar.bz2
进入qemu源码目录后,编译步骤如下:
cd qemu-7.2.0-rc1
mkdir build
cd build
../configure --target-list=aarch64-softmmu
make -j32
file aarch64-softmmu/qemu-system-aarch64
运行 qemu.sh
即可拉起 ARM64 Linux,使用的ARM机器就是是QEMU仿真的virt machine。
注意:QEMU的virt machine默认CPU是cortex-a15,这是一个32位CPU。选择ARMv8的64位CPU可用cortex-a57。
查看QEMU支持的machine和cpu方法如下:
cd qemu-7.2.0-rc1/build/aarch64-softmmu
./qemu-system-aarch64 -M help # 查看支持的machine
./qemu-system-aarch64 -cpu help # 查看支持的CPU
启动脚本 qemu.sh 内容如下:
#!/bin/bash
qemu/build/aarch64-softmmu/qemu-system-aarch64 \
-nographic \
-M virt \
-cpu cortex-a57 \
-smp 2 \
-m 4G \
-kernel linux-6.0.9/build/arch/arm64/boot/Image \
-append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
-initrd initrd.ext4
说明:上面append选项用来给内核传递命令行参数,nokaslr
表示关闭地址随机化,方便gdb调试内核。
QEMU是一个仿真器,有内置的GDB server,给内核提供了强大的调试功能。基本用法如下:
修改 qemu.sh 脚本,加入 -s -S
参数,修改后如下:
#!/bin/bash
qemu/build/aarch64-softmmu/qemu-system-aarch64 \
-nographic \
-M virt \
-cpu cortex-a57 \
-smp 2 \
-m 4G \
-kernel linux-6.0.9/build/arch/arm64/boot/Image \
-append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
-initrd initrd.ext4 \
-s -S
这是执行 qemu.sh 脚本会停住,新开一个终端,使用如下命令调试
cd linux-6.0.9/build
gdb-multiarch vmlinux
# 进入gdb交互界面后,执行
target remote :1234
b start_kernel
c
# 即可在内核入口函数断注
内核大部分时间都在cpu idle,一般在gdb交互界面随机按 ctrl+c
就是停在这个状态,调试大概如下截图:
后面就可以非常方便的调试内核了:
需要注意的是:内核只能用O2编译,很多变量都被优化了,不很方便。
可用下面方法来局部O0编译:
#pragma GCC optimize ("O0")
写在文件开头,O0编译本文件__attribute__((optimize("O0")))
写在函数开头,O0编译本函数具体可以参考:使用 O0 编译 调试 Linux内核的某些部分 和 O0的内核
这样如果遇到需要单步执行,或者看一些变量信息,会更加的方便。