深入理解系统调用
一.实验要求
- 找到一个系统调用, 系统调用号为学号最后两位相同 的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场, 恢复现场,和系统调用返回, 以及重点关注系统调用过程中内核堆栈状态的变化
二.实验过程
- 实验准备
-
配置内核选项
•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)
配置完成后, 将源码重新编译, 然后运行qemu:
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
-
制作根文件系统
-
下载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
-
配置
配置编译成静态链接库
•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 MengningOS!" 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
可以看到我们的脚本 已经顺利的被打包到根文件系统中运行了
-
-
调试75号系统调用
我的学号后两位是75, 查看系统调用表
arch/x86/syscalls/syscall_64.tbl
该系统调用的功能 是将数据刷新到磁盘上面
fdatasync:
Linux的文件存储的是将
数据
和文件信息inode
分别存储的,在inode中保存了如:名称、文件大小size、修改时间、访问时间等信息我们称作metadata元数据
。数据和文件信息inode在物理上是分开存储的,innode修改时需要一次磁盘IO的。
-
编写一个简单的代码来触发fdatasync系统调用
int main() { asm volatile( "movl $0x4B,%eax " //使用EAX传递系统调号75 "syscall " //触发系统调用; ); return 0; }
编译这个代码生成可执行文件
fdatasync_test
(一定要加上-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 -nographic -append "console=ttyS0"
-
调试
- 启动系统后会暂时停止, 这时我们新开一个终端, 输入命令:
gdb vmlinux target remote:1234
- 接着我们输入
c
, 虚拟机会继续运行, 完成初始化并进入初始界面
因为fdatasync对应的函数入口是
_x64_sys_fdatasync
, 所以我们在这个打下断点:-
qemu运行后, 我们在qemu的终端的home目录下, 运行我们之前编译好的fdatasync_test可执行文件, 此时虚拟机会卡主, 我们在调试终端查看调试信息
可以看到gdb成功的捕捉到了断点 -
接着我们进入单步调试, 看看系统调用做了哪些工作
系统首先执行的 syscall_return_slowpath 函数, 准备恢复现场
接下来, 执行用于恢复现场的汇编指令
现场恢复完成
三.总结
- syscall首先通过MSR寄存器查找中断向量表, 找到中断函数的入口位置, 随后执行swags进行压栈和保存现场的动作
- 接着执行do_syscall_64函数, 在ax寄存器中获得系统调用号去执行系统调用
- 接着调用我们的75号系统调用
- 系统调用完结束后执行yscall_return_slowpath(regs), 准备恢复现场
- 恢复现场工作