• 深入理解系统调用


    深入理解系统调用

    一. 实验准备

    1. 详细要求
    1. 找一个系统调用,系统调用号为学号最后2位相同的系统调用(我的学号尾号为66,故记录66号调用)
    2. 通过汇编指令触发该系统调用
    3. 通过gdb跟踪该系统调用的内核处理过程
    4. 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

    写一篇博客记录实验过程并总结分析系统调用的工作机制。

    1. 实验环境

    发行版本:Ubuntu 18.04.4 LTS

    处理器:Intel® Core™ i7-8850H CPU @ 2.60GHz × 3

    图形卡:Parallels using AMD® Radeon pro 560x opengl engine

    GNOME:3.28.2

    二. 实验过程

    1. 环境搭建

    1. 安装开发环境
    sudo apt install build-essential
    sudo apt install qemu # install QEMU
    sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
    
    1. 下载及配置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 
    
    #配置
    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)
    

    1. 编译内核并进行测试
    make -j$(nproc) 
    # 测试⼀下内核能不能正常加载运⾏,因为没有⽂件系统终会kernel panic 
    qemu-system-x86_64 -kernel arch/x86/boot/bzImage  #  此时应该不能正常运行
    


    4. 制作根文件系统

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

    1. 制作内存根文件系统镜像
    1 mkdir rootfs
    2 cd rootfs
    3 cp ../busybox-1.31.1/_install/* ./ -rf
    4 mkdir dev proc sys home
    5 sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
    
    1. 准备init脚本文件放在根文件系统跟目录下(rootfs/init),添加如下内容到init文件。
    1 #!/bin/sh
    2 mount -t proc none /proc mount -t sysfs none /sys
    3 echo "Wellcome MengningOS!"
    4 cd home
    5 /bin/sh
    
    1. 给init脚本添加可执行权限
    1 chmod +x init
    
    1. 打包成内存根文件系统镜像
    1 find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
    
    1. 测试挂载根文件系统
    1 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
    

    2. 进行实验:测试系统调用

    1. 查找学号尾号66对应的系统调用

    1. 编写进行调用的代码(testSemctl.c),调用部分
    asm volatile(
        "movq %1, %%rdi
    	"  // 将第一个参数放入 rdi 寄存器
        "movq %2, %%ecx
    	"  // 将第二个参数放入 ecx 寄存器
      	"movq %3, %%ebx
    	"  // 将第三个参数放入 ebx 寄存器
      	"movq %4, %%rsi
    	"  // 将第四个参数放入 rsi 寄存器
        "movl $0x42, %eax
    	" //传递系统调用号
        "syscall
    	" //系统调用
        "movq %%rax, %0
    	" // 将函数处理结果返回给 res 变量中
        :"=m"(res)
        :"a"(&x), "b"(&y),"c"(&z),"d"(&k)
      );
    
    1. 使用gcc进行编译
    gcc testSemctl.c -o testSemctl -static
    
    1. 重新制作根文件系
    find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
    

    3. GDB调试

    1. 纯命令行启动qemu
    1 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
    

    1. 启动gdb
    cd linux-5.4.34
    # 启动
    gdb vmlinux
    target remote:1234
    # 在semctl调用处打断点
    b __x64_sys_semctl
    

    1. 输入c(continue)继续运行

    1. 运行编写好的调用系统调用的代码

    1. gdb单步调试

    三. 系统调用分析

    1. 分析可见断点定位在ipc/sem.c:1687,查看相关代码
    SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
    {
    	return ksys_semctl(semid, semnum, cmd, arg, IPC_64);
    }
    
    1. 接下来是entry/common.c:300,查看相关代码
    #ifdef CONFIG_X86_64
    __visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
    {
    	struct thread_info *ti;
    
    	enter_from_user_mode();
    	local_irq_enable();
    	ti = current_thread_info();
    	if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
    		nr = syscall_trace_enter(regs);
    
    	if (likely(nr < NR_syscalls)) {
    		nr = array_index_nospec(nr, NR_syscalls);
    		regs->ax = sys_call_table[nr](regs);
    #ifdef CONFIG_X86_X32_ABI
    	} else if (likely((nr & __X32_SYSCALL_BIT) &&
    			  (nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
    		nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
    					X32_NR_syscalls);
    		regs->ax = x32_sys_call_table[nr](regs);
    #endif
    	}
    
    	syscall_return_slowpath(regs);
    }
    #endif
    
    1. arch/x86/entry/entry_64.S:184,查看相关代码
    /*
    	 * Try to use SYSRET instead of IRET if we're returning to
    	 * a completely clean 64-bit userspace context.  If we're not,
    	 * go to the slow exit path.
    	 */
    	movq	RCX(%rsp), %rcx
    	movq	RIP(%rsp), %r11
    
    	cmpq	%rcx, %r11	/* SYSRET requires RCX == RIP */
    	jne	swapgs_restore_regs_and_return_to_usermode
    

    四. 总结

    系统调⽤的执⾏,也就是⽤户程序触发系统调⽤之后,CPU及内核执⾏系统调⽤ 的过程

    int $0x80是CPU压栈⼀些关键寄存器,接着内核负责保存现场,系统调⽤内核 函数处理完后恢复现场,最后通过iret出栈哪些CPU压栈的关键寄存器。

    sysenter和syscall都借助CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,可 以快速切换CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝,但本质上还是中 断处理的思路,压栈关键寄存器、保存现场、恢复现场,最后系统调⽤返回。

    x86-64引⼊了swapgs指令,类似快照的⽅式将保存现场和恢复现场时的CPU寄 存器也通过CPU内部的存储器快速保存和恢复,近⼀步加快了系统调⽤。

  • 相关阅读:
    vue.js 条件与循环
    vue.js 声明式渲染
    数据库设计范式?
    用户购物车,实现添加商品的功能!
    用户购物车功能的实现。
    初始ajax技术
    SQL语句中 INNER JOIN的用法!
    商城 用户登录、注册、注销,购物车。
    EL和 JSTL? 在JSP中简化 java代码的写法!
    小数点后保留2位小数的正则表达式
  • 原文地址:https://www.cnblogs.com/femery/p/12965576.html
Copyright © 2020-2023  润新知