--- 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)