深入理解Linux系统调用

一、实验内容

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

二、实验步骤

1、环境配置

1. 安装开发工具:

    sudo apt install build-essential
    sudo apt install qemu qyfsudo 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. 编译menuOS调试工具

    cd linux-5.4.34
    make defconfig  #默认配置基于'x86_64_defconfig'
    make menuconfig

4. 配置内核选项

    # 打开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)

5.编译内核

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

2、制作根文件系统

电脑加电启动⾸先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。我们这是为了简化实验环境,仅制作内存根文件系统。这里借助BusyBox 构建极简

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

2.配置编译 并安装:

    make menuconfig
    /*
    记得要编译成静态链接,不用动态链接库。
    Settings --->
        [*] Build static binary (no shared libs)
    然后编译安装,默认会安装到源码目录下的 _install 目录中。
    */
    make -j$(nproc) && make install

3.制作内存根文件系统镜像

    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/

4.init脚本

init脚本文件放在根文件系统跟目录下(rootfs/init),这样内核才能加载启动文件从而启动,添加如下内容到init文件。

    #!/bin/sh
    mount -t proc none /proc mount -t sysfs none /sys
    echo "Wellcome TestOS!" echo "--------------------"
    cd home
    /bin/sh
    
    #给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

可以看到内核成功启动

3、系统调用

1.找系统调用

找一个系统调用,系统调用号为学号最后2位(01)相同的系统调用。

Linux源代码中的arch/x86/entry/syscalls/syscall_32.tblarch/x86/entry/syscalls/syscall_64.tbl分别定义了32位x86x86-64的系统调⽤。内核处理函数通过查看linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl系统调用表可以看到01号系统调用为__x64_sys_write

2.编写汇编代码调用该系统调用

创建test.c文件

    #include <stdio.h>
    
    int main()
    {
        asm volatile(     
                "movl $0x12, %eax\n\t" //传递系统调用号
                "syscall\n\t" //系统调用
                );
            return 0;
    }

使用下面命令将test.c进行静态编译

`gcc -o test test.c -static`

将形成的可执行文件放到rootfs/home/目录下,然后重新打包rootfs文件夹

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

这样,我们就完成了系统调用的准备,接下来使用gdb进行调试。

3.gdb跟踪系统调用

先输入如下命令:

因为我们不需要看图形界面,所以使用纯命令启动

`qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"`

然后新开启一个terminal

输入如下命令来开启gdb调试并在我们要调用的系统调用处设置断点:

    cd linux-5.4.34/
    gdb vmlinux
    target remote:1234
    b __x64_sys_write


然后在gdb里面输入c使qemu继续运行,在qemu虚拟机里面,

输入./test ,然后就可以在gdb页面看到系统执行到了我们打断点的位置

继续单步执行:

在系统调用执行完成之后就开始恢复现场,其中__x64_sys_write代码如下

继续单步执行,直到恢复现场完成:

三、实验总结

本次实验通过对01号系统调用的实现,学习了对Linux内核进行断点调试的相关技巧,了解了__x64_sys_write系统调用的作用,使得我加深了对系统调用相关知识的理解
posted @ 2020-05-27 20:52  KTT飞  阅读(304)  评论(0)    收藏  举报