1.配置VSCode
为了防止通过虚拟机下载较慢的问题,选择通过下载vscode安装包,通过finalshell上传虚拟机。
执行以下安装命令进行安装
sudo apt install ./code_1.76.2-1678817801_amd64.deb
还要安装VSCode插件C/C++Intellisense和C/C++Themes。由于插件C/C++Intellisense需要GUN Global,还需要如下命令安装GUN Global。
sudo apt install global
再安装以上插件
2.安装开发工具
sudo apt install build-essential
sudo apt install qemu # install QEMU#作为一个虚拟机
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
#编译内核所需的依赖---一些基础库
3.下载内核源代码
wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch
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
4.配置内核选项
make defconfig # Default configuration is based on 'x86_64_defconfig'
make menuconfig# 打开debug相关选项
打开图片如下图所示:
更改以下配置:
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)
5.编译和运行内核
make -j$(nproc) # nproc gives the number of CPU cores/threads available
# 测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
6.制作根文件系统
电脑加电启动首先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。
我们这里为了简化实验环境,仅制作内存根文件系统。这里借助BusyBox 构建极简内存根文件系统,提供基本的用户态可执行程序。
首先从https://www.busybox.net下载 busybox源代码解压,解压完成后,跟内核一样先配置编译,并安装
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
make menuconfig
#记得要编译成静态链接,不用动态链接库。
Settings --->
[*] Build static binary (no shared libs)静态编译
然后编译安装,默认会安装到源码目录下的 _install 目录中。
make -j$(nproc) && make install
然后制作内存根文件系统镜像,大致过程如下:
mkdir rootfs
cd rootfs
cp ../busybox-1.31.1/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
接下来准备init脚本文件放在根文件系统跟目录下(rootfs/init),添加如下内容到init文件:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome TinyyOS!"
echo "--------------------"
cd home
/bin/sh
接下来对init脚本添加可执行权限:
chmod +x init
打包成内存根文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
测试挂载根文件系统,看内核启动完成后是否执行init脚本
qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd rootfs.cpio.gz
7.调式Linux内核
由于 Linux 内核高度定制化,所以没有办法直接通过配置 includePath 等让 Intellisense 正常提示,这里借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)。在Linux源代码目录下直接运行如下命令就可以生成 compile_commands.json 了。 准备在vscode中打开前面准备好的linux-5.4.34文件夹,需要做两件事情:
(1)命令行输入:
python ./scripts/gen_compile_commands.py
(2)需要在linux-5.4.34文件夹下在此之前直接先手动放好配置文件:新建一个.vscode文件夹,把配置文件里的文件全部放入.vscode文件夹内
修改task.json中command的值使得与当前文件路径一致
8.跟踪Linux内核的启动过程
Linux内核的起点是start_kernel函数,因此先在start_kernel处打断点(点击运行和调试图标,在断点里增加函数断点:start_kernel)
启动调试,程序在断点处暂停,从start_kernel开始进行跟踪分析。
点击单点跳过,这里我们看到了0号进程init_task被设置整个系统的第一个进程(0进程是手工创建的,其他进程都是0号进程创建的)在内核引导时,init_task会被创建并启动,它是所有其他进程的起点。继续跳过,start_kernel会继续执行一些初始化操作。
在start_kernel()函数末尾,arch_call_rest_init()函数体内为rest_init()函数,因此设置一个rest_init函数断点,进入rest_init函数体内,该函数由0号进程执行。
kernel_thread函数创建kernel_init,对应1号进程,是所有用户进程的祖先。接着kernel_thread函数创建kthreadd,对应2号进程,是所有内核进程的祖先。进入kernel_thread函数查看,该函数通过_do_fork函数创建进程。所以可以看到1号进程和2号进程最终都是通过_do_fork创建的,用户态通过系统调用fork创建一个进程最终也是通过_do_fork来完成的。
do_fork原理如下:
接下来在新线程(进程)中运行kernel_init()函数, 点开kernel_init()函数,可以看出,在这个里面调用了run_init_process函数,而run_init_process函数则使用了do_execve函数。do_execve加载可执行文件,运行init程序,执行exec系统调用,这样1号进程就完成了用户态初始化。
查看kthreadd函数定义。2号进程创建并完成初始化。