深入理解系统调用

作业要求

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

实验步骤

一、环境准备

1、安装开发环境

sudo apt install build-essential
sudo apt install qemu 
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
sudo apt install axel

2、下载内核源码

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

3、配置内核选项

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)

4、编译和运行内核

 make -j$(nproc)
//测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage //此时应该无法正常运行

5、制作根文件系统

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

6、制作内存根文件系统镜像

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/

7、准备init脚本,放在rootfs文件夹下,添加如下内容到该文件。

#!/bin/sh
mount -t proc none /proc mount -t sysfs none /sys
echo "Wellcome MengningOS!" echo "--------------------"
cd home
/bin/sh

8、给init脚本添加可执行权限

chmod +x init

9、打包成内存根文件系统镜像

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

10、测试挂在根文件系统,看内核启动完成后是否执行init脚本

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

运行截图如下:

注意:按照上述步骤操作,linux-5.4.34文件夹、rootfs文件夹、rootfs.cpio.gz应在同一目录下。

二、找到要求的系统调用

本人学号尾号为31号。打开syscall_64.tbl,查看要选择进行实验的系统调用。

31号系统调用为shmctl,函数入口为__x64_sys_shmctl

该系统该调用用来控制共享内存。对与共享存储区关联的各种参数进行操作,从而对共享存储区进行控制。

调用该函数使用头文件:

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/shm.h>

参数定义:

int shmctl(id,cmd,buf)

int id,cmd;

struct shmid_ds * buf;

其中:调用成功返回0,否则返回-1。id为被共享存储区的标识符。cmd规定操作的类型。规定如下:

  • IPC_STAT:返回包含在指定的shmid相关数据结构中的状态信息,并且把它放置在用户存储区中的*but指针所指的数据结构中。执行此命令的进程必须有读取允许权。
  • IPC_SET:对于指定的shmid,为它设置有效用户和小组标识和操作存取权。
  • IPC_RMID:删除指定的shmid以及与它相关的共享存储区的数据结构。
  • SHM_LOCK:在内存中锁定指定的共享存储区,必须是超级用户才可以进行此项操作。
  • Buf是一个用户级数据结构地址。
shmid_ds

{struct ipc_perm shm_perm; /*允许权结构*/

int shm_segsz; /*段大小*/

int padl; /*由系统使用;*/

ushort shm_lpid; /*最后操作的进程id;*/

ushort shm_cpid; /*创建者的进程id;*/

ushort shm_nattch; /*当前附界数;*/

short pad2; /*由系统使用;*/

time_t shm_atime; /*最后附接时间*/

time_t shm_dtime; /*最后段接时间*/

time_t shm_ctime; /*最后修改时间*/

}

三、通过汇编触发系统调用

编写C语言文件执行系统调用(父子进程通信共享内存应用)

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <error.h>

#define SIZE 1024

int main()

{

    int shmid ;

    char *shmaddr ;

    struct shmid_ds buf ;

    int flag = 0 ;

    int pid ;

 

    shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;

    if ( shmid < 0 )

    {

            perror("get shm  ipc_id error") ;

            return -1 ;

    }

    pid = fork() ;

    if ( pid == 0 )

    {

        shmaddr = (char *)shmat( shmid, NULL, 0 ) ;

        if ( (int)shmaddr == -1 )

        {

            perror("shmat addr error") ;

            return -1 ;

 

        }

        strcpy( shmaddr, "Hi, I am child process!\n") ;

        shmdt( shmaddr ) ;

        return  0;

    } else if ( pid > 0) {

        sleep(3 ) ;

        flag = shmctl( shmid, IPC_STAT, &buf) ;

        if ( flag == -1 )

        {

            perror("shmctl shm error") ;

            return -1 ;

        }

 

        printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;

        printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;

        printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;

        shmaddr = (char *) shmat(shmid, NULL, 0 ) ;

        if ( (int)shmaddr == -1 )

        {

            perror("shmat addr error") ;

            return -1 ;

 

        }

        printf("%s", shmaddr) ;

        shmdt( shmaddr ) ;

        shmctl(shmid, IPC_RMID, NULL) ;

    }else{

        perror("fork error") ;

        shmctl(shmid, IPC_RMID, NULL) ;

    }

 

    return 0 ;

}

GCC 静态编译后运行,输出结果:

 

 汇编改写手动触发系统调用

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <error.h>

#define SIZE 1024

int main()

{

    int shmid ;

    char *shmaddr ;

    struct shmid_ds buf ;

    int flag = 0 ;

    int pid ;

 

    shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;

    if ( shmid < 0 )

    {

            perror("get shm  ipc_id error") ;

            return -1 ;

    }

    pid = fork() ;

    if ( pid == 0 )

    {

        shmaddr = (char *)shmat( shmid, NULL, 0 ) ;

        if ( (int)shmaddr == -1 )

        {

            perror("shmat addr error") ;

            return -1 ;

 

        }

        strcpy( shmaddr, "Hi, I am child process!\n") ;

        shmdt( shmaddr ) ;

        return  0;

    } else if ( pid > 0) {

        sleep(3 ) ;

        flag = shmctl( shmid, IPC_STAT, &buf) ;

        asm volatile(

        "movq %3, %%esi\n\t"  // 参数3

        "movq %2, %%edx\n\t"   //  参数2

        "movq %1, %%eax\n\t"  //  参数1 

        "movl $0x1F,%%edi\n\t" //  传递系统调用号31

        "shmctl\n\t"          //  系统调用

        "movq %%eax,%0\n\t"    //  结果存到flag中

        :"=m"(flag) // 输出
        
    );

        if ( flag == -1 )

        {

            perror("shmctl shm error") ;

            return -1 ;

        }

 

        printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;

        printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;

        printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;

        shmaddr = (char *) shmat(shmid, NULL, 0 ) ;

        if ( (int)shmaddr == -1 )

        {

            perror("shmat addr error") ;

            return -1 ;

 

        }

        printf("%s", shmaddr) ;

        shmdt( shmaddr ) ;

        shmctl(shmid, IPC_RMID, NULL) ;

    }else{

        perror("fork error") ;

        shmctl(shmid, IPC_RMID, NULL) ;

    }

 

    return 0 ;

}                    

这里只对其中一处进行了汇编。

运行结果如下:

 

 

 四、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

在linux-5.4.34目录下另开一个终端启动gdb进行调试

gdb vmlinux

 

target remote:1234
b __x64_sys_shmctl
c

然后进行GDB调试。

总结

汇编指令syscall触发系统调用,通过MSR寄存器找到了中断函数入口,通过swapgs和压栈动作保存现场。跳转到do_syscall_64函数,在ax寄存器中获取系统调用号,然后去执行系统调用内容。然后准备进行现场恢复操作,执行现场恢复。

posted @ 2020-05-27 18:29  东陵欢喜  阅读(214)  评论(0编辑  收藏  举报