在Qemu中開發內核的優勢
在進行內核驅動開發或者內核功能開發的時候,如果在主機上進行調試,每次安裝需要數分鐘的時間,如果出錯需要重啟主機,要花費更多的時間。
甚至在某些極端情況下會損壞主機文件系統上重要內容,或者啟動出錯則需要更長的時間先恢復錯誤然后繼續調試。
在大部分情況下,內核驅動的開發可以在虛擬機上進行,每次重啟虛機的時間在數十秒之內,可以節省大量的時間。并且不用擔心影響到開發主機,下面介紹如何利用qemu來做內核開發。
第一步安裝qemu虛擬機
有圖形界面的安裝
qemu-img create -f qcow2 new_image.qcow2 40G qemu-system-x86_64 \
-machine q35 -cpu host -enable-kvm \
-smp 2,sockets=1,cores=2,threads=1 -m 4G \
-boot d -cdrom ./ctyunos-2.0.1-220329-everything-x86_64-dvd.iso \
-hda ./new_image.qcow2 -vnc :2
進入安裝界面安裝 vncviewer :2
無圖形界面,通過virt-install安裝
virt-install -n vm1 -r 1024 --vcpus=1 --os-variant=rhel5.4 --accelerate \
--nographics -v --disk path=./new_image.qcow2 --extra-args "console=ttyS0" \
--location ./ctyunos-2.0.1-220329-everything-x86_64-dvd.iso --check path_in_use=off
對qemu鏡像進行必要的設置
NBD方式對鏡像修改
modprobe nbd max_part=8
qemu-nbd -c /dev/nbd0 vm1.qcow2
fdisk -l /dev/nbd0
如果disk是使用磁盤分區的方式
mount /dev/nbd0p3 /mnt/disk
如果disk是使用lvm的方式
vgscan --cache vgchange -ay
如果vg重名,可以先修改鏡像中vg name,完成內部修改后再改回去
mount /dev/mapper/centos-root /mnt/
更改完成之后斷開連接
umount /mnt/disk qemu-nbd --disconnect /dev/nbd0
第二步在主機上編譯鏡像使用的內核
內核編譯 解壓代碼
使用默認平臺編譯配置
make x86_64_defconfig
將內核日志文件系統和訪問鏡像磁盤需要的模塊編入內核
CONFIG_XFS_FS = y CONFIG_EXT4_FS = y
內核調試信息和腳本打開
CONFIG_DEBUG_INFO=y CONFIG_GDB_SCRIPTS=y
使用軟時鐘中斷,避免調試是被中斷
CONFIG_HYPERVISOR_GUEST=y CONFIG_PARAVIRT=y CONFIG_PARAVIRT_CLOCK=y
如果需要調試函數的輸入輸出參數或者臨時變量,可以修改函數的編譯優化選項
__attribute__((optimize("O0")))
第三步qemu配置使用主機上準備好的內核
主機上編譯kernel并安裝模塊
make modules_install
生成啟動需要的initrd
dracut -f ./initrd_4.19.90.img 4.19.90
在qemu啟動配置上加入內核和ramdisk路徑,kernel的啟動參數
-kernel ./kernel/arch/x86/boot/bzImage \
-append 'root=/dev/sda3 crashkernel=512M console=ttyS0 nokaslr' \
-initrd ./initrd_4.19.90.img
在qemu啟動配置上加入調試指令 -S -s
第四步 gdb啟動內核
啟動虛擬機,這時虛擬機cpu是freeze狀態
使用gdb連接到qemu調試端口,并且在內核啟動位置等待
gdb ./kernel/vmlinux
(gdb) target remote: 1234
(gdb) hbreak start_kernel
(gdb) continue
為需要調試的函數加上斷點
(gdb) break ima_load_digest_lists
繼續運行到斷點,并開始調試
(gdb) continue
第四步 gdb調試內核模塊
將需要調試的內核模塊加入到內核中進行編譯, 以紫金設備驅動為例
drivers/infiniband/Kconfig 加入 source drivers/infiniband/hw/zijin_roce/Kconfig
drivers/infiniband/hw/Makefile 加入 obj-$(CONFIG_INFINIBAND_ZIJIN_ROCE) += zijin_roce/
加載到kernel之后, gdb中load新的symboles
(gdb) lx-symbols
(gdb) break zijin_ib_create_cq
如果必須調試第一次驅動加載, 在init流程必走的路徑上加入斷點,再reload symbols, 然后繼續調試
(gdb) break local_pci_probe
(gdb) lx-symbols
至此內核在虛機機鏡像中進行內核或者模塊的調試步驟介紹完畢。
GDB調試技巧
多進程調試
切換fork之后跟蹤父進程還是子進程
(gdb) set follow-fork-mode child
多線程調試
只在特定線程上打斷點
(gdb) break function_name thread n
選擇當前跟蹤的線程
(gdb) thread n
當一個線程中斷時,其他線程繼續運行
(gdb) set non-stop on
其他
gdb打印有默認長度限制,如果要顯示結構體中某些很長的元素
(gdb) set print elements 0
qemu啟動過程會收到多次信號,可以忽略這些信號
(gdb) handle SIGUSR2 nostop noprint