一、实验内容
1.学号末尾为14,故采用14号系统调用
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 "Welcome to SSE_314 OS!" 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
三、查看系统调用表和汇编改写
打开 linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,查看要选择进行实验的系统调用。
找到进行实验的系统调用--14号系统调用
14号系统调用为 rt_sigprocmask。对应的内核处理函数为 __x64_sys_rt_sigprocmask。作用:设定对信号屏蔽集内的信号的处理方式(阻塞或不阻塞)。使用内联汇编小程序new_test.c如下:
int main() { asm volatile( "movl $0x0E,%eax " //使⽤EAX传递系统调⽤号14 "syscall " //触发系统调⽤ ); return 0; }
由于我们搭建的系统不支持动态链接,因此这里我们在使用gcc编译时要用-static静态编译参数):gcc -o test test.c -static
然后将生成的可执行文件文件拷贝至rootfs/home文件夹下
由于我们对系统做了修改,需要重新打包成内存根文件系统镜像。因此要再次使用命令:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
打包完毕后,我们的构建系统的根目录下应该已经有test这一可执行文件了,用qemu运行,检查一下。如下图所示:
接下来我们首先需要检查是否成功触发了14号系统调用,然后逐步跟踪了解内核的处理过程、系统调用入口的保存现场、恢复现场和系统调用返回等流程
四、gdb调试
纯命令行下启动虚拟机
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捕获到了断点 : __x64_sys_rt_sigprocmask 函数,14号系统调用触发成功。如下图
gdb输入命令c,使得虚拟机继续执行,到初始界面
在虚拟机中执行 ./test 会卡住,在gdb界面查看断点分析,
使用 l 命令查看代码情况, n 命令单步执行, step 命令进入函数内部 bt查看堆栈
查看此时堆栈情况,有4层
第一层 __x64_sys_rt_sigprocmask 系统调用函数所在
第二层 do_syscall_64 获取系统调用号, 前往系统调用函数
第三层 entry_SYSCALL_64 () 中断入口,做保存线程工作,调用 do_syscall_64
gdb单步调试过程
执行完这个函数,发现回到了函数堆栈上一层的 do_syscall_64中 接下来要执行的 syscall_return_slowpath(regs) 函数要为恢复现场做准备。
继续执行,发现再次回到了函数堆栈的上一层 entry_SYSCALL_64 () 接下来执行的是用于恢复现场的汇编指令
最后伴随着两个 pop 指令 恢复了 rdi 和 rsp 寄存器。系统调用完成。
五、总结
整理一下整个系统调用的过程:
1.汇编指令syscall 触发系统调用,通过MSR寄存器找到了中断函数入口,此时,代码执行到
/home/sy/linux_lesson/linux-5.4.34/arch/x86/entry/entry_64.S 目录下的 ENTRY(entry_SYSCALL_64) 入口 后开始通过 swapgs 和 压栈动作 保存现场
ENTRY(entry_SYSCALL_64) 是X64系统调用入口
swapgs 是快照保存
call do_syscall_64 /* returns with IRQs disabled */ 是中断向量表查找系统调用号
2.然后跳转到了 home/sy/linux_lesson/linux-5.4.34/arch/x86/entry/common.c 中的 do_syscall_64 函数 在ax寄存器中获取到系统调用号,然后去执行系统调用内容
regs->ax = x32_sys_call_table[nr](regs) ;// 从sys_call_table 获得系统调用号
3.然后程序跳转到 linux-5.4.34/rootfs/home/test.c函数 ,开始执行
4.函数执行完后回到步骤3中的 syscall_return_slowpath(regs) 准备进行现场恢复操作,
5.接着程序再次回到 arch/x86/entry/entry_64.S 执行现场的恢复,最后两句,完成了堆栈的切换
popq %rdi
popq %rsp