前言:ubuntu 22.04 许多bug.
环境:ubuntu 18.04
1.安装开发工具
sudo apt install build-essential
sudo apt install qemu
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
2.下载内核源码
sudo apt install axel
axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar
cd linux-5.4.34
3.配置内核选项
make defconfig
make menuconfig
进入界面后
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging
[*] Kernel debugging
# 关闭KASLR(随机地址),否则会导致打断点失败。这样调试器就可以跟踪到源代码,之所以设置随机地址为了防止黑客攻击:
Processor type and features ---->
[ ] Randomize the address of the kernel image (KASLR)
4.编译和运行内核
make -j$(nproc)
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
编译成功后测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
5.制作内存跟文件系统
电脑加电启动首先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。
我们这里为了简化实验环境,仅制作内存根文件系统。这里借助BusyBox 构建极简内存根文件系统,提供基本的用户态可执行程序。
axel -n 20 https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar -jxvf busybox-1.36.0.tar.bz2
cd busybox-1.36.0
老版本busybox-1.31.1可能会出现错误,这里直接安装最新版本的busybox。
make menuconfig
Settings ---> [*] Build static binary (no shared libs)
编译成静态链接,不用动态链接库
然后编译安装,默认会安装到源码目录下的 _install 目录中。
make -j$(nproc) && make install
制作内存根文件系统镜像:
cd ~
mkdir rootfs
cd rootfs
cp ~/busybox-1.36.0/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
完成之后,将init脚本文件放在根文件系统跟目录下;init内容如下:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Welcome MingOS!"
echo "--------------------"
cd home
/bin/sh
对脚本进行添加可执行权限:
chmod +x init
打包成内存根文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ~/rootfs.cpio.gz
测试挂载根文件系统,看内核启动完成后是否执行init脚本
qemu-system-x86_64 -kernel ~/linux-5.4.34/arch/x86/boot/bzImage -initrd ~/rootfs.cpio.gz
结果如下:
gdb调试:
6.配置VScode调试linux内核
安装GNU Global
sudo apt install global
安装C++相关插件以及GDB DeBug
由于 Linux 内核高度定制化,所以没有办法直接通过配置 includePath 等让 Intellisense 正常提示,这里借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)。在Linux源代码目录下直接运行如下命令就可以生成 compile_commands.json 了。
python ./scripts/gen_compile_commands.py
在linux-5.4.34文件夹中新建一个.vscode文件夹,将配置文件放入:
其中tasks.json文件中的路径要对应自己的文件配置路径
settings.json中的disable的d要改成小写:
vscode的调试环境就配置好了。
7.跟踪linux内核启动过程
Linux内核的起点是start_kernel函数,在start_kernel处打断点;启动调试:
通过单步跳过并跟踪分析, set_task_stack_end_magic将0号进程init_task被设为整个系统的初始进程,在此之后的所有进程都会由0号进程创建,继续单步调试发现会进行各种初始化的操作,包括初始化各种重要的数据结构、驱动程序、中断处理程序等。在这个阶段,内核会建立好一些必要的核心数据结构,如物理内存管理器、虚拟内存管理器,以及进程调度器等。最后到函数的的末尾,由arch_call_rest_init()函数调用rest_init()函数。
再在rest_init处打断点:
进入函数之后看到了kernel_init,即1号进程,它是所有用户进程的祖先,由kernel_thread函数创建,kernel_thread函数创建一个新的内核线程(实际linux不支持线程所以是一个内核进程)。
进入kernel_thread函数查看:
该函数通过_do_fork函数创建进程。所以可以看到1号进程和2号进程最终都是通过_do_fork创建的,用户态通过系统调用fork创建一个进程最终也是通过_do_fork来完成的。