--- tags: Linux Kernel Internals, 作業系統 --- # Linux 核心設計: Kernel Debugging(1): Kdump ## Overview [Kdump](https://zh.wikipedia.org/zh-tw/Kdump) 是 Linux 核心的一個特殊功能,可在發生 [kernel panic](https://en.wikipedia.org/wiki/Kernel_panic) 時建立[Core Dump](https://en.wikipedia.org/wiki/Core_dump)。當出現異常導致 Kdump 被觸發時,kernel 會匯出一個記憶體的 image,其在 Linux 上通稱為 vmcore。該 image 可用於除錯和確定崩潰的原因。這個 image 會以 [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) 格式匯出,可以在處理核心崩潰時通過 `/proc/vmcore` 直接存取,或者可以也可將其自動儲存到本地或遠端檔案系統。 Kdump 大致的運作方式是: 在 kernel panic 的情況下,Kdump 會引導另一個 Linux kernel(稱為 dump-capture kernel),用它來匯出和儲存記憶體狀態。這麼做的目的是讓系統啟動到一個乾淨、可靠的環境,而不是依賴已經崩潰的 kernel,因為後者可能會引發其他問題反而破壞了原本的異常環境。例如,在寫入記憶體轉儲檔案時導致檔案系統損壞。 為了實現這個「雙核心」的環境,Kdump 在核心崩潰後會立即使用 [`kexec`](https://en.wikipedia.org/wiki/Kexec) booting 到 dump-capture kernel,使用 kexec 引導「覆蓋」當前執行的核心,這做法同時避免執行 bootloader 和系統韌體(BIOS/UEFI)的硬體初始化行為。 整體的運作即如下附圖所展示: ![image](https://hackmd.io/_uploads/rk_vv1F3a.png) 為了讓 dump-capture kernel 在系統崩潰並執行不破壞原本的異常環境,系統需要保留少量 RAM 以利其 booting 與運作。不過像是 x86/ppc64 架構可能必需要 RAM 的一個固定位置來 booting。在這種情況下,為避免覆蓋原始 kernel,kexec 會建立該部分 RAM 的副本,以便之後 dump-capture kernel 可以存取到相關訊息。RAM 需保留部分的大小和可選位置通過 boot paramters `crashkernel` 指定。則在主核心啟動之後,kexec 會將 dump-capture kernel 及其 initrd image 預載入到那部分保留的 RAM。 ## 建置與測試 Kdump 環境 接下來,本文將要示範如何使用 Kdump 與相關工具。由於目的旨在介紹使用的方式,並可以輕易的直接在自己的電腦中嘗試,會以虛擬的環境(QEMU)做示範。在實體機器環境上的使用流程其實也大致相同。 實際上,現有材料中已經有許多基於 QEMU 使用 Kdump 的案例,例如: > * [使用 Qemu 虚拟 ARM64 平台演示 kdump 崩溃转存](https://www.byteisland.com/%E4%BD%BF%E7%94%A8-qemu-%E6%BC%94%E7%A4%BA-kdump-%E5%B4%A9%E6%BA%83%E8%BD%AC%E5%AD%98/) > 為了簡化環境的設置,這裡使用更易上手的 [virtme-ng](https://github.com/arighi/virtme-ng) 進行示範,詳細的安裝方式可見[本文](https://hackmd.io/UefggpvoSN63itixrYN5ug#virtme-ng)。若對編譯及啟動 Linux 等相關流程陌生的讀者也可先閱讀 [Linux 核心設計: 開發、測試與除錯環境](https://hackmd.io/UefggpvoSN63itixrYN5ug) 全文。 :::info 使用 virtme-ng 和直接使用 QEMU 最大的區別實際上只在檔案系統的存取。前者可以很容易存取到 host 端的檔案,後者則需相對複雜的設定。由於這裡目的只是展示 Kdump 的功能,因此選擇更容易嘗試的前者。 ::: 此外,下面的展示僅以 x86_64 環境為例,性質上偏向懶人包。若想知道每個步驟背後細節,甚至是其他支援平台的設定,可直接參照 Linux 文件 [Documentation for Kdump - The kexec-based Crash Dumping Solution](https://docs.kernel.org/admin-guide/kdump/kdump.html),後者將提供更清楚的資訊。 ### 編譯 Linux 核心 首先,我們取得任意的 Linux 核心版本 ``` make defconfig ``` 為簡化用詞,我們將一開始啟動的主核心成為 system kernel,崩潰後會啟動的核心則是之前介紹的 dump-capture kernel。則針對 system kernel,必須確認 `.config` 中以下的選項是開啟的: ``` CONFIG_KEXEC=y CONFIG_KEXEC_CORE=y CONFIG_SYSFS=y CONFIG_CRASH_CORE=y ``` 此外還要將除錯資訊編譯到 kernel image 中,否則啟用 kdump 的意義也不大。開啟下面的選項: ``` CONFIG_DEBUG_INFO=y CONFIG_DEBUG_INFO_DWARF4=y CONFIG_DEBUG_KERNEL=y ``` 針對 dump-capture kernel,則需要以下幾個選項: ``` CONFIG_CRASH_DUMP=y CONFIG_PROC_VMCORE=y ``` 一般來說,dump-capture kernel 僅做為 core dump 和除錯目的,因此會和 system kernel 分開編譯。不過為求簡化,在 x86_64 我們也可以讓同一個 kernel image 擔任兩者。此時額外以下兩個選項,本文也以此種方式來實驗。 ``` CONFIG_RELOCATABLE=y CONFIG_PHYSICAL_START=0x100000 ``` 完成 config 之後即可編譯核心。 ``` make -j$(nproc) ``` ### 取得 kxec 工具 透過以下步驟可取得 [kexec](https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git)。 ``` wget http://kernel.org/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz tar xvpzf kexec-tools.tar.gz cd kexec-tools-VERSION ./configure make make install ``` 特別留意由於本文是以 virtme-ng 為環境,因此直接將工具下載到 host 即可。若是用 QEMU 或是實體機器則需將 kexec 放到其可以存取到的檔案系統(例如 rootfs)中。 ### 編譯 initrd 使用 virtme-ng 的情況下,一般來說不需要另外為 system kernel 建立 initrd。不過由於 dump-capture kernel 也需要另外的 initrd,這裡我們仍需建立一個。 詳細方式請參照[編譯並建置 initrd(Busybox)](https://hackmd.io/UefggpvoSN63itixrYN5ug#%E7%B7%A8%E8%AD%AF%E4%B8%A6%E5%BB%BA%E7%BD%AE-initrdBusybox)。 :::warning 作者也嘗試改為使用 [Ubuntu rootfs(ubuntu-base)](http://cdimage.ubuntu.com/ubuntu-base/),好處是提供的工具相對於 busybox 會豐富一點。不過似乎容易因檔案過大而無法和 dump-capture kernel 一同載入。 ``` wget http://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04.1-base-amd64.tar.gz mkdir rootfs; cd rootfs tar xvpzf ../ubuntu-base-22.04.1-base-amd64.tar.gz ``` 如果對此有任何想法歡迎提出~ ::: ### 取得 Core Dump 完成上述環境的準備後,我們可以用 virtme-ng 來啟動 system kernel。 ``` vng -v --append 'crashkernel=256M' ``` 這裡提供留意到這裏必須提供 `crashkernel` 以提示 system kernel 需保留給 dump-capture kernel 的記憶體量。 接著我們要使用之前下載的 kexec 來將 dump-capture kernel 和 initrd 預先載入。參考以下命令,但留意到 kernel 和 initrd 的路徑需改為使用者電腦上的正確路徑。此外,選用 kernel 的檔案格式也會影響參數的選用,詳見 [Load the Dump-capture Kernel](https://docs.kernel.org/admin-guide/kdump/kdump.html#load-the-dump-capture-kernel)。 ``` sudo /usr/local/sbin/kexec -p arch/x86_64/boot/bzImage \ --initrd=../busybox/root.img \ --append="root=/dev/sda 1 irqpoll nr_cpus=1 reset_devices earlyprintk=serial" ``` 載入之後,我們就可以透過下面的命令來製造 panic 了! ``` sudo sh -c "echo c > /proc/sysrq-trigger" ``` 如果一切正常,應該可以看到 panic 導致 kernel 顯示 stack unwinding 的錯誤資訊,之後重新啟動。則啟動完成之後,我們就可以透過以下方式從模擬環境中取得 core dump 的資料了! ``` (輸入 ctrl+a c 進入 QEMU debug 模式) dump-guest-memory -z guest.img ``` :::danger 實體機器上一般是透過 `cp /proc/vmcore <dump-file>` 來取得 core dump,不過 QEMU 上似乎受限於模擬的磁碟空間大小不容易仿照這個做法? ::: ## makedumpfile 如果完整的 vmcore 檔案太大,複製其內容到儲存裝置上將需要一段時間。若只想擷取其中有興趣的內容,則可以改為嘗試 [`makedumpfile`](https://github.com/makedumpfile/makedumpfile) 工具。 ``` $ git clone git@github.com:makedumpfile/makedumpfile.git $ make $ make install ``` ``` $ makedumpfile -R guestdump.img < guest.img ``` ## Crash 重新開機至正常運作的 system kernel 下,我們就可以使用前面取得的 core dump 來分析錯誤發生的原因。推薦的方式是使用 [crash](https://github.com/crash-utility/crash) 這個設計於分析 dump 的工具。藉由以下方式取得並安裝: ``` git clone git@github.com:crash-utility/crash.git cd crash make -j$(nproc) sudo make install ``` 安裝完成後,則可以輸入以下命令。之後應該就可以看到類似的訊息。 ``` $ crash guest.img vmlinux ... KERNEL: vmlinux DUMPFILE: guest.img [PARTIAL DUMP] CPUS: 20 DATE: Wed Feb 28 17:49:36 CST 2024 UPTIME: 00:00:38 LOAD AVERAGE: 0.28, 0.08, 0.03 TASKS: 212 NODENAME: virtme-ng RELEASE: 6.8.0-rc4-g8cec3dd9e593 VERSION: #3 SMP PREEMPT_DYNAMIC Wed Feb 28 17:20:49 CST 2024 MACHINE: x86_64 (2918 Mhz) MEMORY: 4 GB PANIC: "Kernel panic - not syncing: sysrq triggered crash" PID: 521 COMMAND: "sh" TASK: ffff8eba4160b900 [THREAD_INFO: ffff8eba4160b900] CPU: 11 STATE: TASK_RUNNING (PANIC) ``` 在 `PANIC` 的地方我們就可以看到 crash 為我們說明了造成 kernel 崩潰的原因! 更多 crash 可做到的分析請參考 [crash](https://github.com/crash-utility/crash) 底下 README.md 的說明。 ## drgn ### Install [libkdumpfile](https://github.com/ptesarik/libkdumpfile) ``` $ autoreconf -fi $ ./configure $ make $ sudo make install ``` [drgn](https://github.com/osandov/drgn) ``` $ git clone https://github.com/osandov/drgn.git $ cd drgn $ python3 setup.py build $ sudo python3 setup.py install ``` ``` export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ``` ``` $ makedumpfile -R guestdump.img < guest.img $ drgn -c guestdump.img -s vmlinux ``` ``` vng -v -o '\-device vmcoreinfo' --append 'crashkernel=256M' ``` https://github.com/osandov/drgn/issues/350 https://github.com/brenns10/kernel_stuff/tree/master/vmcoreinfo ``` ../vmcoreinfo/dumpphys -i -c ./guest2.img -o vmcoreinfo ``` ``` drgn -c guestdump.img -s vmlinux --vmcoreinfo vmcoreinfo --architecture x86_64 ``` ## Reference * [What's Inside a Linux Kernel Core Dump](https://blogs.oracle.com/linux/post/whats-inside-a-linux-kernel-core-dump?fbclid=IwAR3S-xXAT8LSoUbqERz99k4ccKvsPL8IvGq5dKoICCDNg10WW4bCt9Mnygk) * [Linux Kernel Debugging, Kdump, Crash Tool Basics Part-1](https://www.youtube.com/watch?v=6l0ulgv1OJ4) * [Documentation for Kdump - The kexec-based Crash Dumping Solution](https://docs.kernel.org/admin-guide/kdump/kdump.html)