linux操作系统实验三-搭建vscode调试环境,进行start_kernal调试

发布时间 2023-03-23 20:00:09作者: skadfj

实验三:debug mykernel

 

首先安装开发工具

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

这里我使用的是wsl进行实验,由于没有图形界面,只能使用-nographic启动(后台启动方式)

制作根文件系统

我们这里为了简化实验环境,仅制作内存根文件系统。这里借助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

这里我遇到一个问题

从报错信息,和stime调用有关,原因是glibc2.31开始不在包含stime

在之后的busybox更新中解决了这个问题

因此我们可以选择使用补丁,或者安装更加新版本的busybox,这里另找了一个更加新的stable版本1.33.2

重新安装解压、make menuconfig、编译,如成功则结果如下

执行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 MengningOS!"

echo "--------------------"

cd home

/bin/sh

 

给init脚本添加可执行权限

chmod +x init

 

打包成内存根文件系统镜像

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

测试挂载根文件系统,看内核启动完成后是否执行init脚本

执行目录下应有linux文件夹和文件镜像rootfs.cpio.gz

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

这里使用wsl进行实验,由于没有图形界面,只能使用-nographic后台启动,因此不便观察init执行情况。

 

跟踪调试Linux内核

使用gdb调试

sudo apt install gdb #如未安装,首先安装gdb

 

使用gdb跟踪调试内核,加两个参数,

(1)-s,在TCP 1234端口上创建了一个gdb-server。可以另外打开一个窗口,用gdb把带有符号表的内核镜像vmlinux加载进来,然后连接gdb server,设置断点跟踪内核。

(2)-S代表启动时暂停虚拟机,等待 gdb 执行 continue指令(可以简写为c)。

 

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s

# 纯命令行下启动虚拟机(不弹窗)

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

 

再打开一个窗口,启动gdb,把内核符号表加载进来,建立连接:

cd linux-5.4.34/

gdb vmlinux

(gdb) target remote:1234

(gdb) b start_kernel

c、bt、list、next、step....

 

 

配置VSCode调试Linux内核

使用wsl配置调试环境时与虚拟机环境下有所不同

需要注意的是,使用wsl时,如果简单的使用code . 命令打开wsl中的文件的话可能无法正常配置环境

首先在vscode中安装wsl插件

 

点击左下角连接wsl,成功连接可以看到左下角如图所示

 

接着打开对应的文件夹,此处打开Linux安装目录即可,并进行配置文件的设置,具体可以参见孟宁老师给出的配置文件

https://github.com/mengning/linuxkernel/tree/master/src/kerneldebuging

这里我的launch.json 、settings.json、 tasks.json是直接照搬的,c_cpp_properties.json是在运行调试时自动生成的

 

接着就可以进行调试了,首先打上断点,在init/main.c的start_kernal函数处打上断点。

在wsl中命令行启动qumu

在vscode中点击运行=>启动调试,或者点击F5,即可开始调试

 

理论上也可以不在wsl上先手动启动qemu,只需在vscode运行调试即可,因为在tasks.json中已经配置了对应命令行,但实测需要先在wsl中手动启动才能正常调试。

 

程序来到之前的断点处,通过上方的小框可以进行单步调试

  

开始调试过程:

孟老师ppt中给出了linux启动的过程

 

我们就使用调试方法取跟踪这个过程:

 

0号进程是系统第一个进程,此处init_task作为0号进程被创建。

start_kernel中内核会建立好一些必要的核心数据结构,如物理内存管理器、虚拟内存管理器同时进行一些初始化操作:初始化各种重要的数据结构中断处理程序、驱动等,例如下图是执行到page_aloc_init函数

 

来到start_kernal末尾

 

 

依次进入,就找到了由kernal_thread fork出的1号进程

 

进入,证实了ppt中的说法,进入do_fork

 

从函数的签名可以看出该函数执行fork过程,复制父进程的相关数据用于创建子进程

回到rest_init

 

Kernel_thread中将kernal_init的地址传入,在新的thread中执行

 

在函数中会尝试执行run_init_process

 

 

其中进一步调用do_execve

execve属于exec函数之一,通常在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序

 

do_execve经过一系列函数调用链,完成进程的初始化

回到rest_init

 

可以看到,同样是由kernel_thread(0号进程)创建2号进程,并由2号进程执行kthreadd函数

 

该线程会不断检查列表中是否有需要创建的内核线程并根据需要进行创建。

 

至此,我们已追踪了linux启动过程中0号、1号、2号进程的创建过程,以及各自的大致作用。