深入理解系统调用

一、实验要求

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

二、系统调用号:本人学号末尾为89,相应的系统调用为__x64_sys_readlink

 man查一下这个函数是干嘛的,意思打印出解析过符号链接或者被纳入的(最简洁的)文件名

 

 

 

 

gdb分析与调试

int main()

{

  asm volatile(

  "movl $0x59,%eax\n\t"

  "syscall\n\t" );

  return 0;

}

gcc编译(这里采用静态编译)

gcc -o zyx zyx.c -static

因为rootf有变化,需要重新生成内存根文件镜像,重新挂载镜像,然后运行qemu

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
qemu
-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

在启动qemu时多次触发该系统调用 

 

 

来看一下该系统调用的源码

SYSCALL_DEFINE3(readlink, const char __user *, path, char __user *, buf,
        int, bufsiz)
{
    return do_readlinkat(AT_FDCWD, path, buf, bufsiz);
}

找到do_readlinkat的源码

static int do_readlinkat(int dfd, const char __user *pathname,
             char __user *buf, int bufsiz)
{
    struct path path;
    int error;
    int empty = 0;
    unsigned int lookup_flags = LOOKUP_EMPTY;

    if (bufsiz <= 0)
        return -EINVAL;

retry:
    error = user_path_at_empty(dfd, pathname, lookup_flags, &path, &empty);
    if (!error) {
        struct inode *inode = d_backing_inode(path.dentry);

        error = empty ? -ENOENT : -EINVAL;
        /*
         * AFS mountpoints allow readlink(2) but are not symlinks
         */
        if (d_is_symlink(path.dentry) || inode->i_op->readlink) {
            error = security_inode_readlink(path.dentry);
            if (!error) {
                touch_atime(&path);
                error = vfs_readlink(path.dentry, buf, bufsiz);
            }
        }
        path_put(&path);
        if (retry_estale(error, lookup_flags)) {
            lookup_flags |= LOOKUP_REVAL;
            goto retry;
        }
    }
    return error;
}

readlink和readlinkat函数组合了open、read和close的所有操作。如果函数成功执行,则返回读入buf的字节数。在buf中返回的符号链接的内容不以null字符终止。

 

 

根据调用栈看一下系统调用的过程

SYSCALL_DEFINE3(readlink, const char __user *, path, char __user *, buf,
        int, bufsiz)
{
    return do_readlinkat(AT_FDCWD, path, buf, bufsiz);
}

#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

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

    TRACE_IRQS_IRETQ        /* we're about to change IF */

过程分析:

  • 触发系统调用后,代码执行了/linux-5.4.34/arch/x86/entry/entry_64.S 目录下的ENTRY(entry_SYSCALL_64)入口,然后开始通过swapgs 和压栈动作保存现场:
  • 然后跳转到了/linux-5.4.34/arch/x86/entry/common.c 目录下的 do_syscall_64 函数,在ax寄存器中获取到系统调用号,然后去执行系统调用
  • 查看entry_syscall_64后续的代码,在完成执行现场的恢复,最后的两个popq出栈指令恢复原 rdi 和 rsp的内容,也就是完成了堆栈的切换

通过这次实验,我对系统调用有了直观的认识,阅读linux源码让我收获很大,感谢孟老师!

posted @ 2020-05-27 21:21  LittleTurtle  阅读(393)  评论(0编辑  收藏  举报