Release log:
2021-04-18 日: 完成初版
2021-05-30 日: 添加制作 busybox 根檔案系統的方法,解決 qemu 無法 nographic 運作的問題
原文位址
參考
《庖丁解牛Linux核心分析》
環境說明
作業系統: Ubuntu 20.04.2 LTS(lsb_release -a)
核心版本: 5.4.0-58-generic(uname -a)
qemu 版本: QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.15)
linux 源碼: linux-3.18.6
qemu 簡介以及安裝
Qemu 是純軟體實作的虛拟化模拟器,幾乎可以模拟任何硬體裝置,我們最熟悉的就是能夠模拟一台能夠獨立運作作業系統的虛拟機,虛拟機認為自己和硬體打交道,但其實是和 Qemu 模拟出來的硬體打交道,Qemu 将這些指令轉譯給真正的硬體
至于怎麼安裝 qemu,因為之前沒有記錄步驟,這裡就不描述了,網上資料很多
準備環境
在運作 qemu 之前,我們需要準備好核心鏡像檔案以及一個簡易的記憶體根檔案系統
編譯核心
首先我們需要下載下傳 linux 核心源碼,我使用的是 linux-3.18.6.tar.gz。如果你們需要其他版本的源碼,可以 點選這裡
下載下傳完成之後,解壓檔案,然後編譯核心鏡像,得到一個帶有調試資訊的 bzImage 檔案,指令如下。(我的是 64 位系統,但是想編譯成 32 位的鏡像)
$ tar -xvzf linux-3.18.6.tar.gz
$ cd linux-3.18.6/
$ make i386_defconfig
$ make menuconfig
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
$ make CC=gcc-4.7
關于 make menuconfig,相信大家應該是知道的,網上也有不少資料。如果執行 make menuconfig 提示錯誤,網上也有不少資料
我本地的 gcc 預設版本是 gcc-9.3,如果使用此 gcc 編譯 linux-3.18.6 則會報錯,是以需要指定使用 gcc-4.7 來編譯
制作簡化的檔案系統
核心啟動之後,會加載根檔案系統中名為 init 的可執行檔案。搭建完善的根檔案系統會比較麻煩,此處我們制作一個簡化的記憶體根檔案系統。步驟如下
1、建立一個名為 rootfs 的目錄: mkdir rootfs
2、建立一個 init.c 的檔案以及 Makefile 檔案,檔案内容如下
3、執行 make 指令,生成 rootfs.img 檔案
// rootfs/init.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char *argv[])
{
char inStr[256] = {0};
printf("init running...\n");
while (1)
{
printf("now, you can input some words: ");
fgets(inStr, sizeof(inStr), stdin);
printf("%s\n", inStr);
}
return 0;
}
// rootfs/Makefile
all:
gcc-9 myinit.c -o init -static -m32
find init | cpio -o -Hnewc |gzip -9 > ../rootfs.img
制作 busybox 根檔案系統
步驟如下:
- 到網站下載下傳 busybox 源碼(我這裡使用的是 1.33.0): https://busybox.net/downloads/
- 解壓: tar -xvjf busybox-1.33.0.tar.bz2
- 配置 busybox,使用靜态編譯: make menuconfig
Settings ---> [*] Build static binary (no shared libs)
- 編譯 busybox: make CC=“gcc -m32”
- 安裝 busybox: make CC=“gcc -m32” install
- 制作 rootfs:
$ cd _install $ mkdir -p proc sys dev etc/init.d $ sudo mknod dev/ttyAMA0 c 204 64 $ sudo mknod dev/null c 1 3 $ sudo mknod dev/console c 5 1 $ 建立 /etc/inittab 和 /etc/init.d/rcS 檔案 $ find . | cpio -o --format=newc > ../rootfs.img
補充 /etc/inittab 和 /etc/init.d/rcS 檔案的内容
// /etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
// /etc/init.d/rcS
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
/bin/mount -n -t sysfs none /sys
/bin/mount -t ramfs none /dev
/sbin/mdev -s
使用 qemu 運作核心
使用指令加載并運作核心:
qemu-system-i386 -kernel ./linux-3.18.6/arch/i386/boot/bzImage -initrd ./rootfs.img -append "root=/dev/ram rdinit=/sbin/init console=ttyS0" -nographic -smp 2
這裡的 rootfs.img 檔案可以選擇簡化版的或者 busybox 版本的任何一個即可
如果使用 -nographic,則需要帶 -append “console=ttyS0” 參數,否則可能運作不了。如果想退出,需要先按 ctrl+a,然後按 z。如果沒有使用 -nographic 參數,則會以圖形化界面運作,此時可以同時按下 ctrl-alt-q 強制退出
使用 qemu + gdb 分析核心流程
跟蹤調試核心
使用 gdb 跟蹤調試核心,啟動 qemu 時需要加兩個參數,一個是 -s(在 1234 端口上建立一個 gdb-server),另一個是 -S(CPU 初始化之前當機起來)。則啟動 qemu 的指令變成了:
qemu-system-i386 -kernel ./linux-3.18.6/arch/i386/boot/bzImage -initrd ./rootfs.img -append "root=/dev/ram rdinit=/sbin/init console=ttyS0" -nographic -S -s -smp 2
在另一個視窗啟動 gdb,把核心加載經來,建立連接配接
$ gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
我們可以設定斷點為 start_kernel(void),在這個函數之前,核心代碼主要是彙編語言寫的。然後執行 c 開始運作
(gdb) b start_kernel
(gdb) c
使用 gdb 跟進,會發現在 start_kernel->rest_init 中建立核心線程,在這個線程中執行 kernel_init。
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
ramdisk_execute_command 指向 “init”,即我們前面建立的可執行檔案。調用 run_init_process 建立 init 程序
使用 busybox 制作根檔案系統
此部分内容參考 https://www.anquanke.com/post/id/85837
步驟如下:
- 到網站下載下傳 busybox 源碼(我這裡使用的是 1.33.0): https://busybox.net/downloads/
- 解壓: tar -xvjf busybox-1.33.0.tar.bz2
-
配置 busybox,使用靜态編譯: make ARCH=i386 menuconfig
在配置界面:
Settings ---> [*] Build static binary (no shared libs)
- 編譯 busybox: make ARCH=i386
小知識
區分 vmlinux bzImage zImage
vmlinux 是編譯出來的最原始的 ELF 檔案
bzImage 和 zImage 是 vmlinux 經過 gzip 壓縮後的檔案,b 代表 big,bzImage 解壓縮核心到高端記憶體(1M以上),适用于大核心,也可用于小核心;老的 zImage 解壓縮核心到低端記憶體(第一個640K),适用于小核心,不能用于大核心
bzImage 和 zImage 不僅是一個壓縮檔案,而且在這兩個檔案的開頭部分内嵌有 gzip 解壓縮代碼。是以你不能用 gunzip 或 gzip –dc 解包