一、实验内容
1.学号末尾为94,故采用94号系统调用
2.通过汇编指令触发系统调用
3.通过gdb跟踪该系统调用的内核处理过程
4.阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及关注系统调用过程中内核堆栈状态的变化
二、环境准备
安装开发工具
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 # 此时应该不能正常运行
制作根文件系统
#下载 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 TestOS!" 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
打印并执行成功,内存根文件系统制作完毕
三:查看系统调用,编写调用汇编代码
首先查看要选择进行实验的系统调用--94号系统调用
可知94号系统调用为lchown。对应的内核处理函数为__x64_sys_lchown。作用:修改文件所有者和组别。触发这一系统调用,我们需要写一个小程序。使用内联汇编小程序new_test.c如下:
int main() { asm volatile( "movl $0x5E,%eax\n\t" //使⽤EAX传递系统调⽤号94 "syscall\n\t" //触发系统调⽤ ); return 0; }
由于我们搭建的系统不支持动态链接,因此这里我们在使用gcc编译时要用-static静态编译参数)。
然后将生成的可执行文件文件拷贝至rootfs/home文件夹下,最后,由于我们对系统做了修改,需要重新打包成内存根文件系统镜像。因此要再次使用命令:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
打包完毕后,我们的构建系统的根目录下应该已经有new_test这一可执行文件了,用qemu运行,检查一下。如下图所示:
接下来我们首先需要检查是否成功触发了94号系统调用,然后逐步跟踪了解内核的处理过程、系统调用入口的保存现场、恢复现场和系统调用返回等流程
四:gdb调试
1. 纯命令行启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
运行gdb,进入调试环境
gdb捕获到了断点 —— __x64_sys_lchown函数,94号系统调用触发成功。如下图:
gdb单步调试
四:结果分析
内核的初始化完成了以下的函数调用过程:
start_kernel > trap_init > cpu_init > syscall_init, 并在syscall_init中完成将entry_SYSCALL_64的入口地址存入msr寄存器中,我们用gdb来调试验证以下,分别给这几个函数打上断点,检测断点捕获情况和执行顺序。
汇编指令syscall 触发系统调用,通过MSR寄存器找到了中断函数入口
系统调用的保存现场和恢复现场
entry_SYSCALL_64是系统调用的入口点,它完成了保存现场,调用对应的内核处理函数、恢复现场、系统调用返回等工作。
在linux5.4.34/arch/x86/kernel/entry中找到enrty_64.s,它是enrty_SYSCALL_64的所在
结合代码
可以看到,它没有使用sava_all命令保存现场,而使用了特殊的swapgs,来快照式地保存现场,加快了系统调用的速度。
swapgs之后push了一堆寄存器,将其入内核堆栈,然后转换成pt_regs结构体......如下图所示:
然后,执行了do_syscall_64这一内核函数
跳转获得系统调用号,执行系统调用的内容
调用结束,恢复到用户态执行syscall_return_slowpath
函数要为恢复现场做准备。
swapgs——恢复现场