一、实验目标
(1)、找一个系统调用,系统调用号为学号最后2位相同的系统调用
(2)、通过汇编指令触发该系统调用
(3)、通过gdb跟踪该系统调用的内核处理过程
(4)、重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验过程
(1).内核源码下载及编译
采用的linux内核为5.4.34版本:
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 # 不能正常运行
(2).制作根文件系统
相关代码如下:
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 "Welcome TestOS!" echo "-------------------" cd home /bin/sh
给init脚本增加可执行权限
chmod +x init
输入下面代码查看qemu运行情况:
#打包成内存根⽂件系统镜像 (rootfs下运行) 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
发现出现如下错误:cant't run '/etc/init.d/rcS':No such file or directory
这是由于init脚本文件位于windows系统下编写后再复制到linux系统下,编码方式错误。因此采用以下指令:
写入文件的指令: vi init 按i进入编辑模式 将上方的脚本内容复制进去 按esc退出编辑模式 按:然后wq 最后回车保存推出 #给init脚本添加可执行文件 chmod +x init
运行成功,如下图所示:
(3).触发系统调用
本人学号后两位为:16,故通过查看打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl可知16号调用为ioctl,代码入口为:_64_sys_iotcl,
使用下面的代码触发系统调用:
//sys16.c int main() { asm volatile( //使⽤EAX传递系统调⽤号16 "movl $0x10,%eax\n\t" //触发系统调用 "syscall\n\t" ); return 0; }
对该程序进行静态编译
gcc sys16.c -o syscaall16 -static
重新打包内存跟文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
启动虚拟机
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
在linux-5.4.34目录下启动gdb调试
gdb vmlinux target remote:1234 b __64x_sys_msgrcv c
获取16号系统调用的返回信息
使用bt命令查看栈信息
(4).系统调用的保存和恢复现场
syscall指令触发系统调用,通过MSR寄存器找到了中断函数入口,系统调用入口为entry_SYSCALL_64,其中使用了swapgs这一方法来快照式的保存现场,加快了系统调用,随后对一些相关寄存器进行压栈操作。
在do_syscall_64函数中,在ax寄存器中获取到系统调用号所对应的入口,跳转执行。
随后便执行16号系统调用相关内容,调用结束后准备恢复现场
三、总结
从系统调用的整个过程来看,主要有以下几个阶段:
(1)用户态程序,发生syscall,触发系统调用;
(2)进入内核态,完成内核初始化后,调用entry_SYSCALL_64 ()
(3)完成现场的保存,将关键寄存器压栈,并从CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,更改CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝ ,调用do_syscall_64()
(4)do_syscall_64()函数中得到系统调用号,调用相关的函数gettimeofday()
(5)调用结束后,保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复
(6)系统调用返回,回到用户态程序