使用 GDB + Qemu 調試 Linux 內核


更多奇技淫巧歡迎訂閱博客:https://fuckcloudnative.io

1. 概述

在某些情況下,我們需要對於內核中的流程進行分析,雖然通過 BPF 的技術可以對於函數傳入的參數和返回結果進行展示,但是在流程的調試上還是不如直接 GDB 單步調試來的直接。本文采用的編譯方式如下,在一臺 16 核 CentOS 7.7 的機器上進行內核源碼相關的編譯(主要是考慮編譯效率),調試則是基於 VirtualBox 的 Ubuntu 20.04 系統中,採用 Qemu + GDB 進行單步調試,網上查看了很多文章,在最終進行單步跟蹤的時候,始終不能夠在斷點處停止,進行過多次嘗試和查詢文檔,最終發現需要在內核啓動參數上添加 nokaslr ,本文是對整個搭建過程的總結。

2. Linux 內核編譯和文件系統製作

Linux 內核編譯

編譯內核和製作文件系統在 CentOS 7.7 的機器上。源碼從國內清華的源下載:http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/, 此處選擇 linux-4.19.172.tar.gz 版本。詳細編譯步驟如下:

$ 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

在內核編譯選項中,開啓如下 “Compile the kernel with debug info”, 4.19.172 中默認已經選中:

Kernel hacking —> Compile-time checks and compiler options —> [ ] Compile the kernel with debug info

以上配置完成後會在當前目錄生成 .config 文件,我們可以使用 grep 進行驗證:

# grep CONFIG_DEBUG_INFO .config
CONFIG_DEBUG_INFO=y

接着我們進行內核編譯:

$ nproc       # 查看當前的系統核數
$ make -j 12  # 或者採用 make bzImage 進行編譯, -j N,表示使用多少核並行編譯

# 未壓縮的內核文件,這個在 gdb 的時候需要加載,用於讀取 symbol 符號信息,由於包含調試信息所以比較大
$ 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

不同發行版本下的內核的詳細編譯文檔可以參考這裏[1]

啓動內存文件系統製作

# 首先安裝靜態依賴,否則會有報錯,參見後續的排錯章節
$ 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

# 安裝完成後生成的相關文件會在 _install 目錄下
$ make && make install

cd _install
$ mkdir proc
$ mkdir sys
$ touch init

#  init 內容見後續章節,爲內核啓動的初始化程序
$ vim init

# 必須設置成可執行文件
$ 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 文件內容如下,打印啓動日誌和系統的整個啓動過程花費的時間:

#!/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

到此爲止我們已經編譯了好了 Linux 內核(vmlinux 和 bzImage)和啓動的內存文件系統(rootfs.img)。

錯誤排查

在編譯過程中出現以下報錯:

/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"

出錯的原因是因爲我們採用靜態編譯依賴的底層庫沒有安裝,如果不清楚這些庫有哪些 rpm 安裝包提供,則可以通過 yum provides 命令查看,然後安裝相關依賴包重新編譯即可。

$ 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 啓動內核

在上述步驟準備好以後,我們需要在調試的 Ubuntu 20.04 的系統中安裝 Qemu 工具,其中調測的 Ubuntu 系統使用 VirtualBox 安裝。

$ apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils

把上述編譯好的 vmlinux、bzImage、rootfs.img 和編譯的源碼拷貝到我們當前 Unbuntu 機器中。

拷貝 Linux 編譯的源碼主要是在 gdb 的調試過程中查看源碼,其中 vmlinux 和 linux 源碼處於相同的目錄,本例中 vmlinux 位於 linux-4.19.172 源目錄中。

$ qemu-system-x86_64 -kernel ./bzImage -initrd  ./rootfs.img -append "nokaslr console=ttyS0" -s -S -nographic

使用上述命令啓動調試,啓動後會停止在界面處,並等待遠程 gdb 進行調試,在使用 GDB 調試之前,可以先使用以下命令進程測試內核啓動是否正常。

$ qemu-system-x86_64 -kernel ./bzImage -initrd  ./rootfs.img -append "nokaslr console=ttyS0" -nographic

其中命令行中各參數如下:

  • -kernel ./bzImage:指定啓用的內核鏡像;
  • -initrd ./rootfs.img:指定啓動的內存文件系統;
  • -append "nokaslr console=ttyS0" :附加參數,其中 nokaslr 參數必須添加進來,防止內核起始地址隨機化,這樣會導致 gdb 斷點不能命中;參數說明可以參見 這裏 [2]
  • -s :監聽在 gdb 1234 端口;
  • -S :表示啓動後就掛起,等待 gdb 連接;
  • -nographic:不啓動圖形界面,調試信息輸出到終端與參數 console=ttyS0 組合使用;

4. GDB 調試

在使用 qemu-system-x86_64 命令啓動內核以後,進入到我們從編譯機器上拷貝過來的 Linux 內核源代碼目錄中,在另外一個終端我們來啓動 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             # 啓動調試,則內核會停止在 start_kernel 函數處

整體運行界面如下:

5. Eclipse 圖像化調試

我們可以通過 eclipse-cdt 進行可視化項目調試。

”File“ -> “New” -> “Project” ,然後選擇 ”Makefile Project with Existing Code“ 選項,後續按照嚮導導入代碼。

在 “Run” -> “Debug Configurations” 選項中,創建一個 ”C/C++ Attach to Application“ 的調試選項。

  • Project:選擇我們剛纔創建的項目名字;
  • C/C++ Application:選擇編譯 Linux 內核帶符號信息表的 vmlinux;
  • Build before launching:選擇 ”Disable auto build“;
  • Debugger:選擇 gdbserver,具體設置如下圖;
  • 在 Debugger 中的 Connection 信息中選擇 ”TCP“,並填寫端口爲 ”1234“;

啓動 Debug 調試,即可看到與 gdb 類似的窗口。

啓動 ”Debug“ 調試以後的窗口如下,在 Debug 窗口欄中,設置與 gdb 調試相同的步驟即可。

6. 參考

  • How to compile and install Linux Kernel 5.6.9 from source code [3]
  • 用 qemu + gdb 調試 linux 內核 [4] ***
  • QEMU+busybox 搭建 Linux 內核運行環境 [5] ***
  • QEMU+gdb 調試 Linux 內核全過程 [6] *
  • linux 內核編譯與調試方法 [7]
  • How to Build A Custom Linux Kernel For Qemu (2015 Edition) [8]
  • qemu 與 qemu-kvm 到底什麼區別 [9]
  • 在 qemu 環境中用 gdb 調試 Linux 內核 [10] *

參考資料

[1]

這裏: 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 調試 linux 內核: https://www.jianshu.com/p/431d606d322c

[5]

QEMU+busybox 搭建 Linux 內核運行環境: https://www.sunxiaokong.xyz/2020-01-14/lzx-linuxkernel-qemuinit/

[6]

QEMU+gdb 調試 Linux 內核全過程: https://blog.csdn.net/jasonLee_lijiaqi/article/details/80967912

[7]

linux 內核編譯與調試方法: 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 到底什麼區別: https://www.cnblogs.com/hugetong/p/8808544.html

[10]

在 qemu 環境中用 gdb 調試 Linux 內核: https://www.cnblogs.com/wipan/p/9264979.html


原文鏈接:https://www.ebpf.top/post/qemu_gdb_busybox_debug_kernel/



你可能還喜歡

點擊下方圖片即可閱讀

什麼?WireGuard 可以讓躲在 NAT 後面的客戶端之間直連了??

雲原生是一種信仰 🤘


關注上面的公衆號

後臺回覆◉k8s◉獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!



點擊 "閱讀原文" 獲取更好的閱讀體驗!


         
         
         
❤️ 給個 「在看」 ,是對我最大的支持❤️

本文分享自微信公衆號 - 雲原生實驗室(cloud_native_yang)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章