為了讓後面能透過 GDB 進行 debug,還需要進行以下步驟:
建立完 rootfs 後就可以準備執行 UML 了。首先參考建構 User-Mode Linux 的實驗環境 - 客製化-UML-環境中的步驟,寫一個 Shell Script 負責啟動 UML:
TODO: 各個參數意義
以 ./rootfs/init.sh
作為 initial process,其內容如下:
主要是為了省去每次手動掛載 /proc
與 /sys
兩個目錄的步驟,並在 Command Line 的 #
字元前加上 UML:
字樣分辨 UML 以及 HOST。
TODO: /proc
與 /sys
用途
最後嘗試透過剛剛寫的 Script 啟動 UML:
可以看到 UML 順利啟動了,但是卻出現了找不到 mount
命令的錯誤訊息,試圖透過 ls
列出 /bin
下的執行檔,但 ls
也同樣找不到,因此執行 exit
回到 host 檢查 ./rootfs/bin
目錄下的檔案:
結果發現只有系統應包含的基本命令的執行檔都不在該目錄下,但其中包含了一個 busybox 執行檔,而根據官方文件的說明,它是一個整合了多種 UNIX 常用的工具的單一執行檔,可以透過像是 busybox command
的形式執行對應的命令:
但如果每次都要透過 busybox 執行命令很麻煩,因此可以透過 /bin/busybox --install
這個命令,將其中包含的命令建立在 /bin
目錄下,讓使用者不用透過 busybox
執行命令:
TODO: 官方文件對這個參數的說明
確定基本命令都能使用後,接著就要回到 host 將核心模組所需的檔案安裝到 rootfs 中。
接著依照建構 User-Mode Linux 的實驗環境 - 準備核心模組中的命令進行 make modules
以及 make modules_install
,但 make modules_install
的 MODLIB
要改成 INSTALL_MOD_PATH
,且指定目錄只須為 pwd/rootfs
:
TODO: 為什麼要這些核心模組
若依照建構 User-Mode Linux 的實驗環境 - 準備核心模組使用 MODLIB
的話,會發生以下錯誤:
從錯誤訊息的部份可以看出是 make modules_install
的目標目錄找不到,而該目錄位於 host 的 /bin/modules
目錄下,顯然與預期的目標目錄 ./rootfs/lib/modules/VER
不合。
而檢查 make help | grep modules_install
的說明後會發現,核心模組的安裝位置是由 INSTALL_MOD_PATH
指定:
而參考官方文件的 Building External Modules - Module Installation 中的範例也是使用 INSTALL_MOD_PATH
作為參數:
然後就會根據核心的版本號建立相應的目錄結構,不用額外修改目錄名稱。
depmod? modprobe? Not tainted?
https://www.kernel.org/doc/Documentation/admin-guide/tainted-kernels.rst
參考建構 User-Mode Linux 的實驗環境 - 準備核心模組 中 Makefile 的部份,對 simplefs 的 Makefile 進行修改,但為了保持彈性,這邊選擇根據編譯時是否指定架構決定是否要加 ARCHARG
,而不是直接指定架構 ARCH=um
:
然後再開始編譯 simplefs,但 KDIR
與 PWD
要分別設定成核心目錄以及 simplefs 目錄:
最後再將 simplefs
整個目錄複製到 rootfs 中,並在 UML 中載入核心模組:
首先到 ./rootfs/simplefs
中準備一個 simplefs 的檔案系統:
接著依照建構 User-Mode Linux 的實驗環境 - 搭配 GDB 進行核心追蹤和分析中的說明,建制 GDB Script,並準備一個 Shell Script 自動執行 debug 所需的命令:
然後透過這個 Script 執行 UML,並掛載模組以及剛建立的檔案系統:
[Detaching after fork from child process 16651]
訊息的意義?
在 host 有出現 ls -a
沒有顯示 .
以及 ..
的問題:
但是在 UML 中卻沒有出現這個問題。
首先進入到 UML 掛載模組以及 simplefs:
若先在 GDB 中執行 lx-symbols
命令再回到 UML 中 insmod
會不斷跳出的訊息?
接著在透過 pkill -SIGUSR1 -o vmlinux
回到 GDB,透過 lx-symbols
檢查核心模組的 Debug Symbols 有載入了,因此嘗試對 simplefs_mkdir
函式設定中斷點:
然後再透過 countinue
回到 UML 的掛載目錄( continue
後要多按一次 ENTER 才會回到 UML 的 Command Line),並透過 mkdir
命令測試中斷點是否能夠正常運作:
可以發現中斷點如預期的運作,檢查 dentry
參數內容也能看到目錄名稱確實是剛剛輸入的 "testdir"
。
結果應為非 0 的數字,代表 CPU 支援虛擬化,可以從 BIOS 中開啟虛擬化並繼續以下步驟。
參考 Ubuntu 官方文件中的 KVM 安裝教學進行安裝。
安裝完成後透過以下幾個命令檢查是否安裝成功,預期輸出應如下:
首先因為 git clone
整個 kernel source 可能會花很長時間,因此改從 GitHub 上取得只有原始碼的壓縮檔,這邊使用的是最新釋出的 5.18 的版本:
接著先透過 make defconfig ARCH=x86_64
產生預設的設定檔 .config
,但由於之後要配合 GDB 使用,因此參考 Kernel 文件 Debugging kernel and modules via gdb 以及 Bug hunting 中的說明調整某些設定:
CONFIG_DEBUG_INFO
CONFIG_DEBUG_KERNEL
CONFIG_GDB_SCRIPTS
CONFIG_COMPILE_TEST
CONFIG_DEBUG_INFO_REDUCED
CONFIG_DEBUG_INFO_NONE
各項設定可以參考 lib/Kconfig.debug 或 init/Kconfig 等其他 Kconfig 相關檔案中的說明。
要啟動這些設定可以透過 make menuconfig
進行設定:
完成這幾個步驟後應會啟動 CONFIG_DEBUG_INFO
、CONFIG_GDB_SCRIPTS
並關閉 CONFIG_DEBUG_INFO_REDUCED
、CONFIG_DEBUG_INFO_NONE
,可以再從 .config
中檢查對應設定是否正確的啟動以及關閉。
在 menuconfig 中可以按下 /
來搜尋特定設定的路徑、dependencies。
接著再透過 $ make -j`nproc`
開始編譯核心,最後應會得到兩個重要的檔案:arch/x86_64/boot/bzImage
與 vmlinux
,前者是 Linux Kernel 的執行檔,能透過 QEMU 執行,而後者則包含 Debug 用的資訊,能夠配合 GDB 與前者進行互動。
基本上與前面 UML 使用的 rootfs 相同,這邊透過 QEMU 的 -hda
參數讓 QEMU 能把 host 的映像檔當作 rootfs 來使用,因此要先建立並掛載一個映像檔:
需要注意的是,由於 simplefs 透過 make test.img
建立的映像檔預設大小有 200 MiB,所以這邊建立的大小為 512 MiB,讓需要的檔案都能放進這個映像檔,檔案系統類型則採用與 host 相同的 ext4。
然而由於掛載是用 root 權限,所以 rootfs
目錄只有 root 能修改:
因此為了避免透過 sudo
操作,這邊透過 chown
讓當前使用者能存取該目錄:
接著就直接使用前面寫的 Shell Script 把 rootfs 需要的檔案都放進映像檔的掛載目錄中:
接著是編譯 kernel module 並加入到映像檔中:
simplefs 則先在 kernel 目錄處理完再移動到 rootfs 中:
最後 init script 的內容也與 UML 的內容相同:
這邊直接沿用 UML 的 init sciprt,所以之 command line 前面顯示的名稱也會是 UML。
為了避免 QEMU 使用 rootfs.img
映像檔時修改到這個目錄的內容、造成檔案損毀,這邊直接卸載這個映像檔:
TODO: 系統啟動與掛載流程、ramfs, rootfs, initfs
最後是透過 QEMU 執行核心並透過 GDB 進行 Debug,為此需要準備兩個 Terminal 分別執行 QEMU 與 GDB。
可以利用終端機分割畫面功能或是 tmux 之類的工具分割出多個畫面,以減少切換終端機的操作。
為了讓 QEMU 能執行 Kernel,這邊須幾個參數:
-kernel arch/x86/boot/bzImage
-kernel bzImage
Use bzImage as kernel image. The kernel can be either a Linux kernel or in multiboot format.
-m 512
-m [size=]megs[,slots=n,maxmem=size]
Sets guest startup RAM size to megs megabytes. Default is 128 MiB. Optionally, a suffix of "M" or "G" can be used to signify a value in megabytes or gigabytes respectively. Optional pair slots, maxmem could be used to set amount of hotpluggable memory slots and maximum amount of memory. Note that maxmem must be aligned to the page size.
-hda ./rootfs.img
-hda file
Use file as hard disk 0, 1, 2 or 3 image (see the disk images chapter in the System Emulation Users Guide).
指定的檔案則是前面建立的 rootfs 的映像檔 rootfs.img
。
-s
:讓 GDB 能透過 port 1234 與 Kernel 互動
Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234 (see the GDB usage chapter in the System Emulation Users Guide).
-S
:讓 qemu 命令執行後不會馬上開始執行 Kernel
Do not start CPU at startup (you must type 'c' in the monitor).
-nographic
-nographic
… With this option, you can totally disable graphical output so that QEMU is a simple command line application. The emulated serial port is redirected on the console and muxed with the monitor (unless redirected elsewhere explicitly). Therefore, you can still use QEMU to debug a Linux kernel with a serial console. Use C-a h for help on switching between the console and monitor.
-append "console=ttyS0 nokaslr root=/dev/sda rw init=/init.sh"
-append cmdline
Use cmdline as kernel command line
要傳入的 kernel 參數:
由於前面加了 nographic
參數,所以這邊加上 console=ttyS0
讓訊息輸出到 ttyS0
,而 ttyS0
代表原本執行命令的 Terminal。
nokaslr
nokaslr
When CONFIG_RANDOMIZE_BASE is set, this disables kernel and module base offset ASLR (Address Space Layout Randomization).
root=/dev/sda rw
前面設定的 -hda rootfs.img
會被掛載到 /dev/sda
,指定它為 rootfs,並讓 qemu 中執行的 kernel 能寫入映像檔,否則將會以唯讀模式啟動。
init=/init.sh
與 UML 相似,指定 init script。
而以上整合成一個 Shell Script qemu.sh
方便執行:
然後就可以透過這個 script 執行了:
由於前面有設定 -S
讓 qemu 啟動時不會直接啟動 kernel,若沒有要透過 GDB 啟動 kernel 的話,則須先按 Ctrl+a
再按 c
切換到 qemu monitor,並輸入 c
啟動 kernel:
等個幾秒後應該就會看到熟悉的界面以及錯誤訊息:
這時再次透過 Ctrl+a
+ c
切回到 kernel 中,並執行一次 # /bin/busybox --install
。
要結束 qemu 的話則需再次跳回 qemu monitor,然後輸入 q
結束 qemu。
若是太快離開 qemu 的話,似乎會無法確實寫入到 rootfs 中,實測大約需要等個 15 秒左右,但並不是每次都要等那麼久。
GDB script 則相對簡單不少,需要做的只有三件事:
以上步驟也建立一個 Shell Script debug-qemu.sh
:
這兩個 Script 皆應在 kernel 的最上層目錄中(/path/to/your/linux-5.18/
)執行。
接著執行這個 sciprt 來測試是否能正常透過 GDB 進行 debug:
這個 script 應在 ../qemu.sh
後執行。
在 GDB 中輸入 c
後,qemu 應該就能正常啟動 kernel 了。啟動完成後在 kernel 中嘗試掛載 simplefs:
接著到 GDB 的終端機按下 Ctrl+c
跳回到 GDB 畫面測試:
應該會正確讀取 debug symbols,而中斷點也應該能正常運作:
若是沒有支援 Python 的話則無法使用 kernel 提供的 GDB Script,需要重新編譯 GDB (以 14.2 版為例):
編譯完成後,由於這邊將安裝目錄設定為當前目錄以方便移除,而當前目錄應不在系統路徑 $PATH
下,所以這邊將編譯出的執行檔所在的目錄暫時加到系統路徑下,之後透過 which gdb
應該可以看到 gdb 指向剛編譯出的執行檔,而不是系統的 gdb:
出現這個原因可能是因為 vmlinux-gdb.py
未正確載入,可透過 info auto-load python-scripts
檢查,理想狀況應該可以看到:
而以下列出幾種可能的情況:
vmlinux-gdb.py
這個檔案位在 /path/to/kernel/scripts/gdb/
目錄下,可嘗試將其 link 到 kernel 的根目錄:
出現這個錯誤代表 GDB 拒絕讀取這個檔案,這時依照指示把這個檔案的路徑加到 $HOME/.config/gdb/gdbinit
檔案中:
在部份較舊的 kernel (如 5.15) 提供的 GDB Script 中,可能會發現 scripts/gdb/linux/constants.py
檔案中使用了 1UL
這種 C 語言的語法,這種情況下可以用 gdb.parse_and_eval()
包起來:
General
Filesystem (initramfs, rootfs, tmpfs)
KVM/QEMU
UML