深入理解系统调用

1. 制作根文件系统,借助BusyBox 构建极简内存根⽂件系统,提供基本的⽤户态可执⾏程序

  1.0 下载编译Linux内核

# 安装
sudo apt install build-essential
sudo apt install qemu # install QEMU 
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev

# 重新下载linux内核源码
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  #  此时应该不能正常运行

 

  1.1 下载 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

  1.2 安装后,开始制作

make menuconfig   # 注意编译成静态链接;  如果运行不成功,可尝试重启虚拟机
Settings  --->
    [*] Build static binary (no shared libs) 
#然后编译安装,默认会安装到源码⽬录下的 _install ⽬录中。 
make -j$(nproc) && make install

 深入理解系统调用_第1张图片

  1.3 制作内存根文件系统镜像

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/

  1.4 准备init脚本文件放在根文件系统根目录下(rootfs/init) 

    添加如下内容到init文件

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

 深入理解系统调用_第2张图片

#给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

 重新启动虚拟机,可以看到成功执行了init脚本

 深入理解系统调用_第3张图片 

 

2. 查看linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl下系统调用表,10号系统调用为 mprotect;   对应函数为__x64_sys_mprotect

 该系统调用的作用:  mprotect(const void *start, size_t len, int prot)函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值 

   注意:  所指定的内存区间必须包含整个页, 即区间地址必须和整个系统页大小对齐,且区间长度必须是页大小的整数倍

     深入理解系统调用_第4张图片

 

3. 通过汇编指令触发该系统调用

 创建test.c文件,进行编码

#include 
#include <string.h>
#include 
#include 

#define PAGESIZE 4096
int main(void)
{
    char *p;
    char c;
   
    /* Allocate a buffer; it will have the default
       protection of PROT_READ|PROT_WRITE. */
    p = malloc(1024+PAGESIZE-1);
    if (!p) {
        printf("Couldn’t malloc(1024)");
    }

    p = (char *)(((int) p + PAGESIZE-1) & ~(PAGESIZE-1));

    c = p[666];         /* Read; ok */
    p[666] = 42;        /* Write; ok */    

    /* Mark the buffer read-only. */
    int res;        // 
    asm volatile(
      "mov $0x2, %%edx\n\t"   // 将第3个参数放入 edx 寄存器, 0x2为PROT_READ
      "mov $0x400, %%esi\n\t" // 将第二个参数放入 esi 寄存器    
      "mov %1, %%rdi\n\t"     // 将第一个参数放入 rdi 寄存器
        "mov $0x0a, %%eax\n\t"  // mprotect 的系统调用号为10,将其放入 eax 寄存器
        "syscall\n\t"           // 触发系统调用
        "mov %%rax, %0\n\t"     // 将函数处理结果返回给 res 变量
        :"=m"(res)
        :"b"(p)
     );    
    printf("The return value is : %d \n", res);    
    if (res) {
        printf("Couldn’t mprotect\n");
    }

    //c = p[666];         /* Read; ok */
    //p[666] = 42;        /* Write; program dies on SIGSEGV */
    return 0;
}

  先运行test文件,查看是否成功, 结果为0,表示成功

  

  静态编译该文件得到可执行文件,  并将其放入rootfs/home/目录下,然后重新打包rootfs文件夹:  

gcc -o test test.c -static
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

 

3. 通过gdb跟踪该系统调用的内核处理过程

 运行命令qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s

 深入理解系统调用_第5张图片

  重新打开一个终端,在__x64_sys_mprotect处设置断点

   深入理解系统调用_第6张图片

  断点之后,在qemu模拟器执行 ./test,之后会在该入口点处停止,查看系统调用栈(断点处乱码,gdb错误,暂时无法跟踪)

   深入理解系统调用_第7张图片

 

 

 4.  系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

    参考:  https://www.cntofu.com/book/104/SysCall/syscall-2.md

    在内核初始化过程中,完成了系统调用入口的初始化 --->

    执行./test文件时候, 触发syscall指令, 处理器从用户模式切换到内核模式,

    执行entry_SYSCALL_64 ( arch/x86/entry/entry_64.s ),完成系统调用执行前的所需准备(保护现场)  ---> 

    entry_SYSCALL_64 切换至内核堆栈,在堆栈中存通用目的寄存器, 老的堆栈,代码段, 标志位等  --->

    进入do_syscall_64函数,根据系统调用号,找到对应的函数, 并将返回值保存在regs的ax中  --->

    通过syscall_return_slowpath, 在调用结束时,执行该函数,进行返回 --->

    系统调用结束和,回到entry_SYSCALL_64, 系统调用将用户程序的返回结果放置在通用目的寄存器rax 中,

    因此在系统调用处理完成其工作后,将寄存器的值入栈

    并进行恢复现场,之后退出系统调用,转换到用户态

 

    查看mprotect对应的源码(linux-5.4.34/mm/mprotect.c),其对应的入口函数为__x64_sys_mprotect,  

    整个过程主要函数调用顺序:   entry_SYSCALL_64()  --->  do_syscall_64  --->   syscall_return_slowpath

        4.0  系统调用的入口点在entry_SYSCALL_64处,函数对应源码在arch/x86/entry/entry_64.s文件中

      主要用于保存和恢复现场ENTRY(entry_SYSCALL_64)

ENTRY(entry_SYSCALL_64)    
  UNWIND_HINT_EMPTY
/* * Interrupts are off on entry. * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON, * it is too small to ever cause noticeable irq latency. */
  // swapgs指令用于保护和恢复现场, 如将当前CPU对应的相关寄存器保存起来 swapgs
/* tss.sp2 is scratch space. */ movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* Construct struct pt_regs on stack */
  // 将堆栈段及老的堆栈指针入栈
pushq $__USER_DS /* pt_regs->ss */ pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
  // 使能中断, 因为入口中断被关闭,保存通用目的寄存器,标志位等等
pushq %r11 /* pt_regs->flags */ pushq $__USER_CS /* pt_regs->cs */ pushq %rcx /* pt_regs->ip */ GLOBAL(entry_SYSCALL_64_after_hwframe) pushq %rax /* pt_regs->orig_ax */ PUSH_AND_CLEAR_REGS rax=$-ENOSYS

    TRACE_IRQS_OFF

    /* IRQs are off. */
    movq %rax, %rdi
    movq %rsp, %rsi
    call do_syscall_64 /* returns with IRQs disabled */

     ...

    /*
    * We are on the trampoline stack. All regs except RDI are live.
    * We can do future final exit work right here.
    */
    STACKLEAK_ERASE_NOCLOBBER

    SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

    popq %rdi
    popq %rsp
    USERGS_SYSRET64

END(entry_SYSCALL_64)

你可能感兴趣的:(深入理解系统调用)