• 深入理解系统调用 -- 课程实验2


    一、 搭建Linux内核调试环境

      本次实验的目录架构,一个主目录LinuxK,其包含3个文件夹:linux-5.4.34内核文件夹,busybox文件夹和rootfs文件夹。

      安装开发工具

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

      下载内核源码

    # 在linuxK目录下
    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

      配置内核选项

    # 在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)

      

      编译运行内核

    #在linux-5.4.34目录下
    make -j$(nproc) # nproc gives the number of CPU cores/threads available
    # 测试⼀下内核能不能正常加载运⾏,因为没有⽂件系统终会kernel panic 
    qemu-system-x86_64 -kernel arch/x86/boot/bzImage  #  此时应该不能正常运行

      

      制作根文件系统

      电脑加电启动⾸先由bootloader加载内核,内核紧接着需要挂载内存根⽂件系统,其中包含必要的设备驱动和⼯具,bootloader加载根⽂件系统到内存中,内核会将其挂载到根⽬录/下,然后运⾏根⽂件系统中init脚本执⾏⼀些启动任务,最后才挂载真正的磁盘根⽂件系统。我们这⾥为了简化实验环境,仅制作内存根⽂件系统。这⾥借助BusyBox 构建极简内存根⽂件系统,提供基本的⽤户态可执⾏程序。

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

      注意:由于与gcc兼容性问题,不要去官网下载busybox,可用在https://github.com/mirror/busybox 下载 busybox。

    make menuconfig
    #记得要编译成静态链接,不⽤动态链接库。
    Settings --->
         [*] Build static binary (no shared libs)
    #然后编译安装,默认会安装到源码⽬录下的 _install ⽬录中
    make -j$(nproc) 
    make install
    #制作内存根文件系统镜像
    # 在linuxK目录下
    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 MyOS!"
     echo "--------------------" 
     cd home
     /bin/sh 
    
    #在 rootfs 目录下 给init脚本添加可执⾏权限
    chome +x init
    # 在 rootfs目录下,打包成内存根⽂件系统镜像
    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  

      

      此时,完成内核调试环境的搭建。

    二、跟踪调试Linux内核的基本方法

      本实验中启动虚拟机的方法

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

      ⽤以上命令先启动,然后可以看到虚拟机⼀启动就暂停了。加-nographic -append "console=ttyS0"参数启动不会弹出QEMU虚拟机窗⼝,可以在纯命令⾏下启动虚拟机,此时可以通过“killall qemu-systemx86_64”命令强⾏关闭虚拟机。此时不可关闭QEMU及终端。再打开另一个终端(建议在linux5.4.34目录下打开),输入如下命令:

    gdb vmlinux
    (gdb) target remote:1234
    (gdb) b start_kernel
    c、 bt、 list、 next、 step....

      系统调用概述

      现代cpu通常有多种特权级别,一般来说特权级总共有4个,编号从Ring 0(最高特权)到Ring 3(最低特权),在Linux上之用到Ring 0和RIng 3,用户态对应Ring 3,内核态对应Ring 0。普通应用程序运行在用户态下,其诸多操作都受到限制,而系统调用是运行在内核态的,操作系统一般是通过中断来从用户态切换到内核态的。
      中断一般有两个属性,一个是中断号,一个是中断处理程序。不同的中断有不同的中断号,每个中断号都对应了一个中断处理程序。在内核中有一个叫中断向量表的数组来映射这个关系。当中断到来时,cpu会暂停正在执行的代码,根据中断号去中断向量表找出对应的中断处理程序并调用。中断处理程序执行完成后,会继续执行之前的代码。中断分为硬件中断和软件中断,我们这里说的是软件中断,软件中断通常是一条指令,使用这条指令用户可以手动触发某个中断。例如在i386下,对应的指令是int,在int指令后指定对应的中断号,如int 0x80代表你调用第0x80号的中断处理程序。
      对于每个系统调用都有一个系统调用号,在触发中断之前,会将系统调用号放入到一个固定的寄存器,0x80对应的中断处理程序会读取该寄存器的值,然后决定执行哪个系统调用的代码。
    操作系统通过系统调用为运行于其上的进程提供服务。当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。

     调用流程

      在应用程序内,调用一个系统调用的流程是怎样的呢?我们以一个假设的系统调用 xyz 为例,介绍一次系统调用的所有环节。

      

      如上图,系统调用执行的流程如下:
        1.应用程序 代码调用系统调用( xyz ),该函数是一个包装系统调用的 库函数 ;
        2.库函数 ( xyz )负责准备向内核传递的参数,并触发 软中断 以切换到内核;
        3.CPU被 软中断 打断后,执行 中断处理函数 ,即 系统调用处理函数 ( system_call);
        4.系统调用处理函数 调用 系统调用服务例程 ( sys_xyz ),真正开始处理该系统调用;

      执行态切换

        应用程序 ( application program )与 库函数 ( libc )之间, 系统调用处理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用。 而 库函数 与 系统调用处理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独立的执行流,因此在切换时,需要准备 执行栈 并保存 寄存器 。内核实现了很多不同的系统调用(提供不同功能),而 系统调用处理函数 只有一个。 因此,用户进程必须传递一个参数用于区分,这便是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般通过 eax 寄存器 来传递。

      总结起来, 执行态切换 过程如下:

        1.应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ;
        2.CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ;
        3.系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现);
        4.系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;
        5.系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;
        6.系统调用处理函数 执行 ret 指令切换回 用户态 ;

      查看系统调用表

      打开内核源码目录linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,可以看到12号系统调用是__x64_sys_brk。

      

       汇编调用程序:

      

      汇编结果:

      

      重新生成文件系统:

         结果:

      

    (gdb) b __x64_sys_brk
    Breakpoint 1 at 0xffffffff81199a40: file mm/mmap.c, line 187.
    (gdb) c
    Continuing.
    
    Breakpoint 1, __x64_sys_brk (regs=0xffffc900001b7f58) at mm/mmap.c:187
    187     SYSCALL_DEFINE1(brk, unsigned long, brk)
    (gdb) n
    do_syscall_64 (nr=18446612682188181624, regs=0xffffc900001b7f58) at arch/x86/entry/common.c:300
    300             syscall_return_slowpath(regs);
    (gdb) n
    301     }
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:184
    184             movq    RCX(%rsp), %rcx
    (gdb) n
    185             movq    RIP(%rsp), %r11
    (gdb) n
    187             cmpq    %rcx, %r11      /* SYSRET requires RCX == RIP */
    (gdb) n
    188             jne     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    205             shl     $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
    (gdb) n
    206             sar     $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
    (gdb) n
    210             cmpq    %rcx, %r11
    (gdb) n
    211             jne     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    213             cmpq    $__USER_CS, CS(%rsp)            /* CS must match SYSRET */
    (gdb) n
    214             jne     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    216             movq    R11(%rsp), %r11
    (gdb) n
    217             cmpq    %r11, EFLAGS(%rsp)              /* R11 == RFLAGS */
    (gdb) n
    218             jne     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    238             testq   $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11
    (gdb) n
    239             jnz     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    243             cmpq    $__USER_DS, SS(%rsp)            /* SS must match SYSRET */
    (gdb) n
    244             jne     swapgs_restore_regs_and_return_to_usermode
    (gdb) n
    253             POP_REGS pop_rdi=0 skip_r11rcx=1
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:259
    259             movq    %rsp, %rdi
    (gdb) n
    260             movq    PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:262
    262             pushq   RSP-RDI(%rdi)   /* RSP */
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:263
    263             pushq   (%rdi)          /* RDI */
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:271
    271             SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
    (gdb) n
    273             popq    %rdi
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:274
    274             popq    %rsp
    (gdb) n
    entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:275
    275             USERGS_SYSRET64
    (gdb) n
    0x0000000000475349 in ?? ()
  • 相关阅读:
    第四十一节 jQuery之bootstrap文档
    第四十节 jQuery之bootstrap简介
    Redis 如何实现查询附近的距离
    线上日志快速定位-grep
    Java字符串操作工具类
    JAVA批量插入数据操作+事务提交
    java开发需求中技术常见名称
    MySQL Binlog--MIXED模式下数据更新
    MySQL Replication--修改主键为NULL导致的异常
    MySQL Replication--复制异常1
  • 原文地址:https://www.cnblogs.com/LiScott/p/12976202.html
Copyright © 2020-2023  润新知