一、实验要求
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用,本实验选取第151号系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验内容
1.实验环境搭建
基于上次实验的linux,配置内核编译选项:
make menuconfig
打开debug相关选项:
关闭KASLR:
重新编译内核:
make -j$(nproc)
制作根⽂件系统:
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
编译成静态链接:
编译安装:
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脚本⽂件放在根⽂件系统跟⽬录下:
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome MyOS!" 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
2.选取系统调用,汇编改写
打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl
,查看要选择进行实验的系统调用。
系统调用 mlockall,函数入口为 __x64_sys_mlockall
。编写程序调用mlockall:
gcc编译(这里采用静态编译)后运行,输出结果:
gcc -o gsn gsn.c -static
mlockall 的功能是将进程使用的部分或者全部的地址空间锁定在物理内存中,防止其被交换到swap空间。
这个系统调用接受一个参数;如果指定 MCL_CURRENT,则仅仅当前已分配的内存会被锁定,之后分配的内存则不会;MCL_FUTURE 则会锁定之后分配的所有内存。使用 MCL_CURRENT|MCL_FUTURE 将已经及将来分配的所有内存锁定在物理内存中。
汇编改写手动触发系统调用:
gcc编译后查看执行结果:
gcc -o gsn_asm gsn_asm.c -static
3.gdb调试与分析
重新打包根文件目录
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 -nographic -append "console=ttyS0"
此时虚拟机会暂停在启动界面:
在另一个terminal中开启gdb调试 gdb vmlinux:
连接进行调试,target remote:1234:
gdb输入命令c,使得虚拟机继续执行:
通过gdb在函数入口做如下断点,然后监听:
在虚拟机中执行 gsn,在gdb界面查看断点分析:
首先断点定位为到/home/linux-5.4.34/mm/mlock:
799:
执行完这个函数,回到了函数堆栈上一层的do_sys_call_64
中 ,接下来要执行的 syscall_return_slowpath
函数要为恢复现场做准备:
继续执行,再次回到了函数堆栈的上一层,entry_SYSCALL_64
,接下来执行的是用于恢复现场的汇编指令,最后随着两个popq出栈指令,恢复了rdi
和rsp
寄存器,系统调用完成
4.分析
1.触发系统调用后,代码执行了/linux-5.4.34/arch/x86/entry/entry_64.S 目录下的ENTRY(entry_SYSCALL_64)入口,然后开始通过swapgs 和压栈动作保存现场:
2.然后跳转到了/linux-5.4.34/arch/x86/entry/common.c
目录下的 do_syscall_64
函数,在ax寄存器中获取到系统调用号,然后去执行系统调用内容:
3.查看entry_syscall_64后续的代码,在完成执行现场的恢复,最后的两个popq出栈指令恢复原 rdi 和 rsp的内容,也就是完成了堆栈的切换。