zoukankan      html  css  js  c++  java
  • 深入理解Linux系统调用

    实验要求

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

    实验内容

    查找系统调用

    查看linux内核arch/x86/entry/syscalls/syscall_64.tbl下的64位x86系统对应的系统调用表,并找到07所对应的系统
    调用号。

     根据上图可以看出,07所对应的系统调用为__x64_sys_poll,其对应的API函数为poll。

    编写汇编代码触发系统调用

    查阅相关资料得知,poll()函数功能为:在指定时间内轮询一定数量的文件描述符,来测试其中是否有就绪者,

    int poll(struct pollfd* fds, nfds_t nfds, int timeout);

    其中:

    (1)fds是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体如下:

    (2)nfds指定被监听事件集合fds的大小

    (3)timeout指定poll的超时值,当timeout为-1时poll调用将永远阻塞,直至某个时间发生,而当timeout为0时,poll调用将会立即返回。

     我们通过编写一个程序来测试poll的具体功能,测试代码如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <poll.h>
    
    int main(int argc, char **argv)
    {
        int fd;
        char* filename="1.txt";
        unsigned long cnt=0;
        int ret;
        struct pollfd *key_fds;//定义一个pollfd结构体key_fds
       
        fd = open(filename, O_RDWR);
        if (fd < 0)//小于0说明没有成功
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
        
        if(argc !=1)
        {
            printf("Usage : %s ",argv[0]);
            return 0;
        }
        key_fds ->fd = fd;//文件
        key_fds->events = POLLIN;//poll直接返回需要的条件
        while(1)
        {
            ret =  poll(key_fds, 1, 5000);//调用sys_poll系统调用,如果5S内没有产生POLLIN事件,那么返回,如果有POLLIN事件,直接返回
            if(!ret)
            {
                printf("time out
    ");
            }
            else
            {
                if(key_fds->revents==POLLIN)
                {         
                    printf("test succeed
    ");
                }
            }
         
        }
    
        return 0;
    }

    该程序对指定文件设置监听事件POLLIN,也即是读事件,时间设为5s,若5s内发生读事件,则返回成功并打印:

     在了解了poll系统调用的基本功能后,我们接下来通过编写汇编代码来触发系统调用:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <poll.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    int main(int argc, char **argv)
    {
        int fd;
        char* filename="1.txt";
        int key_val;
        int ret;
        struct pollfd *key_fds;//定义一个pollfd结构体key_fds
       
        fd = open(filename, O_RDWR);
        if (fd < 0)//小于0说明没有成功
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
        
        if(argc !=1)
        {
            printf("Usage : %s ",argv[0]);
            return 0;
        }
        key_fds ->fd = fd;
        key_fds->events = POLLIN;
        //ret =  poll(key_fds, 1, 5000);
        
        int b1 = 1;
        int b2 = 5000;
        asm volatile(
                     "movq %3, %%rdx
    	"
                     "movq %2, %%rsi
    	"
                     "movq %1, %%rdi
    	"
                     "movl $0x07, %%eax
    	"
                     "syscall
    	"
                     "movq %%rax, %0
    	"
                     :"=m"(ret)
                     :"b"(key_fds),"c"(b1),"d"(b2)
                    );
        if(!ret)
        {
            printf("time out
    ");
        }
        else
        {
            if(key_fds->revents==POLLIN)
            {         
                 read(fd, &key_val, 1);          
            printf("test succeed
    ");
        }
        }
        return 0;
    }

    gdb跟踪分析系统调用过程

    首先是环境配置

    安装开发工具

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

    下载内核源代码

    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  #  此时应该不能正常运行

    制作根文件系统

    复制代码
    #下载
    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
    复制代码
    
    

    制作内存根文件系统镜像

    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 SeanOS!"
    echo "--------------------" cd home /bin/sh #给init脚本添加可执行权限 chmod +x init #打包成内存根文件系统镜像 find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz #测试挂载根文件系统,看内核启动完成后是否执行init脚本
    cd .. qemu-system-x86_64 -kernel ../linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

    至此环境配置完毕。

    下面我们将生成的之前编写的c文件的可执行文件文件拷贝至rootfs/home文件夹下,最后,由于我们对系统做了修改,需要重新打包成内存根文件系统镜像。因此要再次使用命令:

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

    再次启动qemu,发现home里出现了我们刚才复制的两个文件:

     下面进行gdb调试工作:

    首先执行

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

    之后再打开个终端,在对应的系统调用入口处打好断点后,执行polltest应用程序,并在gdb调试中使用bt查看当前堆栈。

     

     poll系统调用成功执行。用bt查看堆栈:

     

     可以看到系统调用的入口在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
        /* 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 */

    swapgs指令以类似快照的方式通过CPU内部的存储器,将保存现场和恢复现场时的寄存器保存起来,然后将pt_regs中的相关字段保存到内核栈中。
    紧接着,调用了do_syscall_64,代码如下

    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 */

    先将rax中的值保存在了栈中,然后通过rdi,rsi进行传参,其中rdi传递的是系统调用号,rsi传递的是pt_regs
    函数do_syscall_64()的代码如下

    #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);
    }

    在该函数中,通过传入的系统调用号nr找到相应的系统调用,并将返回值保存在regs的ax中。
    调用结束后,执行syscall_return_slowpath,进行返回。
    然后在gdb单步调试中,我们可以看到从syscall_return_slowpath返回后,开始恢复现场。主要是将之前保存在栈中的寄存器的值,重新恢复到原来的寄存器中。

    系统调用返回,由内核态回到用户态。

  • 相关阅读:
    android 微信(5.3)聊天UI的布局思考
    android 屏幕适配
    不同Activity之间的动画切换
    Freemarker 进行字符串截取
    如何使带背景图片的Button按钮中的文字居中偏上显示
    关于在IE-8下 button的背景图片不能正确显示的问题
    android Xmpp+openfire 消息推送 :SASL authentication failed using mechanism DIGEST-MD5
    Android运行出现“java.io.IOException: 您的主机中的软件放弃了一个已建立的连接。”
    计算机网络
    Java基础-3
  • 原文地址:https://www.cnblogs.com/seanloveslife/p/12960431.html
Copyright © 2011-2022 走看看