天天看點

如何使用 qemu + gdb 分析 Linux 核心代碼

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 根檔案系統

步驟如下:

  1. 到網站下載下傳 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

步驟如下:

  1. 到網站下載下傳 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 解包

繼續閱讀