or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
建構 User-Mode Linux 的實驗環境
直播錄影
User-Mode Linux 概況
User-Mode Linux (以下簡稱
UML
) 顧名思義是將 Linux 核心移植到 user-space,如此一來,就可將這個修改的核心當作一般的 Linux process 來執行,技術分類來說屬於 para-virtualization,在 Kernel-based Virtual Machine (KVM) 問世前 (Intel/AMD 硬體虛擬化加速擴展普及之前,見 x86 virtualization),UML 是唯一 Linux 核心內建的虛擬化機制。UML 有什麼好處呢?至少有以下應用:kernel unit-testing framework(KUnit)也採用 UML:
透過 UML,我們可以建構出一個極佳的測試環境。Android 5.0 以後,使用 UML 來測試核心和網路連線:
rootfs/net_test.sh
及run_net_test.sh
UML 所使用的檔案系統對宿主 Linux 來說也不過只是單純的檔案,一切都好比置身於保護的 sandbox (原文的意思就是「貓沙盒」,給調皮的貓咪一個自得其樂卻不傷害家具的器具,引申為受限的封閉測試機制),經由適當配置,我們大可放心對虛擬機器作任何更動,而不必擔憂損害到真實的硬體與系統。
相當重要的觀念是:UML 本身就是全功能的核心,具備專屬的虛擬環境,對硬體的支援僅仰賴於宿主 Linux 系統。
CONFIG_UML_TIME_TRAVEL_SUPPORT
UML 預計會重用 LKL (Linux Kernel Library) 的成果。
建構 User-Mode Linux 和搭配的檔案系統
編譯 User-Mode Linux 所仰賴的套件,原則上如同編譯 Linux 核心,以 Ubuntu Linux 來說,需要事先安裝以下套件:
取得 Linux 核心原始程式碼,以 v5.12.0 為例:
切換到 Linux 核心原始程式碼,欣賞史上最偉大的開放原始碼專案的面貌:
若沒有特別說明,本文在開發端 (即編輯修改原始程式碼、編譯和準備相關工具等等動作) 的工作目錄皆位於上方解壓縮後的核心原始程式碼目錄。
為避免目錄切換導致的錯誤,可用環境變數保存: (此處
WS
指 "workspace",命名沒有特別意思)設定核心組態,特別注意
ARCH=um
就是指定 UML:如果編譯順利的話,預期會得到名為
linux
的執行檔:參考輸出:
為了說明 UML 真的就是一般的執行檔,我們也能這樣做:
然後就會看到輸出中有
--showconfig
,iomem
,mem
,debug
等等字樣。光有 Linux 核心是不夠的,我們還要準備 root file system (簡稱
rootfs
),本文採用 Alpine Linux,後者支援 x86, x86-64, ARMhf, AArch64 等硬體架構。建立檔案系統時,可能會遇到權限問題,但我們又不想貿然用
sudo
來執行命令 (避免不小心毀壞開發環境),這時可安裝 fakeroot 套件:以下從 Alpine Linux 的套件管理系統 APK (不要跟 Ubuntu/Debian 的 apt 搞混) 建立 rootfs:
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →可先略過。
接著我們就能準備啟動 UML,為方便之後測試,我們先建立以下檔案,名為
UML.sh
:注意,當指定 rootfs 型態為 hostfs 時,可搭配以下兩種參數: (擇一或組合):
hostfs=相對路徑
rootflags=絕對路徑
這裡採用
hostfs=
來指定相對路徑,即稍早建立的rootfs
目錄。隨即啟動 UML:
命令一執行,就會看到類似以下的輸出:
別懷疑,這表示 UML 和第一個使用者層級的程式 (即
/bin/sh
) 已啟動,很快!可在
UML.sh
裡頭./linux
後方加上ubd0=/dev/null
來抑制Failed to initialize ubd device 0 :Couldn't determine size of device's file
這個錯誤訊息。為了區隔命令不是在開發主機 (也稱為 host) 而是在 UML 的環境 (也稱為 guest) 中執行,我們用
UML #
的表示法來標註在 UML 環境中執行的命令。稍早準備的檔案系統,已有 busybox,不過相關的 symbolic link 還未設定,我們需要執行以下: (只要做一次)
由於我們沒有特別去撰寫 init scripts,像是 procfs 沒預先掛載,需要手動執行以下命令:
之後你可在 UML 執行
ps
和uname -a
一類的命令。還記得編譯 UML 時,我們在 Linux 核心程式碼指定
ARCH=um
,這對於 UML 環境的影響是什麼呢?執行下列命令:預期會得到類似的輸出:
在
mode
那行可見是skas
,可對照 UML 的文件 skas mode,後者是 "Separate Kernel Address Space" 的縮寫。在命令提示執行
exit
命令,就會讓 UML 停止運作,因為已經沒有使用者層級的程式:你會看到類似的輸出:
如果發現開發環境的終端機游標消失,可執行
reset
(不要擔心,這是重置終端機,不是整台電腦) 命令。若你嫌每次都要額外執行
$ reset
,也可將下方命令加在UML.sh
的最後一行:這樣下次啟動 UML 並離開後,終端機就會自行復原。
網路設定
以下命令針對開發環境:
修改之前的 UML 啟動參數,在
umid=uml0
後面增加hostname=uml1 eth0=tuntap,tap0
(記得前後有空白)重新啟動 UML 後,執行以下命令:
預期可見以下輸出:
客製化 UML 環境
tini 工具可避免 UML 一類的 guest 環境造成 zombie processes。取得並在 rootfs 做好準備:
建立
rootfs/init.sh
檔案,其內容如下:記得要變更檔案權限,使其可執行:
在
rootfs/init.sh
中,我們在/bin/sh
後方加上+m
參數,目的是抑制/bin/sh: can't access tty; job control turned off
這項錯誤訊息。之前我們準備了
UML.sh
檔案來啟動 UML,因應上述這個 init script,修改UML.sh
來指定init
:接著重新執行
UML.sh
即可在開機過程自動掛載/proc
和/sys
。我們可變更 UML 環境中命令提示訊息,使得裡頭包含
UML
字樣,例如:也可追加終端機色彩,使得
UML
命令提示更顯目:這樣命令提示訊息就變成綠色。更多色彩的組合,可參見:
可在 host 端修改
rootfs/init.sh
,將上述export PS1
加在exec /sbin/tini /bin/sh
的前一行,這樣下次啟動 UML 就會生效。準備核心模組
編譯核心模組
預期將看到若干個以
.ko
結尾的檔案。隨後我們將安裝這些核心模組到 rootfs 所在的目錄,注意最終目錄名稱應為這裡先用固定名稱,稍後切換到 UML 環境時再更名:
啟動 UML 並在環境中執行以下命令:
測試:
預期輸出:
確認核心模組的功能正確運作後,接著我們可撰寫自己的核心模組。在 Linux 核心程式碼最上層建立一個名為
tests
的目錄:建立檔案
tests/hello.c
,其內容為:還要有對應的
Makefile
,內容如下:注意在
default:
和$(MAKE)
之間不是空白字元,而是 Tab,同理,clean:
和$(MAKE)
之間也以 Tab 區隔。編譯上述核心模組並複製到 rootfs 中:
再次啟動 UML,掛載
hello.ko
:預期輸出為:
隨後卸載核心模組:
剛才的核心模組太單純,我們再挑戰稍微複雜的試驗。以下的核心模組嘗試將 Linux 內部的
jiffies
和HZ
透過 sysfs 揭露給使用者層級。先準備開發用目錄:
建立
tests/ticks/ticks.c
檔案,內容如下:也準備對應的
tests/ticks/Makefile
檔案,內容如下:- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →$(MAKE) -C
之前的字元不是空白,而是 tab編譯核心模組:
pushd
和popd
是 GNU Bash 提供的內建命令,可參見 Directory Stack Builtins,允許快速在目錄間切換,若你不使用 GNU Bash,該命令可能會無效,請自行切換目錄並記得回到$WS
指向的目錄,即 Linux 核心原始程式碼所在目錄。進入 UML 環境,並進行測試:
應可看到各自的數值,其中
HZ
設定為100
。搭配 GDB 進行核心追蹤和分析
Linux 核心提供 GDB script,讓分析和除錯更加便利,不過 GDB script 用 Python 撰寫,於是我們首先檢查 gdb 是否開啟 Python 的整合:
若看到輸出
2
也就是 Python 執行1 + 1
的結果,那表示 GDB 內建 Python 模組。建構 GDB script:
注意上方的
ARCH=um
不能省略,否則會使核心組態錯亂。用下行命令來啟動 GDB,指定載入 Linux 核心提供的 GDB script:
預期可見到:
如果沒有見到
Linux version 5.12.0
這個訊息,就意味著 Linux 核心提供的 GDB script 沒有正確產生。GDB script 沒有正確載入的錯誤訊息如下:以下用
(gdb)
表示在 GDB 命令提示列裡頭輸入的命令此時在
(gdb)
命令提示符號旁輸入lx
但不要按下 Enter/Return 按鍵,而是按下 Tab 按鍵,就會發現 GDB 自動補完由 GDB script 提供的自行定義命令,像是lx-dmesg
,lx-symbols
,lx-cpus
等等。詳細描述可透過執行以下命令取得:為了讓後續的操作更便利,我們準備名為
gdbinit
的檔案,內容如下:再執行:
用意是將
FULLPATH
換為目前目錄的完整路徑。為何要設定 handle 呢?
SIGSEGV
作為內部通訊,而 GDB 會自動將此 signal 轉包給除錯器本身;SIGUSR1
是我們要求停止 UML 執行所用的 signal;接著可執行以下命令:
預期會看到 UML 的核心版本號碼和
(gdb)
命令提示。此刻,我們終於可在 GDB 內啟動 UML:這時可見 UML 執行到
UML:/ #
命令提示訊息。不過很快就發現,就算按下 Ctrl-C 組合按鍵,我們仍身處 UML,那該如何喚醒 GDB 呢?再開啟另一個終端機視窗,然後執行:再切換回原本執行 GDB 的終端機視窗,可見到以下訊息:
我們即可在 GDB 執行命令,例如:
(gdb) print $lx_module("hello")
應在UML # insmod /hello.ko
之後執行。注意到最後一個命令的
$
符號,這是 GDB script 所定義的函式。我們可執行以下 GDB 命令:上方函式的參數
1
就表示 PID (Process ID) = 1 即init
程序。我們繼續做實驗:預期得到以下輸出:
第一個欄位是地址,第二個欄位是 PID,不難發現 init 程序的地址是
0x63828040
,於是我們可揭開 init 的內部資訊: (很長的列表,按下 Enter 繼續瀏覽)也可檢驗 Linux 核心內部來得知:
預期輸出就是
0x63828040
。別忘了,Linux 核心用 linked list 來表示程序資訊,來做實驗:預期輸出:
感受到 UML 搭配 GDB 的威力了吧!
延伸閱讀: Debugging kernel and modules via gdb
當然也能在 GDB 設定中斷點,比方說在掛載核心模組的實作程式碼:
參考輸出:
注意到這個中斷點編號為
1
,如果每次有核心模組掛載時,就會觸發中斷並且停留在命令提示列,說來有點惱人,於是我們可善用 GDB 裡頭的command
。先執行以下命令: (1
是數字一)然後會見到這樣的訊息:
在提示符號
>
後面,就是我們自訂的命令動作,繼續輸入以下:在
end
輸入後,我們又見到熟悉的(gdb)
命令提示列。輸入
(gdb) continue
繼續執行,並參照上述掛載hello.ko
:這時就會觸發中斷點,參考輸出如下:
用
(gdb) list
觀察,預期會得到類似下方輸出:TODO: