当前位置:   article > 正文

Linux操作系统分析Lab3:基于VSCode的Linux内核调试环境搭建及start_kernel跟踪分析

linux内核调试环境搭建

1. 安装开发工具
sudo apt install build-essential
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
  • 1
  • 2
  • 3
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
  • 1
  • 2
  • 3
  • 4
  • 5
3. 配置内核选项
make defconfig # Default configuration is based on 'x86_64_defconfig'
make menuconfig
# 打开debug相关选项,按Y选中,按N取消选中
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)
# asve保存,exit退出
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
4. 编译和运行内核
# 编译内核
make -j$(nproc) # nproc gives the number of CPU cores/threads available
# 测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
  • 1
  • 2
  • 3
  • 4
5. 制作根文件系统

电脑加电启动首先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。
本实验借助BusyBox 构建极简内存根文件系统,提供基本的用户态可执行程序。

1) 下载BusyBox源码
cd ~
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
  • 1
  • 2
  • 3
  • 4
2) 配置BusyBox
make menuconfig
# 记得要编译成静态链接,不用动态链接库。
Settings --->
	[*] Build static binary (no shared libs)
  • 1
  • 2
  • 3
  • 4
3) 编译安装
#默认会安装到源码目录下的 _install 目录中
make -j$(nproc) && make install 
  • 1
  • 2
4) 制作内存根文件系统镜像
cd ~
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/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
5) 创建init脚本
touch init
# 将下面的内容写到init文件中
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh
# 给init脚本添加执行权限
chmod +x init
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
6) 测试挂载根文件系统和测试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
  • 1
  • 2
  • 3
  • 4

运行结果如下,可以看到init脚本中的内容“Wellcome ZjlOS!”输出到屏幕上,说明initial脚本正常执行

在这里插入图片描述

6. 跟踪调试Linux内核
# 图形化界面启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
# 命令行下启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
  • 1
  • 2
  • 3
  • 4

另外打开一个终端窗口

cd linux-5.4.34/
gdb vmlinux
(gdb) target remote:1234 # tcp建立连接
(gdb) b start_kernel # 设置断点
c、bt、list、next、step.... # gdb的一些调试命令,
  • 1
  • 2
  • 3
  • 4
  • 5

调试的部分操作如下,调试完毕输入’q’退出调试,然后输入"killall qemu-system-x86_64"关闭qemu进程

在这里插入图片描述

7.VSCode配置调试环境

通过之前的操作可以发现,在命令行下进行gdb的调试不是很方便,因此配置VSCode环境进行Linux内核的调试

配置环境

sudo apt install global
cd linux-5.4.34/
# 借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)
python ./scripts/gen_compile_commands.py
  • 1
  • 2
  • 3
  • 4

安装必要的插件
在这里插入图片描述
编写配置文件.vscode/tasks.json和.vscode/launch.json,具体参考 VSCode调试Linux内核配置文件

在start_kernel函数处设置断点,调试Linux内核,观察start_kernel的执行过程

在这里插入图片描述

可以发现,start_kernel()函数实际上就是在不断的调用其他的函数,来进行一些初始化的工作,主要完成的工作有以下几点:

特定于体系结构的设置
解释命令行参数
初始化数据结构和缓存
查找已知的系统错误
最后创建idle和init进程,调用do_basic_setup函数,进行驱动设置
  • 1
  • 2
  • 3
  • 4
  • 5

此时执行start_kernel()函数的是0号进程,当它来到最后一条命令arch_call_rest_init()时,这个函数又调用了另一个函数rest_init(),rest_init()函数创建kernel_init进程也就是1号进程 和 kthreadd进程也就是2号进程,并执行1号进程和2号进程的处理函数。
在arch_call_rest_init()函数处设置断点进行调试

在这里插入图片描述

在rest_init()函数中,我们主要关心pid = kernel_thread(kernel_init, NULL, CLONE_FS)和pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)这两行代码,它们分别创建了1号进程和2号进程。

首先调试跟踪pid = kernel_thread(kernel_init, NULL, CLONE_FS),单步调试后进入kernel_thread()函数
在这里插入图片描述

实际上它又调用了_do_fork()函数,调试进入该函数,_do_fork函数主要完成了调用copy_process()复制父进程、获得pid、调用wake_up_new_task将子进程加入就绪队列等待调度执行等,_do_fork函数最终返回进程的pid,即1

在这里插入图片描述

当kernel_thread(kernel_init)成功返回后,就会调用kernel_init函数,其实这时候1号进程已经产生了,接下来进入kernel_init函数,看看它做了什么
在这里插入图片描述

kernel_init_freeable函数中就会做各种外设驱动的初始化;最主要的工作就是通过execve执行/init可以执行文件。至此,1号进程已创建完毕,同时也执行了init可执行文件

接下来看rest_init函数中的另一条命令pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),它也是调用了_do_fork函数,并返回pid2,然后由2号进程执行kthreadd函数,进入kthreadd函数

在这里插入图片描述

在这段代码中,2号进程首先进行了一些初始化工作,然后就进入了for循环,在循环中,首先设置当前的进程的状态是TASK_INTERRUPTIBLE是可以中断的,然后查看kthread_create_list链表是不是空,如果是空则就调度出去,让出cpu,接下来把进程状态设置为TASK_RUNNING运行时,不断检查进程创建列表,如果不是空,则从链表中取出一个,然后调用kthread_create去创建一个内核线程。

至此2号进程也已创建完毕,并完成了它的工作

在rest_init函数的末尾,会执行cpu_startup_entry函数,此时0号进程退化为idle进程,只有没有进程执行时,cpu才会调度idle进程

在这里插入图片描述

至此,对于start_kernel的简单分析已完成,上述内容可以总结如下

start_kernel 相当于内核的 main 函数,内核的生命周期就是从执行这个函数的第一条语句开始的,直到最后一个函数 reset_init(),内核将不再从这个函数中返回,而是陷入这个函数里面的一个 while(1) 死循环,这个死循环被作为 idle 进程,也就是 0 号进程。其中在reset_init()中,0号进程会创建1号进程和2号进程,并完成各自的工作。

参考资料:
start_kernel启动函数——简版

Linux0号进程,1号进程,2号进程

作者:518

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/239899?site
推荐阅读
相关标签
  

闽ICP备14008679号