• 正文
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

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

5小時前
187
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

前言

對用戶態(tài)進程,利用gdb調(diào)試代碼是很方便的手段。而對于內(nèi)核態(tài)的問題,可以利用crash等工具基于coredump文件進行調(diào)試。

其實我們也可以利用一些手段對Linux內(nèi)核代碼進行g(shù)db調(diào)試,qemu就是一種。

qemu是一款完全軟件模擬(Binary translation)的虛擬化軟件,在虛擬化的實現(xiàn)中性能相對較差。但利用它在測試環(huán)境中g(shù)db調(diào)試Linux內(nèi)核代碼,是熟悉Linux內(nèi)核代碼的一個好方法。

本文實驗環(huán)境:

    ubuntu 20.04busybox-1.32.1Linux kernel 4.9.3QEMUGDB 10.1

編譯內(nèi)核源碼

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

在內(nèi)核編譯選項中,開啟如下"Compile the kernel with debug info"

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

示意圖如下,利用鍵盤選中debug選項,然后敲"Y"勾選:

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

grep?CONFIG_DEBUG_INFO?.config
CONFIG_DEBUG_INFO=y

編譯內(nèi)核

make?bzimage?-j4

編譯完成后,會在當(dāng)前目錄下生成vmlinux,這個在 gdb 的時候需要加載,用于讀取 symbol 符號信息,包含了所有調(diào)試信息,所以比較大。

壓縮后的鏡像文件為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內(nèi)核文件的區(qū)別:

vmlinux 編譯出來的最原始的內(nèi)核文件,未壓縮。

zImage ?是vmlinux經(jīng)過gzip壓縮后的文件。

bzImage bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在于,zImage解壓縮內(nèi)核到低端內(nèi)存(第一個640K)。

bzImage解壓縮內(nèi)核到高端內(nèi) 存(1M以上)。如果內(nèi)核比較小,那么采用zImage或bzImage都行,如果比較大應(yīng)該用bzImage。

uImage ?U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的tag。

vmlinuz 是bzImage/zImage文件的拷貝或指向bzImage/zImage的鏈接。

initrd ?是“initial ramdisk”的簡寫。一般被用來臨時的引導(dǎo)硬件到實際內(nèi)核vmlinuz能夠接管并繼續(xù)引導(dǎo)的狀態(tài)。

編譯busybox

Linux系統(tǒng)啟動階段,boot loader加載完內(nèi)核文件vmlinuz后,內(nèi)核緊接著需要掛載磁盤根文件系統(tǒng),但如果此時內(nèi)核沒有相應(yīng)驅(qū)動,無法識別磁盤,就需要先加載驅(qū)動。

而驅(qū)動又位于/lib/modules,得掛載根文件系統(tǒng)才能讀取,這就陷入了一個兩難境地,系統(tǒng)無法順利啟動。

于是有了initramfs根文件系統(tǒng),其中包含必要的設(shè)備驅(qū)動和工具,bootloader加載initramfs到內(nèi)存中,內(nèi)核會將其掛載到根目錄/,然后運行/init腳本,掛載真正的磁盤根文件系統(tǒng)。

這里借助BusyBox構(gòu)建極簡initramfs,提供基本的用戶態(tài)可執(zhí)行程序。

可以從busybox官網(wǎng)地址下載最新版本,或者直接使用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之前,我們需要對其進行設(shè)置,執(zhí)行make menuconfig,如下

這里一定要選擇靜態(tài)編譯,編譯好的可執(zhí)行文件busybox不依賴動態(tài)鏈接庫,可以獨立運行,方便構(gòu)建initramfs。

之后選擇Exit退出,到這里我們就可以編譯busybox了,執(zhí)行下面的命令

make?-j?8
#?安裝完成后生成的相關(guān)文件會在?_install?目錄下
make?&&?make?install

構(gòu)建initramfs根文件系統(tǒng)

[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的內(nèi)容如下

#!/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啟動調(diào)試內(nèi)核

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

    • :指定啟用的內(nèi)核鏡像;

-initrd ../initramfs.cpio.gz

    • :指定啟動的內(nèi)存文件系統(tǒng);

-append "nokaslr console=ttyS0"

    • :附加參數(shù),其中

nokaslr

    • 參數(shù)必須添加進來,防止內(nèi)核起始地址隨機化,這樣會導(dǎo)致 gdb 斷點不能命中;

-s

    • :監(jiān)聽在 gdb 1234 端口;

-S

    • :表示啟動后就掛起,等待 gdb 連接;

-nographic

    • :不啟動圖形界面,調(diào)試信息輸出到終端與參數(shù)

console=ttyS0

    組合使用;

在另一個窗口中,輸入gdb,即可開啟調(diào)試。

(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調(diào)試時報錯了,在查閱了諸多資料后,很多博客都給出了修復(fù)方法:源碼重新安裝gdb,并修改gdb/remote.c文件的一段代碼。但是我嘗試了,發(fā)現(xiàn)行不通。

出現(xiàn)該問題的原因是:編譯 的是64 位模式的內(nèi)核代碼,但是運行是在 32 位保護模式下。64 位代碼將無法在該環(huán)境中正常運行。

終于在stackflow上找到了修復(fù)方法:具體可以參考下面兩篇文章。

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

文章中給出了三種修復(fù)方法,我這里只列出了一種,即修改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 啟動調(diào)試。

???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?<http://gnu.org/licenses/gpl.html>
This?is?free?software:?you?are?free?to?change?and?redistribute?it.
There?is?NO?WARRANTY,?to?the?extent?permitted?by?law.
Type?"show?copying"?and?"show?warranty"?for?details.
This?GDB?was?configured?as?"x86_64-pc-linux-gnu".
Type?"show?configuration"?for?configuration?details.
For?bug?reporting?instructions,?please?see:
<https://www.gnu.org/software/gdb/bugs/>.
Find?the?GDB?manual?and?other?documentation?resources?online?at:
????<http://www.gnu.org/software/gdb/documentation/>.

For?help,?type?"help".
Type?"apropos?word"?to?search?for?commands?related?to?"word".
(gdb)?file?vmlinux
Reading?symbols?from?vmlinux...
(gdb)?target?remote?localhost:1234
Remote?debugging?using?localhost:1234
warning:?Can?not?parse?XML?target?description;?XML?support?was?disabled?at?compile?time
warning:?Assuming?long-mode?change.?[Remote?'g'?packet?reply?is?too?long:?PU]
0x000000000000fff0?in?exception_stacks?()
(gdb)?break?start_kernel
Breakpoint?1?at?0xffffffff81fc6a95:?file?init/main.c,?line?486.
(gdb)?break??rest_init
Breakpoint?2?at?0xffffffff818aa1e1:?file?init/main.c,?line?385.
(gdb)?c
Continuing.

Breakpoint?1,?start_kernel?()?at?init/main.c:486
486?????????????set_task_stack_end_magic(&init_task);
(gdb)?c
Continuing.

Breakpoint?2,?rest_init?()?at?init/main.c:385
385?????{
(gdb)?

在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

掃碼加我微信

進技術(shù)交流群

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

作者就職于某500強公司,擔(dān)任BSP工程師。具有豐富的嵌入式開發(fā)經(jīng)驗。專欄主要分享計算機基礎(chǔ),操作系統(tǒng),Linux驅(qū)動開發(fā),Arm體系與架構(gòu),C/C++,數(shù)據(jù)結(jié)構(gòu)與算法等相關(guān)文章。歡迎關(guān)注我的公眾號【嵌入式與Linux那些事】,一起學(xué)習(xí)交流。