
雖然我們很多人都是在linux系統上做應用程序開發,一般接觸不到Linux內核代碼,但是了解Linux內核的底層實現機制,對應用程序的開發,尤其是性能方面的優化提升會有很大的幫助。
研究Linux內核,我們可以看看源碼,并且把內核代碼給跑起來,通過gdb來調試它。下面我們來具體實操看一下Linux內核的編譯以及調試方法。
cd /usr/srcwget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.4.11.tar.xztar -xf linux-6.4.11.tar.xzcd /usr/src/linux-6.4.11
sudo apt install libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev dwarves
執行 sudo make menuconfig 點擊save保存,生成.config文件,在vim .config修改如下內核參數配置:
CONFIG_DEBUG_INFO=y #在內核和內核模塊中包含調試信息CONFIG_FRAME_POINTER=y #將調用幀信息保存在寄存器或堆棧上的不同位置,使gdb在調試內核時可以更準確地構造堆棧回溯跟蹤(stack back traces)。CONFIG_GDB_SCRIPTS=y CONFIG_KGDB=y #啟用內置的內核調試器,該調試器允許進行遠程調試CONFIG_DEBUG_INFO_REDUCED=nCONFIG_RANDOMIZE_BASE=n #KASLR會更改引導時放置內核代碼的基地址, 無法從gdb設置斷點CONFIG_SYSTEM_TRustED_KEYS=""CONFIG_SYSTEM_REVOCATION_KEYS=""
如果CONFIG_SYSTEM_TRUSTED_KEYS和CONFIG_SYSTEM_REVOCATION_KEYS沒有修改,會報如下錯誤:
No rule to make target 'debian/canonical-certs.pem', needed by 'certs/x509_certificate_list'. Stop.
sudo make -j8 sudo make bzImage #編譯內核映像文件sudo make modules #編譯模塊sudo make modules_install #安裝模塊sudo make install #安裝內核
安裝內核后,確認/boot/grub/grub.cfg中是否已增加了剛剛編譯的新的內核選項
重啟虛擬機。在GRUB界面選擇 Ubuntu 高級選項,選擇剛剛的內核版本linux-6.4.11進去,就可以進入了新的內核。
可以看到,Ubuntu虛擬機原來的內核版本是5.4.0-156,這里給它升級了新的內核版本6.4.11:
wget https://busybox.NET/downloads/busybox-1.36.1.tar.bz2 tar -xvf busybox-1.36.1.tar.bz2
修改.config編譯參數:先執行make defconfig,在.config文件中添加CONFIG_STATIC=y
mkdir rootfscd rootfs/cp -r ../busybox-1.36.1/_install/bin/ .cp -r ../busybox-1.36.1/_install/sbin/ .cp -r ../busybox-1.36.1/_install/usr/ .mkdir dev proc syscd ..chmod 777 -R rootfs/cd rootfs/touch init
#!/bin/sh
dmesg -n 1mount -t devtmpfs none /devmount -t proc none /procmount -t sysfs none /syssetsid cttyhack /bin/sh
chmod 777 initfind . | cpio -R root:root -H newc -o | gzip > ../rootfs.gz
Linux內核有多種調試方式,這里我們采用的是通過QEMU虛擬機加gdb遠程調試的方式。
物理機:windows系統調試機: Ubuntu 20.04.5 LTS虛擬機,安裝在VMware上,內核版本為5.4.0-156被調試機:QEMU虛擬機,使用新編譯的內核6.4.11版本和自制的簡易文件系統
apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils
這里需要指定上面我們編譯linux內核時產生的內核映像文件bzImage和剛剛制作的rootfs.gz文件系統:
qemu-system-x86_64 -kernel /usr/src/linux-6.4.11/arch/x86_64/boot/bzImage -initrd /home/kernel/rootfs.gz -Append "nokaslr console=ttyS0" -s -S -nographic
我們也可以先不加-s和-S參數,測試驗證一下編譯的Linux內核是否能正常啟動:
qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -nographic
-kernel ./bzImage:指定啟用的內核鏡像;-initrd ./rootfs.img:指定啟動的內存文件系統;-append "nokaslr console=ttyS0":附加參數,其中 參數必須添加進來,防止內核起始地址隨機化,這樣會導致 gdb 斷點不能命中;參數說明可以參見這里。nokaslr-s:監聽在 gdb 1234 端口;-S:表示啟動后就掛起,等待 gdb 連接((CPU 初始化之前凍結起來);-nographic:不啟動圖形界面,調試信息輸出到終端與參數 組合使用;console=ttyS
如果要退出QEMU虛擬機,可以先按ctrl + a鍵,然后再按x鍵,即可退出QEMU。
編譯Linux內核時,會生成一個vmlinux文件,vmlinux是Linux內核編譯出來的原始的內核文件,可以用來進行調試內核和定位內核問題。
cd /usr/src/linux-6.4.11/gdb vmlinuxtarget remote :1234
跟gdb調試普通程序一樣,我們可以設置一下斷點,然后按c鍵繼續運行:
b start_kernelb rest_initc
可以看到gdb在start_kernel和rest_init兩個函數斷點處停住了,按c鍵后,QMUE虛擬機成功進入了系統。start_kernel是Linux內核啟動時C代碼開始的地方,研究內核啟動過程就可以從start_kernel開始看。