编译系统内核
首先需要下载Linux的内核源码,几个常用的内核源码下载途径如下:
- GitHub - torvalds/linux: Linux kernel source tree:适合git commit找补丁和漏洞
- Index of /pub/linux/kernel/:下载源码的压缩包
sudo apt-get source linux-image-$(uname -r)
:下载当前发行版的内核,但是版本往往不全- Ubuntu KernelGitGuide:对于Ubuntu可以获取到发行版的源码,自行进行编译
此次就以编译Linux 5.15.1为例,首先下载并解压源码:
1 | wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.1.tar.gz |
然后是对内核的编译选项进行设置,常用的基本流程如下:
1 | make **_deconfig |
make **_deconfig
**_deconfig
是用来指定此次编译使用的默认配置文件,该配置文件会从指定的**_dedonfig
文件中加载配置,并保存到源码目录的.config
文件中。
在嵌入式领域,例如需要为ARM架构指定适配特定芯片的Linux内核,配置命令如下:
1 | make ARCH=ARM imx_v6_v7_defconfig |
在此处,我们的目的是需要编译一个x86_64架构的内核,因此执行命令如下:
1 | make x86_64_defconfig |
make menuconfig
一般来说,调试内核需要关闭系统的地址随机化、开启调试信息等,除此之外,还需要根据调试的目的来选择需要编译的模块,例如在调试netfilter的时候,就需要开启一些NF_TABLES等相关的配置。
如下是关闭地址随机化和开启调试信息的步骤:1
2
3
4
5
6
7
8
9Processor type and features --->
-*- Build a relocatable kernel
[ ] Randomize the address of the kernel image (KASLR)
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Generate BTF typeinfo
[*] Provide GDB scripts for kernel debugging
在menu界面,可以通过输入/
来搜索需要开启的内核特性,这比一个个去手动找方便。
设置完毕后,可以主动保存或者退出提示保存
make
开始进行编译,可以根据CPU的核数开启多线程编译,提高速度,例如make -j8
。
编译完成后,和调试相关的两个重要文件的路径和作用如下:
- vmlinux:当前目录,原始的内核文件,未压缩,可以用于gdb调试时加载
- bzImage:
./arch/x86_64/boot/bzImage
,压缩后的内核,真正运行的内核
制作文件系统
要启动一个能正常运行的Linux,除了内核之外,还需要一个根文件系统。文件系统的制作可以采用手动的方式,也可以采用自动化的脚本部署。
手动制作一个文件系统,基本的步骤就是:
- 下载某些发行版的基本根文件系统,例如Ubuntu的根文件系统
- 制作磁盘镜像,可以先使用
dd
创建一个磁盘镜像,然后使用mkfs
来为磁盘镜像进行分区和格式化 - 挂载磁盘镜像,并将根文件系统装载到磁盘镜像中。
手动创建根文件系统磁盘镜像就不再更多展开说,后续会单独出一篇文章来进行介绍。
自动化的脚本部署则是推荐一个内核模糊测试工具syzkaller
中的脚本create-image.sh
,其工作的基本原理是通过工具debootstrap
根据参数创建一个Debian的根文件系统磁盘镜像,大概的工作流程如下:
- 设置参数,例如Debian的发行版本、系统架构、需要安装的组件
- 下载Debian的基本根文件系统,并使用qemu-user-static和chroot到根文件系统目录
- apt-get安装相关的组件,然后打包到磁盘镜像中
1 | sudo apt-get install debootstrap |
例如,./create-image.sh -d bullseye
制作一个Debian的bullseye发行版根文件系统镜像。
qemu+gdb连调
运行所需要的内核和文件系统镜像都制作好了,基本如下:
就可以使用如下的QEMU命令来运行并调试内核:
1 | sudo qemu-system-x86_64 \ |
- m:指定运行内存
- kernel:指定内核
- append:指定console口、磁盘、串口等
- drive:磁盘
- nographic:非图形界面,也就是命令行界面
- net:e1000网卡
- S:等待gdb的命令后再运行
- s:开启本地调试端口1234
随后,使用gdb去连接和调试:
1 | file ./vmlinux |
可以看到,gdb成功断在了函数start_kernel
处