利用QEMU+GDB搭建Linux內核調試環境
前言
對用戶態進程,利用gdb調試代碼是很方便的手段。而對于內核態的問題,可以利用crash等工具基于coredump文件進行調試。
其實我們也可以利用一些手段對Linux內核代碼進行gdb調試,qemu就是一種。
qemu是一款完全軟件模擬(Binary translation)的虛擬化軟件,在虛擬化的實現中性能相對較差。但利用它在測試環境中gdb調試Linux內核代碼,是熟悉Linux內核代碼的一個好方法。
本文實驗環境:
ubuntu 20.04
busybox-1.32.1
Linux kernel 4.9.3
QEMU
GDB 10.1
編譯內核源碼
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git tar -xvzf linux-4.9.301.tar.gz cd linux-4.9.301 make menuconfig
在內核編譯選項中,開啟如下"Compile the kernel with debug info"
Kernel hacking ---> Compile-time checks and compiler options ---> [ ] Compile the kernel with debug info
示意圖如下,利用鍵盤選中debug選項,然后敲"Y"勾選:
以上配置完成后會在當前目錄生成 .config 文件,我們可以使用 grep 進行驗證:
grep CONFIG_DEBUG_INFO .config CONFIG_DEBUG_INFO=y
編譯內核
make bzimage -j4
編譯完成后,會在當前目錄下生成vmlinux,這個在 gdb 的時候需要加載,用于讀取 symbol 符號信息,包含了所有調試信息,所以比較大。
壓縮后的鏡像文件為bzImage, 在arch/x86/boot/目錄下。
? linux-4.9.301 ls -hl vmlinux -rwxrwxr-x 1 ubuntu ubuntu 578M Apr 15 08:14 vmlinux ? linux-4.9.301 ls -hl ./arch/x86_64/boot/bzImage lrwxrwxrwx 1 ubuntu ubuntu 22 Apr 15 08:15 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage ? linux-4.9.301 ls -hl ./arch/x86/boot/bzImage -rw-rw-r-- 1 ubuntu ubuntu 9.3M Apr 15 08:15 ./arch/x86/boot/bzImage
幾種linux內核文件的區別:
vmlinux 編譯出來的最原始的內核文件,未壓縮。
zImage 是vmlinux經過gzip壓縮后的文件。
bzImage bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在于,zImage解壓縮內核到低端內存(第一個640K)。
bzImage解壓縮內核到高端內 存(1M以上)。如果內核比較小,那么采用zImage或bzImage都行,如果比較大應該用bzImage。
uImage U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的tag。
vmlinuz 是bzImage/zImage文件的拷貝或指向bzImage/zImage的鏈接。
initrd 是“initial ramdisk”的簡寫。一般被用來臨時的引導硬件到實際內核vmlinuz能夠接管并繼續引導的狀態。
編譯busybox
Linux系統啟動階段,boot loader加載完內核文件vmlinuz后,內核緊接著需要掛載磁盤根文件系統,但如果此時內核沒有相應驅動,無法識別磁盤,就需要先加載驅動。
而驅動又位于/lib/modules,得掛載根文件系統才能讀取,這就陷入了一個兩難境地,系統無法順利啟動。
于是有了initramfs根文件系統,其中包含必要的設備驅動和工具,bootloader加載initramfs到內存中,內核會將其掛載到根目錄/,然后運行/init腳本,掛載真正的磁盤根文件系統。
這里借助BusyBox構建極簡initramfs,提供基本的用戶態可執行程序。
可以從busybox官網地址下載最新版本,或者直接使用wget下載我使用的版本。
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
在編譯busybox之前,我們需要對其進行設置,執行make menuconfig,如下
這里一定要選擇靜態編譯,編譯好的可執行文件busybox不依賴動態鏈接庫,可以獨立運行,方便構建initramfs。
之后選擇Exit退出,到這里我們就可以編譯busybox了,執行下面的命令
make -j 8 # 安裝完成后生成的相關文件會在 _install 目錄下 make && make install
構建initramfs根文件系統
[root@localhost temp]# ls busybox-1.29.0 busybox-1.29.0.tar.bz2 [root@localhost temp]# mkdir initramfs [root@localhost temp]# cd initramfs [root@localhost initramfs]# cp ../busybox-1.29.0/_install/* -rf ./ [root@localhost initramfs]# mkdir dev proc sys [root@localhost initramfs]# sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/ [root@localhost initramfs]# rm -f linuxrc [root@localhost initramfs]# vim init [root@localhost initramfs]# chmod a+x init [root@localhost initramfs]# ls bin dev init proc sbin sys usr
其中init的內容如下
#!/bin/busybox sh echo "{==DBG==} INIT SCRIPT" mount -t proc none /proc mount -t sysfs none /sys echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds" exec /sbin/init
打包initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz [root@localhost initramfs]# ls ../ busybox-1.29.0 busybox-1.29.0.tar.bz2 initramfs initramfs.cpio.gz
安裝QEMU
apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils
安裝GDB
wget https://ftp.gnu.org/gnu/gdb/gdb-10.1.tar.gz tar -xzvf gdb-10.1.tar.gz cd gdb-10.1 ./configure # 必需要安裝這兩個庫 sudo apt-get install texinfo sudo apt-get install build-essential make -j 8 sudo make install
QEMU啟動調試內核
? linux-4.9.301 qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd ../initramfs.cpio.gz -append "nokaslr console=ttyS0" -s -S -nographic
-kernel ./arch/x86/boot/bzImage:指定啟用的內核鏡像;
-initrd ../initramfs.cpio.gz:指定啟動的內存文件系統;
-append "nokaslr console=ttyS0" :附加參數,其中 nokaslr 參數必須添加進來,防止內核起始地址隨機化,這樣會導致 gdb 斷點不能命中;
-s :監聽在 gdb 1234 端口;
-S :表示啟動后就掛起,等待 gdb 連接;
-nographic:不啟動圖形界面,調試信息輸出到終端與參數 console=ttyS0 組合使用;
在另一個窗口中,輸入gdb,即可開啟調試。
(gdb) target remote localhost:1234 Remote debugging using localhost:1234 warning: Can not parse XML target description; XML support was disabled at compile time Remote 'g' packet reply is too long (expected 560 bytes, got 608 bytes): 0000000000000000000000000000000000000000000000006306000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ff0000000000000200000000f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801f0000 (gdb) Remote debugging using localhost:1234 Undefined command: "Remote". Try "help". (gdb) warning: Can not parse XML target description; XML support was disabled at compile timeQuit
但是,在啟動GDP調試時報錯了,在查閱了諸多資料后,很多博客都給出了修復方法:源碼重新安裝gdb,并修改gdb/remote.c文件的一段代碼。但是我嘗試了,發現行不通。
出現該問題的原因是:編譯 的是64 位模式的內核代碼,但是運行是在 32 位保護模式下。64 位代碼將無法在該環境中正常運行。
終于在stackflow上找到了修復方法:具體可以參考下面兩篇文章。
https://stackoverflow.com/questions/48620622/how-to-solve-qemu-gdb-debug-error-remote-g-packet-reply-is-too-long
https://wiki.osdev.org/QEMU_and_GDB_in_long_mode
文章中給出了三種修復方法,我這里只列出了一種,即修改GDB源碼,重新編譯安裝。
--- gdb/remote.c 2016-04-14 11:13:49.962628700 +0300 +++ gdb/remote.c 2016-04-14 11:15:38.257783400 +0300 @@ -7181,8 +7181,28 @@ buf_len = strlen (rs->buf); /* Further sanity checks, with knowledge of the architecture. */ +// HACKFIX for changing architectures for qemu. It's ugly. Don't use, unless you have to. + // Just a tiny modification of the patch of Matias Vara (http://forum.osdev.org/viewtopic.php?f=13&p=177644) if (buf_len > 2 * rsa->sizeof_g_packet) - error (_("Remote 'g' packet reply is too long: %s"), rs->buf); + { + warning (_("Assuming long-mode change. [Remote 'g' packet reply is too long: %s]"), rs->buf); + rsa->sizeof_g_packet = buf_len ; + + for (i = 0; i < gdbarch_num_regs (gdbarch); i++) + { + if (rsa->regs[i].pnum == -1) + continue; + + if (rsa->regs[i].offset >= rsa->sizeof_g_packet) + rsa->regs[i].in_g_packet = 0; + else + rsa->regs[i].in_g_packet = 1; + } + + // HACKFIX: Make sure at least the lower half of EIP is set correctly, so the proper + // breakpoint is recognized (and triggered). + rsa->regs[8].offset = 16*8; + } /* Save the size of the packet sent to us by the target. It is used as a heuristic when determining the max size of packets that the
cd gdb-10.1 ./configure make -j 8 sudo make install
接著就可以敲gdb 啟動調試。
? linux-4.9.301 gdb GNU gdb (GDB) 10.1 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later
在start_kernel 和 rest_init 打了兩個斷點, 兩個斷點都成功命中了。
本文參考
https://www.shuzhiduo.com/A/kjdw2a2q5N/
https://cloud.tencent.com/developer/article/1793157
https://blog.csdn.net/alexanderwang7/article/details/113180447
https://blog.csdn.net/sjc2870/article/details/122017247
https://stackoverflow.com/questions/8662468/remote-g-packet-reply-is-too-long
https://stackoverflow.com/questions/4943857/linux-kernel-live-debugging-how-its-done-and-what-tools-are-used/42316607#42316607
ARM C 語言 Linux 嵌入式 虛擬化
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。