使用 GDB + Qemu 調(diào)試 Linux 內(nèi)核

更多奇技淫巧歡迎訂閱博客:https://fuckcloudnative.io
1. 概述
在某些情況下,我們需要對于內(nèi)核中的流程進(jìn)行分析,雖然通過 BPF 的技術(shù)可以對于函數(shù)傳入的參數(shù)和返回結(jié)果進(jìn)行展示,但是在流程的調(diào)試上還是不如直接 GDB 單步調(diào)試來的直接。本文采用的編譯方式如下,在一臺 16 核 CentOS 7.7 的機(jī)器上進(jìn)行內(nèi)核源碼相關(guān)的編譯(主要是考慮編譯效率),調(diào)試則是基于 VirtualBox 的 Ubuntu 20.04 系統(tǒng)中,采用 Qemu + GDB 進(jìn)行單步調(diào)試,網(wǎng)上查看了很多文章,在最終進(jìn)行單步跟蹤的時候,始終不能夠在斷點處停止,進(jìn)行過多次嘗試和查詢文檔,最終發(fā)現(xiàn)需要在內(nèi)核啟動參數(shù)上添加 nokaslr ,本文是對整個搭建過程的總結(jié)。

2. Linux 內(nèi)核編譯和文件系統(tǒng)制作
Linux 內(nèi)核編譯
編譯內(nèi)核和制作文件系統(tǒng)在 CentOS 7.7 的機(jī)器上。源碼從國內(nèi)清華的源下載:http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/, 此處選擇 linux-4.19.172.tar.gz 版本。詳細(xì)編譯步驟如下:
$?sudo?yum?group?install?"Development?Tools"
$?yum?install?ncurses-devel?bison?flex?elfutils-libelf-devel?openssl-devel
$?wget?http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/v4.x/linux-4.19.172.tar.gz
$?tar?xzvf?linux-4.19.172.tar.gz
$?cd?linux-4.19.172/
$?make?menuconfig
在內(nèi)核編譯選項中,開啟如下 “Compile the kernel with debug info”, 4.19.172 中默認(rèn)已經(jīng)選中:
Kernel hacking —> Compile-time checks and compiler options —> [ ] Compile the kernel with debug info

以上配置完成后會在當(dāng)前目錄生成 .config 文件,我們可以使用 grep 進(jìn)行驗證:
#?grep?CONFIG_DEBUG_INFO?.config
CONFIG_DEBUG_INFO=y
接著我們進(jìn)行內(nèi)核編譯:
$?nproc???????#?查看當(dāng)前的系統(tǒng)核數(shù)
$?make?-j?12??#?或者采用?make?bzImage?進(jìn)行編譯,?-j?N,表示使用多少核并行編譯
#?未壓縮的內(nèi)核文件,這個在?gdb?的時候需要加載,用于讀取?symbol?符號信息,由于包含調(diào)試信息所以比較大
$?ls?-hl?vmlinux
-rwxr-xr-x?1?root?root?449M?Feb??3?14:46?vmlinux
#?壓縮后的鏡像文件
$?ls?-hl?./arch/x86_64/boot/bzImage
lrwxrwxrwx?1?root?root?22?Feb??3?14:47?./arch/x86_64/boot/bzImage?->?../../x86/boot/bzImage
$?ls?-hl?./arch/x86/boot/bzImage
-rw-r--r--?1?root?root?7.6M?Feb??3?14:47?./arch/x86/boot/bzImage
不同發(fā)行版本下的內(nèi)核的詳細(xì)編譯文檔可以參考這里[1]。
啟動內(nèi)存文件系統(tǒng)制作
#?首先安裝靜態(tài)依賴,否則會有報錯,參見后續(xù)的排錯章節(jié)
$?yum?install?-y?glibc-static.x86_64?-y
$?wget?https://busybox.net/downloads/busybox-1.32.1.tar.bz2
$?tar?-xvf?busybox-1.32.1.tar.bz2
$?cd?busybox-1.32.1/
$?make?menuconfig
#?安裝完成后生成的相關(guān)文件會在?_install?目錄下
$?make?&&?make?install
$?cd?_install
$?mkdir?proc
$?mkdir?sys
$?touch?init
#??init?內(nèi)容見后續(xù)章節(jié),為內(nèi)核啟動的初始化程序
$?vim?init
#?必須設(shè)置成可執(zhí)行文件
$?chmod?+x?init
$?find?.?|?cpio?-o?--format=newc?>?./rootfs.img
cpio:?File?./rootfs.img?grew,?2758144?new?bytes?not?copied
10777?blocks
$?ls?-hl?rootfs.img
-rw-r--r--?1?root?root?5.3M?Feb??2?11:23?rootfs.img
其中上述的 init 文件內(nèi)容如下,打印啟動日志和系統(tǒng)的整個啟動過程花費(fèi)的時間:
#!/bin/sh
echo?"{==DBG==}?INIT?SCRIPT"
mkdir?/tmp
mount?-t?proc?none?/proc
mount?-t?sysfs?none?/sys
mount?-t?debugfs?none?/sys/kernel/debug
mount?-t?tmpfs?none?/tmp
mdev?-s
echo?-e?"{==DBG==}?Boot?took?$(cut?-d'?'?-f1?/proc/uptime)?seconds"
#?normal?user
setsid?/bin/cttyhack?setuidgid?1000?/bin/sh
到此為止我們已經(jīng)編譯了好了 Linux 內(nèi)核(vmlinux 和 bzImage)和啟動的內(nèi)存文件系統(tǒng)(rootfs.img)。
錯誤排查
在編譯過程中出現(xiàn)以下報錯:
/bin/ld:?cannot?find?-lcrypt
/bin/ld:?cannot?find?-lm
/bin/ld:?cannot?find?-lresolv
/bin/ld:?cannot?find?-lrt
collect2:?error:?ld?returned?1?exit?status
Note:?if?build?needs?additional?libraries,?put?them?in?CONFIG_EXTRA_LDLIBS.
Example:?CONFIG_EXTRA_LDLIBS="pthread?dl?tirpc?audit?pam"
出錯的原因是因為我們采用靜態(tài)編譯依賴的底層庫沒有安裝,如果不清楚這些庫有哪些 rpm 安裝包提供,則可以通過 yum provides 命令查看,然后安裝相關(guān)依賴包重新編譯即可。
$?yum?provides?*/libm.a
//?...
glibc-static-2.17-317.el7.x86_64?:?C?library?static?libraries?for?-static?linking.
Repo????????:?base
Matched?from:
Filename????:?/usr/lib64/libm.a
3. Qemu 啟動內(nèi)核
在上述步驟準(zhǔn)備好以后,我們需要在調(diào)試的 Ubuntu 20.04 的系統(tǒng)中安裝 Qemu 工具,其中調(diào)測的 Ubuntu 系統(tǒng)使用 VirtualBox 安裝。
$?apt?install?qemu?qemu-utils?qemu-kvm?virt-manager?libvirt-daemon-system?libvirt-clients?bridge-utils
把上述編譯好的 vmlinux、bzImage、rootfs.img 和編譯的源碼拷貝到我們當(dāng)前 Unbuntu 機(jī)器中。
拷貝 Linux 編譯的源碼主要是在 gdb 的調(diào)試過程中查看源碼,其中 vmlinux 和 linux 源碼處于相同的目錄,本例中 vmlinux 位于 linux-4.19.172 源目錄中。
$?qemu-system-x86_64?-kernel?./bzImage?-initrd??./rootfs.img?-append?"nokaslr?console=ttyS0"?-s?-S?-nographic
使用上述命令啟動調(diào)試,啟動后會停止在界面處,并等待遠(yuǎn)程 gdb 進(jìn)行調(diào)試,在使用 GDB 調(diào)試之前,可以先使用以下命令進(jìn)程測試內(nèi)核啟動是否正常。
$?qemu-system-x86_64?-kernel?./bzImage?-initrd??./rootfs.img?-append?"nokaslr?console=ttyS0"?-nographic
其中命令行中各參數(shù)如下:
-kernel ./bzImage:指定啟用的內(nèi)核鏡像;-initrd ./rootfs.img:指定啟動的內(nèi)存文件系統(tǒng);-append "nokaslr console=ttyS0":附加參數(shù),其中nokaslr參數(shù)必須添加進(jìn)來,防止內(nèi)核起始地址隨機(jī)化,這樣會導(dǎo)致 gdb 斷點不能命中;參數(shù)說明可以參見這里[2]。-s:監(jiān)聽在 gdb 1234 端口;-S:表示啟動后就掛起,等待 gdb 連接;-nographic:不啟動圖形界面,調(diào)試信息輸出到終端與參數(shù)console=ttyS0組合使用;

4. GDB 調(diào)試
在使用 qemu-system-x86_64 命令啟動內(nèi)核以后,進(jìn)入到我們從編譯機(jī)器上拷貝過來的 Linux 內(nèi)核源代碼目錄中,在另外一個終端我們來啟動 gdb 命令:
[linux-4.19.172]$?gdb
(gdb)?file?vmlinux???????????#?vmlinux?位于目錄?linux-4.19.172?中
(gdb)?target?remote?:1234
(gdb)?break?start_kernel?????#?有些文檔建議使用?hb?硬件斷點,我在本地測試使用?break?也是?ok?的
(gdb)?c?????????????#?啟動調(diào)試,則內(nèi)核會停止在?start_kernel?函數(shù)處
整體運(yùn)行界面如下:

5. Eclipse 圖像化調(diào)試
我們可以通過 eclipse-cdt 進(jìn)行可視化項目調(diào)試。
”File“ -> “New” -> “Project” ,然后選擇 ”Makefile Project with Existing Code“ 選項,后續(xù)按照向?qū)?dǎo)入代碼。

在 “Run” -> “Debug Configurations” 選項中,創(chuàng)建一個 ”C/C++ Attach to Application“ 的調(diào)試選項。
Project:選擇我們剛才創(chuàng)建的項目名字; C/C++ Application:選擇編譯 Linux 內(nèi)核帶符號信息表的 vmlinux; Build before launching:選擇 ”Disable auto build“; Debugger:選擇 gdbserver,具體設(shè)置如下圖; 在 Debugger 中的 Connection 信息中選擇 ”TCP“,并填寫端口為 ”1234“;
啟動 Debug 調(diào)試,即可看到與 gdb 類似的窗口。

啟動 ”Debug“ 調(diào)試以后的窗口如下,在 Debug 窗口欄中,設(shè)置與 gdb 調(diào)試相同的步驟即可。

6. 參考
How to compile and install Linux Kernel 5.6.9 from source code[3] 用 qemu + gdb 調(diào)試 linux 內(nèi)核[4] *** QEMU+busybox 搭建 Linux 內(nèi)核運(yùn)行環(huán)境[5] *** QEMU+gdb 調(diào)試 Linux 內(nèi)核全過程[6] * linux 內(nèi)核編譯與調(diào)試方法[7] How to Build A Custom Linux Kernel For Qemu (2015 Edition)[8] qemu 與 qemu-kvm 到底什么區(qū)別[9] 在 qemu 環(huán)境中用 gdb 調(diào)試 Linux 內(nèi)核[10] *
參考資料
這里: https://www.cyberciti.biz/tips/compiling-linux-kernel-26.html
[2]這里: https://www.zhihu.com/question/270476360
[3]How to compile and install Linux Kernel 5.6.9 from source code: https://www.cyberciti.biz/tips/compiling-linux-kernel-26.html
[4]用 qemu + gdb 調(diào)試 linux 內(nèi)核: https://www.jianshu.com/p/431d606d322c
[5]QEMU+busybox 搭建 Linux 內(nèi)核運(yùn)行環(huán)境: https://www.sunxiaokong.xyz/2020-01-14/lzx-linuxkernel-qemuinit/
[6]QEMU+gdb 調(diào)試 Linux 內(nèi)核全過程: https://blog.csdn.net/jasonLee_lijiaqi/article/details/80967912
[7]linux 內(nèi)核編譯與調(diào)試方法: https://www.cnblogs.com/syw-casualet/p/5271369.html
[8]How to Build A Custom Linux Kernel For Qemu (2015 Edition): http://mgalgs.github.io/2015/05/16/how-to-build-a-custom-linux-kernel-for-qemu-2015-edition.html
[9]qemu 與 qemu-kvm 到底什么區(qū)別: https://www.cnblogs.com/hugetong/p/8808544.html
[10]在 qemu 環(huán)境中用 gdb 調(diào)試 Linux 內(nèi)核: https://www.cnblogs.com/wipan/p/9264979.html
原文鏈接:https://www.ebpf.top/post/qemu_gdb_busybox_debug_kernel/


你可能還喜歡
點擊下方圖片即可閱讀

云原生是一種信仰???
關(guān)注上面的公眾號
后臺回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點擊?"閱讀原文"?獲取更好的閱讀體驗!
??給個「在看」,是對我最大的支持??

