目录
1、安装开发工具
2、下载内核源代码
3、配置内核选项
4、编译和运行内核
5、制作根文件系统
6、配置VSCode调试Linux内核
7、跟踪分析Linux内核的启动过程
sudo apt install build-essential
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
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
内核源码文件如下:
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)
make -j$(nproc) # nproc gives the number of CPU cores/threads available
# 测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
- •电脑加电启动首先由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
#需要到linux-5.4.34文件夹下来创建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命令是对proc和sys进行挂载
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
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 linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
# 改成了:
qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd rootfs.cpio.gz
•下面具体看看如何使用gdb跟踪调试Linux内核。使用gdb跟踪调试内核,加两个参数,一个是-s,在TCP 1234端口上创建了一个gdb-server。可以另外打开一个窗口,用gdb把带有符号表的内核镜像vmlinux加载进来,然后连接gdb server,设置断点跟踪内核。若不想使用1234端口,可以使用-gdb tcp:xxxx来替代-s选项),另一个是-S代表启动时暂停虚拟机,等待 gdb 执行 continue指令(可以简写为c)。
qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
#其中-S意思是Stopped,-s为gdb提供一个调试端口tcp:1234。
# 纯命令行下启动虚拟机
再打开一个窗口,启动gdb,把内核符号表加载进来,建立连接
cd linux-5.4.34/
gdb vmlinux
(gdb) target remote:1234
(gdb) b start_kernel
c、bt、list、next、step....
先下载vscode,并装上以下插件
在vscode中打开前面准备好的linux-5.4.34文件夹前,需进行配置
在linux-5.4.34文件夹下新建一个.vscode文件夹,把配置文件里的文件全部放入.vscode文件夹内:
接下来就可以在vscode里打开linux-5.4.34文件夹:可以看到.vscode配置文件夹已经出现了
因为linux内核的起点是"start_kernel"函数,因此先在start_kernel处打断点,从start_kernel开始进行跟踪分析:
接着一边stepover一边观察start_kernel执行的一些初始化操作,首先第一步会跳转到set_task_stack_end_magic(&init_task); 即创建0号进程init_task进程。
0号进程init_task被设置整个系统的第一个进程(0进程是手工创建的静态进程,其他进程都是0号进程创建的)在内核引导时,init_task会被创建并启动,它是所有其他进程的祖先。
接下来start_kernel依次执行内核各个重要子系统的初始化,比如cpu、驱动程序、中断处理程序、物理内存管理器、虚拟内存管理器等等
最后来到结尾处arch_call_rest_init(),然后接着在此处打上断点,stepinto, 接着调试:
然后进入到res_init(void)函数中,发现rest_init函数调用kernel_thread函数,启动了2个内核线程:根据注释可知,这里要先生成kernel_init,此为进程1 ,它是所有用户进程的祖先
点开kernel_thread函数的定义,发现kernel_thread函数是通过_do_fork函数来创建进程的。
再查看_do_fork()函数,发现_do_fork函数主要完成了调用copy_process()复制父进程、调用wake_up_new_task将子进程加入就绪队列等待调度执行等行为来创建进程。
接下来查看kernel_init()函数:按顺序执行/init可执行文件,完成用户态初始化
在linux内核中默认指定了4个用户空间的程序:/sbin/init
、/etc/init
、/bin/init
、/bin/sh
。调用try_to_run_init_process()
函数启动它们。如果其中的任意一个程序被启动了,则退出。
接下来是2号进程的创建,2号进程是所有内核进程的祖先,kernel_thread创建了2号进程,同时进程执行的函数是kthreadd()。
查看kthreadd()函数,此函数循环设置当前进程的状态为TASK_INTERRUPTIBLE是可以中断的,判断kthread_create_list是不是为空,如果为空则就调度出去,让出cpu,如果不为空,则从链表取出一个,然后调用kthread_create去创建一个内核线程。