---
title: 建構 User-Mode Linux 的實驗環境
image: https://i.imgur.com/dmklPWz.jpg
description: 自 Linux 核心原始程式碼編譯出 User-mode Linux 並運用工具來建構實驗所需的檔案系統
tags: LINUX KERNEL, LKI
---
# 建構 User-Mode Linux 的實驗環境
> 主講人: [jserv](https://wiki.csie.ncku.edu.tw/User/jserv)
==[直播錄影](https://www.youtube.com/watch?v=ivqO-yCgk2o)==
## User-Mode Linux 概況
![](https://i.imgur.com/mEu1c2N.png)
[User-Mode Linux](https://en.wikipedia.org/wiki/User-mode_Linux) (以下簡稱 `UML`) 顧名思義是將 Linux 核心移植到 user-space,如此一來,就可將這個修改的核心當作一般的 Linux process 來執行,技術分類來說屬於 [para-virtualization](https://en.wikipedia.org/wiki/Paravirtualization),在 [Kernel-based Virtual Machine (KVM)](https://www.linux-kvm.org/) 問世前 (Intel/AMD 硬體虛擬化加速擴展普及之前,見 [x86 virtualization](https://en.wikipedia.org/wiki/X86_virtualization)),UML 是唯一 Linux 核心內建的虛擬化機制。UML 有什麼好處呢?至少有以下應用:
* 對與硬體架構無關的一般性 Linux 程式作偵錯與快速測試
* 檢驗 (客製化) 檔案系統的完整性與正確性,特別是 [init scripts](https://www.linuxfromscratch.org/lfs/view/stable/chapter09/usage.html) 相關的部份
* 在單機建構虛擬網路環境,以多個網路單元進行模擬操作
* 搭配 gdb 來追蹤 Linux 核心主體流程,快速測試新的演算法或引入改進
* 易於部署的 Linux 教學環境
kernel unit-testing framework([KUnit](https://www.kernel.org/doc/html/latest/dev-tools/kunit/))也採用 UML:
> KUnit addresses the problem of being able to run tests **without needing a virtual machine or actual hardware** with User Mode Linux. User Mode Linux is a Linux architecture, like ARM or x86; however, unlike other architectures it compiles to a standalone program that can be run like any other program directly inside of a host operating system; to be clear, it does not require any virtualization support; it is just a regular program.
透過 UML,我們可以建構出一個極佳的測試環境。Android 5.0 以後,使用 UML 來測試核心和網路連線:
* [Kernel Networking Unit Tests](https://source.android.com/devices/architecture/kernel/network_tests)
* [kernel/tests - net/test](https://android.googlesource.com/kernel/tests/+/master/net/test) 原始程式碼中,UML 相關的檔案是 `rootfs/net_test.sh` 及 `run_net_test.sh`
UML 所使用的檔案系統對宿主 Linux 來說也不過只是單純的檔案,一切都好比置身於保護的 sandbox (原文的意思就是「貓沙盒」,給調皮的貓咪一個自得其樂卻不傷害家具的器具,引申為受限的封閉測試機制),經由適當配置,我們大可放心對虛擬機器作任何更動,而不必擔憂損害到真實的硬體與系統。
相當重要的觀念是:UML 本身就是全功能的核心,具備專屬的虛擬環境,對硬體的支援僅仰賴於宿主 Linux 系統,
* [Virtual Labs with User Mode Linux](https://harvie.cz/papers/unsorted/Virtual-UML-Lab-Presentation.pdf) (2004 年)
![](https://i.imgur.com/HNx75jK.png)
> UML = Linux-Kernel running as regular Linux user-process without root privileges
* [UML Status Report](http://events17.linuxfoundation.org/sites/events/files/slides/slides_5.pdf) (2017 年)
* [Advanced testing with UserModeLinux](https://static.sched.com/hosted_files/osseu19/ca/slides.pdf) (2019 年)
- [ ] Early printk directly prints to the console
* Not much kernel machinery needed (hence: early)
* On UML earlyprintk is just fprintf()
* fprintf() on the host side
- [ ] UML + [PCAP](http://user-mode-linux.sourceforge.net/old/UserModeLinux-HOWTO-6.html)
> [pcap](https://www.tcpdump.org/pcap.html) - not much use for actual network connectivity, but great for monitoring traffic on the host
* Userspace within UML wants to see a real ethernet device
* Have a ethernet driver in UML: Instead of talking to real or emulated hardware just use libpcap
* Call within the driver libpcap functions
- [ ] Advanced example: Timetravel
* Test framework took to long, Some test cases had to run into timeouts, Timeouts are bad for testing
* `CONFIG_UML_TIME_TRAVEL_SUPPORT`
- [ ] Kernel is built as regular program, links to libc
* Has a real main() function
* Every program within UML runs under [ptrace()](http://man7.org/linux/man-pages/man2/ptrace.2.html)
> 1. ptrace 系統呼叫用以實做 gdb 一類可斷點 (breakpoint) 的追蹤除錯,或作系統呼叫的追蹤分析
> 2. ptrace 允許一個 parent process 去監控另一個 process 的執行,並得以檢驗 / 更改執行時期的系統 image (映射於虛擬記憶體) 和暫存器
> 3. 使用情境可透過 fork 系統呼叫去建立 child process (搭配 exec 系統呼叫) 或者直接追蹤某個已執行的 process
* UML makes every syscall a no-op on host side
* Calls the syscall handler with UML kernel
* Page faults via SIGSEGV
* Interrupts (Timer, …) are signals
UML 預計會重用 [LKL (Linux Kernel Library)](https://github.com/lkl/linux) 的成果。
> 延伸閱讀: [LKL: 重用 Linux 核心的成果](https://hackmd.io/@sysprog/linux-lkl)
## 建構 User-Mode Linux 和搭配的檔案系統
編譯 User-Mode Linux 所仰賴的套件,原則上如同編譯 Linux 核心,以 Ubuntu Linux 來說,需要事先安裝以下套件:
```shell
$ sudo apt install build-essential libncurses-dev flex bison
$ sudo apt install xz-utils wget ca-certificates bc
```
取得 [Linux 核心原始程式碼](https://www.kernel.org/),以 v5.12.0 為例:
```shell
$ wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.12.tar.xz
$ tar xvf linux-5.12.tar.xz
```
切換到 Linux 核心原始程式碼,欣賞史上最偉大的開放原始碼專案的面貌:
```shell
$ cd linux-5.12
```
:::info
若沒有特別說明,本文在開發端 (即編輯修改原始程式碼、編譯和準備相關工具等等動作) 的工作目錄皆位於上方解壓縮後的核心原始程式碼目錄。
為避免目錄切換導致的錯誤,可用環境變數保存: (此處 `WS` 指 "workspace",命名沒有特別意思)
```shell
export WS=`pwd`
```
:::
設定核心組態,特別注意 `ARCH=um` 就是指定 UML:
```shell
$ make mrproper
$ make defconfig ARCH=um SUBARCH=x86_64
$ make linux ARCH=um SUBARCH=x86_64 -j `nproc`
```
如果編譯順利的話,預期會得到名為 `linux` 的執行檔:
```shell
$ file linux
```
參考輸出:
> linux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0
為了說明 UML 真的就是一般的執行檔,我們也能這樣做:
```shell
$ ./linux --help
```
然後就會看到輸出中有 `--showconfig`, `iomem`, `mem`, `debug` 等等字樣。
光有 Linux 核心是不夠的,我們還要準備 root file system (簡稱 `rootfs`),本文採用 [Alpine Linux](https://alpinelinux.org/),後者支援 x86, x86-64, ARMhf, AArch64 等硬體架構。
建立檔案系統時,可能會遇到權限問題,但我們又不想貿然用 `sudo` 來執行命令 (避免不小心毀壞開發環境),這時可安裝 fakeroot 套件:
```shell
$ sudo apt install fakeroot
```
以下從 [Alpine Linux](https://alpinelinux.org/) 的套件管理系統 APK (不要跟 Ubuntu/Debian 的 [apt](https://wiki.debian.org/Apt) 搞混) 建立 rootfs:
```shell
$ export REPO=http://dl-cdn.alpinelinux.org/alpine/v3.13/main
$ mkdir -p rootfs
$ curl $REPO/x86_64/APKINDEX.tar.gz | tar -xz -C /tmp/
$ export APK_TOOL=`grep -A1 apk-tools-static /tmp/APKINDEX | cut -c3- | xargs printf "%s-%s.apk"`
$ curl $REPO/x86_64/$APK_TOOL | fakeroot tar -xz -C rootfs
$ fakeroot rootfs/sbin/apk.static \
--repository $REPO --update-cache \
--allow-untrusted \
--root $PWD/rootfs --initdb add alpine-base
$ echo $REPO > rootfs/etc/apk/repositories
$ echo "LABEL=ALPINE_ROOT / auto defaults 1 1" >> rootfs/etc/fstab
```
:::warning
:warning: 上述命令執行過程中可能見到類似下方的錯誤訊息:
```
Operation not permitted
script exited with error 127
```
可先略過。
:::
接著我們就能準備啟動 UML,為方便之後測試,我們先建立以下檔案,名為 `UML.sh`:
```shell
#!/bin/sh
./linux umid=uml0 \
root=/dev/root rootfstype=hostfs hostfs=./rootfs \
rw mem=64M init=/bin/sh quiet
```
注意,當指定 rootfs 型態為 hostfs 時,可搭配以下兩種參數: (擇一或組合):
* `hostfs=相對路徑`
* `rootflags=絕對路徑`
這裡採用 `hostfs=` 來指定相對路徑,即稍早建立的 `rootfs` 目錄。
隨即啟動 UML:
```shll
$ chmod +x UML.sh
$ ./UML.sh
```
命令一執行,就會看到類似以下的輸出:
```
Failed to initialize ubd device 0 :Couldn't determine size of device's file
/bin/sh: can't access tty; job control turned off
/ #
```
別懷疑,這表示 UML 和第一個使用者層級的程式 (即 `/bin/sh`) 已啟動,很快!
可在 `UML.sh` 裡頭 `./linux` 後方加上 `ubd0=/dev/null` 來抑制 `Failed to initialize ubd device 0 :Couldn't determine size of device's file` 這個錯誤訊息。
:::info
為了區隔命令不是在開發主機 (也稱為 **host**) 而是在 UML 的環境 (也稱為 **guest**) 中執行,我們用 ==`UML # `== 的表示法來標註在 UML 環境中執行的命令。
:::
稍早準備的檔案系統,已有 busybox,不過相關的 symbolic link 還未設定,我們需要執行以下: (只要做一次)
```shell
UML # /bin/busybox --install
```
由於我們沒有特別去撰寫 init scripts,像是 procfs 沒預先掛載,需要手動執行以下命令:
```shell
UML # mount -t proc none /proc
```
之後你可在 UML 執行 `ps` 和 `uname -a` 一類的命令。
還記得編譯 UML 時,我們在 Linux 核心程式碼指定 `ARCH=um`,這對於 UML 環境的影響是什麼呢?執行下列命令:
```shell
UML # cat /proc/cpuinfo
```
預期會得到類似的輸出:
```
processor : 0
vendor_id : User Mode Linux
model name : UML
mode : skas
host : Linux node1 4.15.0-72-generic #81-Ubuntu SMP Tue Nov 26 12:20:02 UTC 2019 x86_64
bogomips : 7722.59
```
在 `mode` 那行可見是 `skas`,可對照 UML 的文件 [skas mode](http://user-mode-linux.sourceforge.net/old/skas.html),後者是 "Separate Kernel Address Space" 的縮寫。
在命令提示執行 `exit` 命令,就會讓 UML 停止運作,因為已經沒有使用者層級的程式:
```shell
UML # exit
```
你會看到類似的輸出:
```
Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
...
(core dumped) ./linux
```
如果發現開發環境的終端機游標消失,可執行 `reset` (不要擔心,這是重置終端機,不是整台電腦) 命令。
若你嫌每次都要額外執行 `$ reset`,也可將下方命令加在 `UML.sh` 的最後一行:
```shell
stty sane ; echo
```
這樣下次啟動 UML 並離開後,終端機就會自行復原。
## 網路設定
以下命令針對開發環境:
```shell
$ sudo ip tuntap add tap0 mode tap
$ sudo ip link set tap0 up
$ sudo ip address add 192.168.100.100/24 dev tap0
```
修改之前的 UML 啟動參數,在 `umid=uml0` 後面增加 `hostname=uml1 eth0=tuntap,tap0` (記得前後有空白)
重新啟動 UML 後,執行以下命令:
```shell
UML # ip link set eth0 up
UML # ip address add 192.168.100.101/24 dev eth0
UML # ping -c 3 192.168.100.100
```
預期可見以下輸出:
```
PING 192.168.100.100 (192.168.100.100): 56 data bytes
64 bytes from 192.168.100.100: seq=0 ttl=64 time=0.343 ms
64 bytes from 192.168.100.100: seq=1 ttl=64 time=0.235 ms
64 bytes from 192.168.100.100: seq=2 ttl=64 time=0.281 ms
--- 192.168.100.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2054ms
rtt min/avg/max/mdev = 0.050/0.057/0.065/0.006 ms
```
## 客製化 UML 環境
[tini](https://github.com/krallin/tini) 工具可避免 UML 一類的 guest 環境造成 [zombie processes](https://en.wikipedia.org/wiki/Zombie_process)。取得並在 rootfs 做好準備:
```shell
$ wget -O rootfs/sbin/tini https://github.com/krallin/tini/releases/download/v0.19.0/tini-static
$ chmod +x rootfs/sbin/tini
```
建立 `rootfs/init.sh` 檔案,其內容如下:
```shell
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sys /sys
exec /sbin/tini /bin/sh +m
```
記得要變更檔案權限,使其可執行:
```shell
$ chmod +x 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`:
```shell
#!/bin/sh
./linux umid=uml0 \
root=/dev/root rootfstype=hostfs hostfs=./rootfs \
rw mem=64M init=/init.sh quiet
stty sane ; echo
```
接著重新執行 `UML.sh` 即可在開機過程自動掛載 `/proc` 和 `/sys`。
我們可變更 UML 環境中命令提示訊息,使得裡頭包含 `UML` 字樣,例如:
```shell
UML # export PS1='UML:\w\ $ '
```
也可追加終端機色彩,使得 `UML` 命令提示更顯目:
```shell
UML # export PS1='\[\033[01;32mUML:\w\033[00m \$ '
```
這樣命令提示訊息就變成綠色。更多色彩的組合,可參見:
* [How to Change the Color of your Linux Prompt](https://linuxhostsupport.com/blog/how-to-change-the-color-of-your-linux-prompt/)
* [Bash Profile Generator](http://xta.github.io/HalloweenBash/)
可在 host 端修改 `rootfs/init.sh`,將上述 `export PS1` 加在 `exec /sbin/tini /bin/sh` 的前一行,這樣下次啟動 UML 就會生效。
## 準備核心模組
編譯核心模組
```shell
$ make ARCH=um SUBARCH=x86_64 modules
```
預期將看到若干個以 `.ko` 結尾的檔案。隨後我們將安裝這些核心模組到 rootfs 所在的目錄,注意最終目錄名稱應為
```shell
/lib/modules/`uname -r`
```
這裡先用固定名稱,稍後切換到 UML 環境時再更名:
```shell
$ make modules_install MODLIB=`pwd`/rootfs/lib/modules/VER ARCH=um
```
> As suggested by [`make help`](https://www.kernel.org/doc/makehelp.txt)
> ```
> Other generic targets:
> all - Build all targets marked with [*]
> * vmlinux - Build the bare kernel
> * modules - Build all modules
> modules_install - Install all modules to INSTALL_MOD_PATH (default: /)
> ...
> ```
> [name=Nickchen Nick]
> [time=Thu, Jun 16, 2022 10:32 AM]
啟動 UML 並在環境中執行以下命令:
```shell
UML # cd /lib/modules
UML # mv VER `uname -r`
UML # depmod -ae `uname -r`
```
測試:
```shell
UML # modprobe isofs
UML # lsmod
```
預期輸出:
```
Module Size Used by Tainted: G
isofs 25330 0
```
確認核心模組的功能正確運作後,接著我們可撰寫自己的核心模組。在 Linux 核心程式碼最上層建立一個名為 `tests` 的目錄:
```shell
$ mkdir -p tests
```
建立檔案 `tests/hello.c`,其內容為:
```cpp
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello World! - init\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Hello World! - exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
```
還要有對應的 `Makefile`,內容如下:
```cpp
obj-m += hello.o
PWD := $(shell pwd)
KDIR := $(PWD)/..
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules ARCH=um
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean ARCH=um
```
注意在 `default:` 和 `$(MAKE)` 之間不是空白字元,而是 Tab,同理,`clean:` 和 `$(MAKE)` 之間也以 Tab 區隔。
編譯上述核心模組並複製到 rootfs 中:
```shell
$ make -C tests
$ cp tests/hello.ko rootfs/
```
再次啟動 UML,掛載 `hello.ko`:
```shell
UML # insmod hello.ko
```
預期輸出為:
```
Hello World! - init
```
隨後卸載核心模組:
```shell
UML # rmmod hello
```
剛才的核心模組太單純,我們再挑戰稍微複雜的試驗。以下的核心模組嘗試將 Linux 內部的 `jiffies` 和 `HZ` 透過 sysfs 揭露給使用者層級。
先準備開發用目錄:
```shell
$ mkdir -p tests/ticks
```
建立 `tests/ticks/ticks.c` 檔案,內容如下:
```cpp
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/kernel.h>
static ssize_t jiffies_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf) {
return sprintf(buf, "%lu", jiffies);
}
static ssize_t hz_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf) {
return sprintf(buf, "%u", HZ);
}
static struct kobj_attribute jiffies_attr =
__ATTR(jiffies, 0444, jiffies_show, NULL);
static struct kobj_attribute hz_attr = __ATTR(hz, 0444, hz_show, NULL);
static struct attribute *ticks_attrs[] = { &jiffies_attr.attr, &hz_attr.attr,
NULL };
static struct attribute_group ticks_grp = { .attrs = ticks_attrs };
static struct kobject *ticks;
static int __init ticks_init(void) {
int retval;
ticks = kobject_create_and_add("ticks", NULL);
if (!ticks)
return -EEXIST;
retval = sysfs_create_group(ticks, &ticks_grp);
if (retval)
kobject_put(ticks);
return retval;
}
module_init(ticks_init);
static void __exit ticks_exit(void) {
sysfs_remove_group(ticks, &ticks_grp);
kobject_put(ticks);
}
module_exit(ticks_exit);
MODULE_LICENSE("GPL");
```
也準備對應的 `tests/ticks/Makefile` 檔案,內容如下:
```shell
obj-m := ticks.o
KDIR= ../../
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules ARCH=um
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean ARCH=um
```
:::info
:warning: 在 `$(MAKE) -C` 之前的字元不是空白,而是 tab
:::
編譯核心模組:
```shell
$ pushd tests/ticks
$ make
$ popd
$ cp tests/ticks/ticks.ko rootfs/
```
:::info
`pushd` 和 `popd` 是 [GNU Bash](https://www.gnu.org/software/bash/) 提供的內建命令,可參見 [Directory Stack Builtins](https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html),允許快速在目錄間切換,若你不使用 [GNU Bash](https://www.gnu.org/software/bash/),該命令可能會無效,請自行切換目錄並記得回到 `$WS` 指向的目錄,即 Linux 核心原始程式碼所在目錄。
:::
進入 UML 環境,並進行測試:
```shell
UML # insmod /ticks.ko
UML # cat /sys/ticks/jiffies
UML # cat /sys/ticks/hz
```
應可看到各自的數值,其中 `HZ` 設定為 `100`。
## 搭配 GDB 進行核心追蹤和分析
Linux 核心提供 GDB script,讓分析和除錯更加便利,不過 GDB script 用 Python 撰寫,於是我們首先檢查 gdb 是否開啟 Python 的整合:
```shell
$ gdb -q -ex "python print(1+1)" -ex "quit"
```
若看到輸出 `2` 也就是 Python 執行 `1 + 1` 的結果,那表示 GDB 內建 Python 模組。
建構 GDB script:
```shell
$ echo "CONFIG_GDB_SCRIPTS=y" > .config-fragment
$ ARCH=um scripts/kconfig/merge_config.sh .config .config-fragment
$ make ARCH=um scripts_gdb
```
注意上方的 `ARCH=um` 不能省略,否則會使核心組態錯亂。
用下行命令來啟動 GDB,指定載入 Linux 核心提供的 GDB script:
```shell
$ gdb -ex "add-auto-load-safe-path scripts/gdb/vmlinux-gdb.py" \
-ex "file vmlinux" \
-ex "lx-version" -q
```
預期可見到:
```
Reading symbols from vmlinux...done.
Linux version 5.12.0 (jserv@node1) (gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1 Tue Apr 26 05:41:22 CST 2021
```
如果沒有見到 `Linux version 5.12.0` 這個訊息,就意味著 Linux 核心提供的 GDB script 沒有正確產生。GDB script 沒有正確載入的錯誤訊息如下:
```
Undefined command: "lx-version". Try "help".
```
:::info
以下用 ==`(gdb) `== 表示在 GDB 命令提示列裡頭輸入的命令
:::
此時在 `(gdb) ` 命令提示符號旁輸入 `lx` 但不要按下 Enter/Return 按鍵,而是按下 Tab 按鍵,就會發現 GDB 自動補完由 GDB script 提供的自行定義命令,像是 `lx-dmesg`, `lx-symbols `, `lx-cpus` 等等。詳細描述可透過執行以下命令取得:
```shell
(gdb) apropos lx
```
為了讓後續的操作更便利,我們準備名為 `gdbinit` 的檔案,內容如下:
```shell
python gdb.COMPLETE_EXPRESSION = gdb.COMPLETE_SYMBOL
add-auto-load-safe-path scripts/gdb/vmlinux-gdb.py
file vmlinux
lx-version
set args umid=uml0 root=/dev/root rootfstype=hostfs rootflags=FULLPATH/rootfs rw mem=64M init=/init.sh quiet
handle SIGSEGV nostop noprint
handle SIGUSR1 nopass stop print
```
再執行:
```shell
$ sed -i 's|FULLPATH|'"$PWD"'|' gdbinit
```
用意是將 `FULLPATH` 換為目前目錄的完整路徑。
為何要設定 handle 呢?
* UML 使用 `SIGSEGV` 作為內部通訊,而 GDB 會自動將此 signal 轉包給除錯器本身;
* `SIGUSR1` 是我們要求停止 UML 執行所用的 signal;
接著可執行以下命令:
```shell
$ gdb -q -x gdbinit
```
預期會看到 UML 的核心版本號碼和 `(gdb) ` 命令提示。此刻,我們終於可在 GDB 內啟動 UML:
```shell
(gdb) run
```
這時可見 UML 執行到 `UML:/ #` 命令提示訊息。不過很快就發現,就算按下 Ctrl-C 組合按鍵,我們仍身處 UML,那該如何喚醒 GDB 呢?再開啟另一個終端機視窗,然後執行:
```shell
$ pkill -SIGUSR1 -o vmlinux
```
再切換回原本執行 GDB 的終端機視窗,可見到以下訊息:
```
Thread 1 "vmlinux" received signal SIGUSR1, User defined signal 1.
```
我們即可在 GDB 執行命令,例如:
```shell
(gdb) lx-mounts
(gdb) lx-cmdline
(gdb) lx-ps
(gdb) lx-dmesg
(gdb) lx-lsmod
(gdb) print $lx_module("hello")
```
`(gdb) print $lx_module("hello")` 應在 `UML # insmod /hello.ko` 之後執行。
注意到最後一個命令的 `$` 符號,這是 GDB script 所定義的函式。我們可執行以下 GDB 命令:
```shell
(gdb) print $lx_task_by_pid(1).comm
(gdb) print $lx_task_by_pid(1).cred
```
上方函式的參數 `1` 就表示 PID (Process ID) = 1 即 `init` 程序。我們繼續做實驗:
```shell
(gdb) lx-ps
```
預期得到以下輸出:
```shell
0x603f8aa0 <init_task> 0 swapper
0x63828040 1 tini
0x63832080 2 kthreadd
0x638380c0 3 kworker/0:0
0x6383a100 4 kworker/0:0H
0x63846140 5 kworker/u2:0
0x6384c180 6 mm_percpu_wq
0x6384e1c0 7 ksoftirqd/0
0x6385a200 8 kdevtmpfs
0x6386a240 9 netns
0x63888280 10 oom_reaper
0x638902c0 11 writeback
0x63892300 12 kworker/u2:1
0x6389c340 15 kblockd
0x638a2380 16 blkcg_punt_bio
0x638fc3c0 17 kworker/0:1
0x63916400 18 kswapd0
0x639c0480 21 sh
```
第一個欄位是地址,第二個欄位是 PID,不難發現 init 程序的地址是 `0x63828040`,於是我們可揭開 init 的內部資訊: (很長的列表,按下 Enter 繼續瀏覽)
```shell
(gdb) print *(struct task_struct *)0x63828040
```
也可檢驗 Linux 核心內部來得知:
```shell
(gdb) p $container_of(init_task.tasks.next, "struct task_struct", "tasks")
```
預期輸出就是 `0x63828040`。別忘了,Linux 核心用 linked list 來表示程序資訊,來做實驗:
```shell
(gdb) lx-list-check init_task.tasks
```
預期輸出:
```
Starting with: {next = 0x63828230, prev = 0x639c0670}
list is consistent: 18 node(s)
```
感受到 UML 搭配 GDB 的威力了吧!
延伸閱讀: [Debugging kernel and modules via gdb](https://www.kernel.org/doc/html/v4.10/dev-tools/gdb-kernel-debugging.html)
當然也能在 GDB 設定中斷點,比方說在掛載核心模組的實作程式碼:
```shell
(gdb) break do_init_module
```
參考輸出:
```
Breakpoint 1 at 0x600865ce: file kernel/module.c, line 3519.
```
注意到這個中斷點編號為 `1`,如果每次有核心模組掛載時,就會觸發中斷並且停留在命令提示列,說來有點惱人,於是我們可善用 GDB 裡頭的 `command`。先執行以下命令: (`1` 是數字一)
```shell
(gdb) command 1
```
然後會見到這樣的訊息:
```
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>
```
在提示符號 `>` 後面,就是我們自訂的命令動作,繼續輸入以下:
```shell
py if str(gdb.parse_and_eval("mod->name")).find("hello") != 1: gdb.execute("continue", False, False)
end
```
在 `end` 輸入後,我們又見到熟悉的 `(gdb) ` 命令提示列。
輸入 `(gdb) continue` 繼續執行,並參照上述掛載 `hello.ko`:
```shell
UML # insmod /hello.ko
```
這時就會觸發中斷點,參考輸出如下:
```
Thread 1 "vmlinux" hit Breakpoint 1, do_init_module (mod=0x6481d140) at kernel/module.c:3519
3519 {
```
用 `(gdb) list` 觀察,預期會得到類似下方輸出:
```cpp
3514 *
3515 * Keep it uninlined to provide a reliable breakpoint target, e.g. for the gdb
3516 * helper command 'lx-symbols'.
3517 */
3518 static noinline int do_init_module(struct module *mod)
3519 {
3520 int ret = 0;
3521 struct mod_initfree *freeinit;
3522
3523 freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL)
```
---
TODO:
* [Docker in User Mode Linux](https://github.com/weber-software/diuid)
* [Kernel Networking Unit Tests](https://source.android.com/devices/architecture/kernel/network_tests): The tests use User-Mode Linux to boot the kernel as a process on a Linux host machine.
* video: [Easy Kernel Development with User Mode Linux](https://youtu.be/-LA9aBfC6j8)
* [Getting the best from your server with User-Mode Linux](https://blog.bytemark.co.uk/wp-content/uploads/2012/04/GettingTheBest.pdf)
* [Running virtualized native drivers in User Mode Linux](http://folk.uio.no/paalee/referencing_publications/ref-nr-guffens-presentation05.pdf)
* [Advanced Network Simulation under User-Mode Linux](https://www.strongswan.org/uml/DFN_UML.pdf)
* [WiFi Layer for User Mode Linux](https://sites.google.com/site/vincentguffens/wifi4uml)
* [User Mode Linux HOWTO](https://www.kernel.org/doc/Documentation/virt/uml/UserModeLinux-HOWTO.txt)
* [Linux as a Hypervisor](https://www.linuxsecrets.com/kdocs/ols/2006/ols2006v1-pages-225-234.pdf)
* [Netkit](http://wiki.netkit.org/): Netkit exploits open source software (mostly licensed under GPL) and is heavily based on the User Mode Linux (UML)
* [User-Mode Linux](http://elf.cs.pub.ro/soa/res/lectures/lecture-05-uml.pdf)
* [User-mode Linux/Guide](https://wiki.gentoo.org/wiki/User-mode_Linux/Guide)
* [User Mode Linux: Linux inside Linux inside Linux inside...](http://www.mulix.org/lectures/user-mode-linux-lkdsg-oct-2003/user_mode_linux.pdf)