研究Linux内核,我们可以看看源码,并且把内核代码给跑起来,通过gdb来调试它。下面我们来具体实操看一下Linux内核的编译以及调试方法。
cd /usr/src
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.4.11.tar.xz
tar -xf linux-6.4.11.tar.xz
cd /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=n
CONFIG_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 rootfs
cd 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 sys
cd ..
chmod 777 -R rootfs/
cd rootfs/
touch init
#!/bin/sh
dmesg -n 1
mount -t devtmpfs none /dev
mount -t proc none /proc
mount -t sysfs none /sys
setsid cttyhack /bin/sh
chmod 777 init
find . | 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 vmlinux
target remote :1234
跟gdb调试普通程序一样,我们可以设置一下断点,然后按c键继续运行:
b start_kernel
b rest_init
c
可以看到gdb在start_kernel和rest_init两个函数断点处停住了,按c键后,QMUE虚拟机成功进入了系统。start_kernel是Linux内核启动时C代码开始的地方,研究内核启动过程就可以从start_kernel开始看。