这一章一起动手做一下内核的调试。
环境这块折腾了很久,之前都是Ubuntu14的,现在这个需要更新到Ubuntu22才可以~,大家动手做实验的时候注意啦!
一、搭建QEMU+Debian实验平台
一、安装工具
1.1、Linux主机安装需要的工具包
sudo apt-get install qemu libncurses5-dev gcc-aarch64-linux-gnu build-essential git bison flex libssl-dev qemu-system-arm
1.2、安装完成后,检查QEMU版本
qemu-system-aarch64 --version
二、下载源码
下载runninglinuxkernel_5.0源码
git clone https://gitee.com/zhang-ge/runninglinuxkernel_5.0.git
二、运行实验平台
一、编译内核
cd runninglinuxkernel_5.0
如果在编译内核之前想进入menuconfig界面来配置内核:
git checkout rlk_5.0
./run_debian_arm64.sh menuconfig
编译内核。
$ ./run_debian_arm64.sh build_kernel
执行上述脚本需要几十分钟,依赖于主机的计算能力。接着,编译根文件系统。
$ sudo ./run_debian_arm64.sh build_rootfs
编译根文件系统需要管理员权限,而编译内核则不需要。
执行完成后会生成一个名为rootfs_arm64.ext4的根文件系统。
二、运行系统
运行刚才编译好的ARM64版本的Linux系统。
运行run_debian_arm64.sh脚本,输入run参数即可。
$./run_debian_arm64.sh run
系统登录名: benshushu 密码:123
切换到root用户:su root
三、实验平台初体验
一、在线安装软件包
QEMU虚拟机可以通过VirtIO-Net技术来生成一个虚拟的网卡,通过NAT网络桥接技术和主机进行网络共享。使用ifconfig命令来检查网络配置。
但这里我发现ifconfig不起作用,系统返回 bash: ifconfig: command not found 错误说明系统无法找到 ifconfig 命令。这可能是因为最新版本的Linux发行版中已经不推荐使用ifconfig命令了,而应该使用ip命令来替代。
可以尝试使用以下命令来代替ifconfig:
ip addr show
可以看到生成了一个名为enp0s1的网卡设备,分配的IP地址为:10.0.2.15。
提前更改系统时间
root@ubuntu:~# date -s 2020-03-29 #假设最新日期是2020年3月29日Sun Mar 29 00:00:00 UTC 2020
这里需要提前导入新的 GPG 密钥,使用以下命令:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9
通过apt update命令来更新Debian系统的软件仓库。
apt update
使用apt install命令来安装软件包。比如,可以在线安装gcc。
root@ubuntu:~# apt install gcc
二、共享文件夹
在主机和QEMU虚拟机之间共享文件。 主机和QEMU虚拟机可以通过NET_9P技术进行文件共享,这个需要QEMU虚拟机和主机的Linux内核都使能NET_9P的内核模块。
本实验平台已经支持主机和QEMU虚拟机的共享文件,可以通过如下简单方法来测试。
复制一个文件到runninglinuxkernel_5.0/kmodules目录下面。
$ cp test.c runninglinuxkernel_5.0/kmodules
启动QEMU虚拟机之后,首先检查一下/mnt目录是否有test.c文件。
root@ubuntu:/# cd /mnt root@ubuntu:/mnt # ls README test.c
我们在后续的实验中会经常利用这个特性,比如把编译好的内核模块或者内核模块源代码放入QEMU虚拟机。
四、编译加载模块及单步调试内核
一、编译加载内核模块
在本书中,常常需要编译内核模块并放入QEMU虚拟机中以加载内核模块。我们这里提供两种编译内核模块的方法。一种是在主机上交叉编译,然后共享到QEMU虚拟机,另一种方法是在QEMU虚拟机里本地编译。
首先,在QEMU虚拟机中安装必要的软件包。
root@ubuntu: # apt install build-essential
在QEMU虚拟机里编译内核模块时需要指定QEMU虚拟机本地的内核路径,例如BASEINCLUDE变量指向了本地内核路径。
“/lib/modules/$(shell uname -r)/build”是一个链接文件,用来指向具体内核源代码路径,通常是指向已经编译过的内核路径。
BASEINCLUDE ?= /lib/modules/$(shell uname -r)/build
编译内核模块,下面以最简单的hello_world内核模块程序为例。
cd hello_world #进入内核模块代码所在的目录
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
root@ubuntu:/mnt/hello_world# make
make -C /lib/modules/5.0.0+/build M=/mnt/hello_world modules;
make[1]: Entering directory '/usr/src/linux'
CC [M] /mnt/hello_world/test-1.o
LD [M] /mnt/hello_world/test.o
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/hello_world/test.mod.o
LD [M] /mnt/hello_world /test.ko
make[1]: Leaving directory '/usr/src/linux'
root@ubuntu: /mnt/hello_world#
提前编写modules_helloworld.c、Makefile文件
#include <linux/init.h>
#include <linux/module.h>
int hello_init(void)
{
printk("enter hello world!\n");
return 0;
}
void hello_exit(void)
{
printk("exit hello world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
BASEINCLUDE ?= /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := modules_helloworld.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(BASEINCLUDE) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(CURRENT_PATH) clean
编译
运行,这里可能insmod不成功,而insmod和modprobe命令的可执行文件都可以在/sbin目录中找到,这意味着这两个命令应该已经正确安装在系统中了。
根据您之前尝试过的加载模块的命令和报错信息,看起来问题可能是命令使用方式不正确。
使用以下命令加载模块:
/sbin/insmod modules_helloworld.ko
二、单步调试ARM64 Linux内核
Ubuntu提前安装gdb-multiarch
sudo apt install gdb-multiarch
再进入实验平台时,加上启动GDB
./run_debian_arm64.sh run debug
上述脚本会运行如下命令。
-S:表示QEMU虚拟机会冻结CPU,直到在远程的GDB中输入相应控制命令。
-s:表示在1234端口接受GDB的调试连接。
接下来,在另外一个超级终端中启动GDB。
linux@linux-virtual-machine:~$ cd Desktop/work/runninglinuxkernel_5.0/
linux@linux-virtual-machine:~/Desktop/work/runninglinuxkernel_5.0$ gdb-multiarch --tui vmlinux
此时会来到GDB端口:
(gdb) set architecture aarch64 //设置aarch64架构
(gdb) target remote localhost:1234 //通过1234端口远程连接到QEMU虚拟机
(gdb) b start_kernel //在内核的start_kernel处设置断点
(gdb) c
如图所示,GDB开始接管Linux内核运行,并且到断点处暂停,这时即可使用GDB命令来调试内核。
五、总结
总之,这部分实验还是比较轻松的,实现了QEMU+Debian实验平台 的搭建和编译加载模块及单步调试内核。
以后可以基于此来调试Linux,当系统发生段错误或崩溃,都可以使用GDB来排查错误了,是工作中非常实用的技巧!