深入理解系统调用

一、实验要求

  • 找一个系统调用,系统调用号为学号最后2位相同的系统调用
  • 通过汇编指令触发该系统调用
  • 通过gdb跟踪该系统调用的内核处理过程
  • 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

二、配置实验环境

步骤一、下载内核和开发工具

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

步骤二、解压Linux内核,并配置

#解压
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 #shift+y确定勾选
        [*] 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  #  此时应该不能正常运行

成功后 :

步骤四、安装并编译busybox制作根文件系统

cd ..
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

步骤五、制作根文件系统

cd ..
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脚本文件

touch init
vim 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

将init文件放在rootfs目录下,并授予其运行权限

chmod +x init

步骤七、打包系统镜像,测试文件系统

#打包成内存根⽂件系统镜像 
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../ rootfs.cpio.gz 
#测试挂载根⽂件系统,看内核启动完成后是否执⾏init脚本 
cd ..
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

出现以下界面,表明init脚本已经运行了,到这里环境已经搭建完成。

 

 三、系统调用实验

我的学号位数是08,在64位调用表里可以查到对应的系统调用函数是__x64_sys_lseek

触发系统调用的test.c程序如下:

#include <stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[]) 
{ 
 int fd;
//fd= creat(argv[1],0755);
//if (fd<0)
//    printf("error!");

asm volatile( 
 "movl %1,%%ebx\n\t" //系统调⽤传递第⼀个参数使⽤EBX寄存器,为argv[1]
 "movl $0755,%%ecx\n\t"//系统调⽤传递第二个参数使⽤ECX寄存器,为0755
 "movl $0x8,%%eax\n\t"//使⽤%eax传递系统调⽤号8,⽤16进制为0x8
 "int $0x80\n\t" //触发系统调⽤
 "movl %%eax,%0\n\t" //通过EAX寄存器返回系统调⽤值 
 :"=m"(fd) 
 :"b"(argv[1])
 ); 
printf("%d\n",fd);
fclose(fd);
 return 0; 
}

在gdb中加断点__ia32_sys_creat,运行test.c编译成的可执行程序test,在断点处停止,观察堆栈情况。

"int $0x80\n\t"指令触发系统调用后,先执行entry_INT80_compat函数,该函数对现场进行保存,之后调用do_int80_syscall_32函数,由用户态切换至内核态,再调用do_syscall_32_irqs_on函数,在其内调用__ia32_sys_creat函数完成creat系统调用的功能,之后切换回用户态,entry_INT80_compat函数恢复现场。

 运行test 2.txt命令成功创建了2.txt文件。

       Linux内核中⼤约定义了四五百个系统调⽤,这时内核如何知道⽤户态进程希望调⽤的是哪个系统调⽤呢?内核通过给每个系统调⽤⼀个编号来区分,即系统调⽤号。内核实现了很多不同的系统调⽤,⽤户态进程必须指明需要执⾏哪个系统调⽤,这需要使⽤EAX寄存器传递⼀个名为系统调⽤号的参数。除了系统调⽤号外,系统调⽤也可能需要传递参数,在32位x86体系结构下普通的函数调⽤是通过将参数压栈的⽅式传递的。系统调⽤从⽤户态切换到内核态,在⽤户态和内核态这两种执⾏模式下使⽤的是不同的堆栈,即进程的⽤户态堆栈和进程的内核态堆栈,传递参数⽅法⽆法通过参数压栈的⽅式,⽽是通过寄存器传递参数的⽅式。寄存器传递参数的个数是有限制的,⽽且每个参数的⻓度不能超过寄存器的⻓度,32位x86体系结构下寄存器的⻓度最⼤32位。除了EAX⽤于传递系统调⽤号外,参数按顺序赋值给EBX、ECX、EDX、ESI、EDI、EBP,参数的个数不能超过6个,即上述6个寄存器。如果超过6个就把某⼀个寄存器作为指针,指向内存,就可以通过内存来传递更多的参数。以上就是32位x86体系结构下系统调⽤的参数传递⽅式。
  由于压栈的⽅式需要读写内存,函数调⽤速度较慢,64位x86体系结构下普通的函数调⽤和系统调⽤都是通过寄存器传递参数,RDI、RSI、RDX、RCX、R8、R9这6个寄存器⽤作函数/系统调⽤参数传递,依次对应第 1 参数到第 6 个参数。

 

posted on 2020-05-27 17:04  陈陈陈7  阅读(259)  评论(0)    收藏  举报

导航