核心流程
首先,本文主要讲述如何编译Linux
内核并在qemu
虚拟机上运行。这里针对的架构是aarch64
。
本文的实验平台是Ubuntu 16.04
。
为了达成目标,我们需要有qemu
、buildroot
和linux
安装包或源码。
首先确保qemu-system-aarch64
命令可用,可以通过在命令行执行qemu-system-aarch64 --version
判断。
下载buildroot
源码,链接见下文平台工具。假设其绝对路径保存在变量BUILD_ROOT_PATH
中。执行以下命令
cd $BUILD_ROOT_PATH
make menuconfig
在弹出的配置界面中,设置Target option ---> Target Architecture
为AArch64 (little endian)
;设置Toolchain ---> Toolchain type
为External toolchain
,这时我们可以看到Toolchain ---> Toolchain
的值为linaro AArch64 xxxx.xx
;设置System configuration ---> Enable root login with password
开启,并设置System configuration ---> Root password
为xxxx
(任意的你喜欢的密码);设置System configuration ---> Run a getty (login prompt) after boot ---> TTY port
的值为ttyAMA0
(这一条非常重要,不然虚拟机可能启动不了);设置Target packages ---> Show packages that are also provided by busybox
开启;设置Target packages ---> Debugging, profiling and benchmark ---> strace
开启;设置Filesystem images ---> cpio the root filesystem
开启。
在配置完成之后,执行
make
注意:这里可能需要配置wget
代理。
生成的rootfs.cpio
在目录$BUILD_ROOT_PATH/output/images
下面。
下载Linux
内核源码,链接见下文平台工具。假设其绝对路径保存在变量LINUX_KERNEL_PATH
中。执行以下命令
cd $LINUX_KERNEL_PATH
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make defconfig
vim .config
在打开的文件中,找到CONFIG_CMDLINE
这个配置选项,设置其值为console=ttyAMA0
;找到CONFIG_INITRAMFS_SOURCE
这个配置选项,设置其值为$BUILD_ROOT_PATH/output/images/rootfs.cpio
(注意,这里要自己展开变量BUILD_ROOT_PATH
);设置CONFIG_DEBUG_INFO
配置项为y
。
配置结束后,执行以下命令
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j16
执行结束后,我们可以看到生成了一些文件,其中包括$LINUX_KERNEL_PATH/vmlinux
和$LINUX_KERNEL_PATH/arch/arm64/boot/Image
在当前目录创建一个shell
脚本,避免重复输入指令。其文件名为start.sh
,其内容为
qemu-system-aarch64 -machine virt -cpu cortex-a57 \
-machine type=virt -nographic -smp 1 \
-m 2048 \
-kernel ./arch/arm64/boot/Image \
--apend "console=ttyAMA0" \
$1 $2
执行./startup.sh
,这是可以用qemu
启动linux
内核。在进入命令行之前,需要输入buildroot login:
的值,其值为root
,然后需要输入Password:
,这是前文构建rootfs.cpio
的时候,配置项System configuration ---> Root password
的值。然后就可以进入命令行执行以下常用命令了。(注意,需要先cd /
)
如果要退出qemu
,可以先按Ctrl + A
,然后按X
。
为了在主机和qemu
虚拟机之间共享文件,我们可以创建一个目录,其绝对路径为SHARED_FILE_PATH
。然后执行以下命令
cd $BUILD_ROOT_PATH
vim .config
修改BR2_ROOTFS_OVERLAY
配置项的值为$SHARED_FILE_PATH
(注意,自行展开变量)。
保存后执行以下命令重新创建 rootfs.cpio
。
rm $BUILD_ROOT_PATH/output/images/rootfs.*
make
然后需要重新编译内核,即
cd $LINUX_KERNEL_PATH
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j16
依然通过./start.sh
启动虚拟机跑linux
内核,进入命令行后,执行cd /; ls
,我们可以看到$SHARED_FILE_PATH
目录下的文件。
重新执行./start.sh -s -S
进入qemu
的调试状态,然后开一个新的shell
,输入命令
cd $LINUX_KERNEL_PATH
aarch64-linux-gnu-gdb ./vmlinux -ex "target remote :1234"
现在,可以像以往一样使用gdb
进行调试了……
下面介绍如何编译一个linux
内核模块。
cd $SHARED_FILE_PATH
vim hello.c
hello.c
的内容为
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
int helloinit(void)
{
printk(KERN_DEBUG "hello world !!\n");
return 0;
}
void helloexit(void)
{
printk(KERN_DEBUG "goodbye world !!\n");
}
module_init(helloinit);
module_exit(helloexit);
MODULE_LICENSE("GPL");
然后在当前目录创建Makefile
内容如下
ifneq (${KERNELRELEASE},)
obj-m := hello.o
else
KERNEL_SOURCE := $LINUX_KERNEL_PATH # 注意自行展开变量LINUX_KERNEL_PATH
PWD := $(shell pwd)
default:
${MAKE} -C ${KERNEL_SOURCE} M=${PWD} modules
clean:
${MAKE} -C ${KERNEL_SOURCE} M=${PWD} clean
endif
然后执行交叉编译命令
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make
OK,重新编译rootfs.cpio
和linux
内核,然后运行qemu
。在进入linux
命令行后,执行以下命令
cd /
insmod hello.ko
dmesg | tail # 可以看到打印信息 hello world !!
rmmod hello.ko
dmesg | tail # 可以看到打印信息 goodbye world !!
相关经验
Linux Kernel
- 使用
qemu
并开启gdb server
功能之后,在gdb
窗口输入b start_kernel
,进入最初的内核初始化函数。
平台工具
参考
- Starting Linux kernel exploration
- Debugging an ARM64 linux kernel using QEMU
- Writing kernel modules
- Debugging Kernel and Modules using GDB