深入理解系统调用
Posted waaq
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解系统调用相关的知识,希望对你有一定的参考价值。
一、实验要求
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用 (这里我的系统调用号就是73)
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、环境配置
1 下载内核源代码
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
2 配置内核选项
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)
3 编译和运行内核
make -j$(nproc) # nproc gives the number of CPU cores/threads available # 测试?下内核能不能正常加载运?,因为没有?件系统最终会kernel panic qemu-system-x86_64 -kernel arch/x86/boot/bzImage
4 制作根文件系统
电脑加电启动?先由bootloader加载内核,内核紧接着需要挂载内存根?件系统,其中包含必要的设备驱动和?具,bootloader加载根?件系统到内存中,内核会将其挂载到根?录/下,然后运?根?件系统中init脚本执??些启动任务,最后才挂载真正的磁盘根?件系统。
我们这?为了简化实验环境,仅制作内存根?件系统。这?借助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
然后制作内存根?件系统镜像,?致过程如下:
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
环境配置成功!
三、查看系统调用
系统调?是?种特殊的中断,中断分外部中断(硬件中断)和内部中断(软件中断),内部中断?称为异常(Exception),异常?分为故障(fault)和陷阱(trap)。系统调?就是利?陷阱(trap)这种软件中断?式主动从?户态进?内核态的。
?般来说,从?户态进?内核态是由中断触发的,可能是硬件中断,在?户态进程执?时,硬件中断信号到来,进?内核态,就会执?这个中断对应的中断服务例程。也可能是?户态程序执?过程中,调?了?个系统调?,陷?了内核态,叫作陷阱(trap)(系统调?是特殊的中断)。
系统调?具有以下功能和特性:
把?户从底层的硬件编程中解放出来。操作系统为我们管理硬件,?户态进程不?直接与硬件设备打交道。
极?地提?系统的安全性。如果?户态进程直接与硬件设备打交道,会产?安全隐患,可能引起系统崩溃。
使?户程序具有可移植性。?户程序与具体的硬件已经解耦合并?接?代替了,不会有紧密的关系,便于在不同系统间移植。
我的学号后三位是73,73号系统调用是:flock
flock简介:
在多个进程同时操作同一份文件的过程中,很容易导致文件中的数据混乱,需要锁操作来保证数据的完整性,这里介绍的针对文件的锁,称之为“文件锁”-flock。
flock是建议性锁,不具备强制性。一个进程使用flock将文件锁住,另一个进程可以直接操作正在被锁的文件,修改文件中的数据,原因在于flock只是用于检测文件是否被加锁,针对文件已经被加锁,另一个进程写入数据的情况,内核不会阻止这个进程的写入操作,也就是建议性锁的内核处理策略。
先通过汇编触发系统调用
#include<stdio.h> #include<sys/file.h> #include <fcntl.h> #include <unistd.h> int main(void){ int f; f=open("test.txt",O_WRONLY|O_CREAT); printf("The fd value is : %d ", f); int ret = flock(f,1); printf("The return is : %d ", ret); close(f); return 0; }
通过gcc编译后,利用objdump反编译,查看汇编代码:
gcc -o test test.c -static objdump -S test > test.S
找到flock的汇编代码如下:
之后编写内嵌汇编代码,手动触发系统调用
#include<stdio.h> #include<sys/file.h> # include <fcntl.h> #include <unistd.h> int main(void){ int f; int ret; f=open("test.txt",O_WRONLY|O_CREAT); asm volatile( "movl $0x1, %%esi " "movl $0x3, %%edi " "mov $0x49, %%eax " "syscall " "movq %%rax,%0 " //保存返回值 :"=m"(ret) ); printf("The return is : %d ", ret); close(f); return 0; }
重新编译和并运行:
返回0说明程序可以正常运行,系统调用成功。
GDB调试跟踪
把编译好的testa文件放在rootfs/syscall目录下,重新生成根文件系统:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
使?gdb跟踪调试内核,加两个参数,?个是-s,在TCP 1234端?上创建了?个gdbserver。可以另外打开?个窗?,?gdb把带有符号表的内核镜像vmlinux加载进来,然后连接gdb server,设置断点跟踪内核。若不想使?1234端?,可以使?-gdb tcp:xxxx来替代-s选项),另?个是-S代表启动时暂停虚拟机,等待 gdb 执? continue指令(可以简写为c)。
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
另外再打开一个终端,cd到目录linux-5.4.34,利用下面的命令启动gdb,并建立连接:
gdb vmlinux
target remote:1234
设置好断点后,输入c继续,就会自动调用qemu运行,然后cd到程序所在目录下运行程序,程序将会在所设定的断点位置断开。(如果没断开,可能是最开始编译时,没能按照要求选择debug信息。)
系统调用分析
系统调用的入口在entry_SYSCALL_64(),在arch/x86/entry/syscalls/entry_64.S中可以找到此函数:
swapgs指令通过CPU内部的存储器,将保存现场和恢复现场时的寄存器保存起来,然后将pt_regs中的相关字段保存到内核栈中。
之后系统调用了do_syscall_64
函数do_syscall_64接收系统调用号和pt_regs,根据相关参数执行系统调用,。系统调用完成后执行do_syscall_64 中的 syscall_return_slowpath() ,进行现场恢复操作,然后回用户态。
以上是关于深入理解系统调用的主要内容,如果未能解决你的问题,请参考以下文章