前言
對(duì)用戶態(tài)進(jìn)程,利用gdb調(diào)試代碼是很方便的手段。而對(duì)于內(nèi)核態(tài)的問(wèn)題,可以利用crash等工具基于coredump文件進(jìn)行調(diào)試。
其實(shí)我們也可以利用一些手段對(duì)Linux內(nèi)核代碼進(jìn)行g(shù)db調(diào)試,qemu就是一種。
qemu是一款完全軟件模擬(Binary translation)的虛擬化軟件,在虛擬化的實(shí)現(xiàn)中性能相對(duì)較差。但利用它在測(cè)試環(huán)境中g(shù)db調(diào)試Linux內(nèi)核代碼,是熟悉Linux內(nèi)核代碼的一個(gè)好方法。
本文實(shí)驗(yàn)環(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)核編譯選項(xiàng)中,開(kāi)啟如下"Compile the kernel with debug info"
Kernel?hacking?--->
??Compile-time?checks?and?compiler?options?--->
????[?]?Compile?the?kernel?with?debug?info
示意圖如下,利用鍵盤(pán)選中debug選項(xiàng),然后敲"Y"勾選:
以上配置完成后會(huì)在當(dāng)前目錄生成 .config
文件,我們可以使用 grep
進(jìn)行驗(yàn)證:
grep?CONFIG_DEBUG_INFO?.config
CONFIG_DEBUG_INFO=y
編譯內(nèi)核
make?bzimage?-j4
編譯完成后,會(huì)在當(dāng)前目錄下生成vmlinux,這個(gè)在 gdb 的時(shí)候需要加載,用于讀取 symbol 符號(hào)信息,包含了所有調(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 編譯出來(lái)的最原始的內(nèi)核文件,未壓縮。
zImage ?是vmlinux經(jīng)過(guò)gzip壓縮后的文件。
bzImage bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在于,zImage解壓縮內(nèi)核到低端內(nèi)存(第一個(gè)640K)。
bzImage解壓縮內(nèi)核到高端內(nèi) 存(1M以上)。如果內(nèi)核比較小,那么采用zImage或bzImage都行,如果比較大應(yīng)該用bzImage。
uImage ?U-boot專用的映像文件,它是在zImage之前加上一個(gè)長(zhǎng)度為0x40的tag。
vmlinuz 是bzImage/zImage文件的拷貝或指向bzImage/zImage的鏈接。
initrd ?是“initial ramdisk”的簡(jiǎn)寫(xiě)。一般被用來(lái)臨時(shí)的引導(dǎo)硬件到實(shí)際內(nèi)核vmlinuz能夠接管并繼續(xù)引導(dǎo)的狀態(tài)。
編譯busybox
Linux系統(tǒng)啟動(dòng)階段,boot loader加載完內(nèi)核文件vmlinuz后,內(nèi)核緊接著需要掛載磁盤(pán)根文件系統(tǒng),但如果此時(shí)內(nèi)核沒(méi)有相應(yīng)驅(qū)動(dòng),無(wú)法識(shí)別磁盤(pán),就需要先加載驅(qū)動(dòng)。
而驅(qū)動(dòng)又位于/lib/modules
,得掛載根文件系統(tǒng)才能讀取,這就陷入了一個(gè)兩難境地,系統(tǒng)無(wú)法順利啟動(dòng)。
于是有了initramfs根文件系統(tǒng),其中包含必要的設(shè)備驅(qū)動(dòng)和工具,bootloader加載initramfs到內(nèi)存中,內(nèi)核會(huì)將其掛載到根目錄/
,然后運(yùn)行/init
腳本,掛載真正的磁盤(pán)根文件系統(tǒng)。
這里借助BusyBox構(gòu)建極簡(jiǎn)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之前,我們需要對(duì)其進(jìn)行設(shè)置,執(zhí)行make menuconfig,如下
這里一定要選擇靜態(tài)編譯,編譯好的可執(zhí)行文件busybox
不依賴動(dòng)態(tài)鏈接庫(kù),可以獨(dú)立運(yùn)行,方便構(gòu)建initramfs。
之后選擇Exit退出,到這里我們就可以編譯busybox了,執(zhí)行下面的命令
make?-j?8
#?安裝完成后生成的相關(guān)文件會(huì)在?_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
#?必需要安裝這兩個(gè)庫(kù)
sudo?apt-get?install?texinfo
sudo?apt-get?install?build-essential
make?-j?8
sudo?make?install
QEMU啟動(dòng)調(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
-
- :指定啟動(dòng)的內(nèi)存文件系統(tǒng);
-append "nokaslr console=ttyS0"
-
- :附加參數(shù),其中
nokaslr
-
- 參數(shù)必須添加進(jìn)來(lái),防止內(nèi)核起始地址隨機(jī)化,這樣會(huì)導(dǎo)致 gdb 斷點(diǎn)不能命中;
-s
-
- :監(jiān)聽(tīng)在 gdb 1234 端口;
-S
-
- :表示啟動(dòng)后就掛起,等待 gdb 連接;
-nographic
-
- :不啟動(dòng)圖形界面,調(diào)試信息輸出到終端與參數(shù)
console=ttyS0
- 組合使用;
在另一個(gè)窗口中,輸入gdb,即可開(kāi)啟調(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
但是,在啟動(dòng)GDP調(diào)試時(shí)報(bào)錯(cuò)了,在查閱了諸多資料后,很多博客都給出了修復(fù)方法:源碼重新安裝gdb,并修改gdb/remote.c文件的一段代碼。但是我嘗試了,發(fā)現(xiàn)行不通。
出現(xiàn)該問(wèn)題的原因是:編譯 的是64 位模式的內(nèi)核代碼,但是運(yùn)行是在 32 位保護(hù)模式下。64 位代碼將無(wú)法在該環(huán)境中正常運(yù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 啟動(dòng)調(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 打了兩個(gè)斷點(diǎn), 兩個(gè)斷點(diǎn)都成功命中了。
本文參考
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
掃碼加我微信
進(jìn)技術(shù)交流群