Socket与系统调用深度分析 ——X86 64环境下Linux5.0以上的内核中

1.Socket与系统调用——概述

Socket API编程接口之上可以编写基于不同网络协议的应用程序;

Socket接口在用户态通过系统调用机制进入内核;

内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调用为例进行分析;

socket相关系统调用的内核处理函数内部通过“多态机制”对不同的网络协议进行的封装方法;

下面会将Socket API编程接口系统调用机制内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,并在X86 64环境下Linux5.0以上的内核中进一步跟踪验证。

至于QEMU搭建X86 64位实验环境,可以参考上一篇博文https://www.cnblogs.com/qfdzztt/p/12018425.html

2.系统调用机制以及内核中相关源代码

socket系统调用发生流程(如下图所示)

当用户进程使用socket API 的时候,会产生向量为0x80的编程异常,系统执行系统调用。

进程传递系统调用号到寄存器eax,指明需要哪个系统调用,同时会将系统调用需要的参数存入相关寄存器。

系统调用处理函数system_call是Linux中所有系统调用的入口点,通过进程存在eax寄存器中的系统调用号决定调用哪个系统调用。

其中,socket api有两种系统调用方式:(1)所有的socket系统调用的总入口是sys_socketcall(系统调用号102)  (2)每一个独立的socket api都对应一个单独的系统调用。

系统调用初始化

系统调用的初始化过程为:start_kernel --> trap_init --> cpu_init --> syscall_init。主要分为两步,第一步是中断初始化,第二步是系统调用初始化

当产生向量为0x80的编程异常时,系统怎么就知道要执行中断处理函数system_call呢?那是因为在初始化内核的时候,会执行中断初始化函数trap_init,此函数拷贝中断异常向量表到指定位置,系统就能根据中断向量号跳转到对应的中断处理。而系统调用对应的中断是软件中断,其向量号为0x80。在第一步中,通过将软件中断的处理程序system_call(entry_SYSCALL_64)与0x80绑定,所以执行int 0x80时系统就会跳转到中断处理函数system_call。

其次,中断处理函数还需要根据系统调用号来执行相应的系统调用,这就需要初始化系统调用,将系统调用中断向量与服务例程绑定。内核维护一张系统调用表system call table,系统调用表是Linux内核源码文件 arch/x86/entry/syscall_64.c中定义的数组sys_call_table的对应。在第二步中,cpu_init函数调用syscall_init完成per-cpu状态初始化。该函数执行系统调用入口的初始化,该函数没有参数且首先填充两个特殊模块寄存器:

第一个特殊模块集寄存器- MSR_STAR 的 63:48 为用户代码的代码段。这些数据将加载至 CS 和 SS 段选择符,由提供将系统调用返回至相应特权级的用户代码功能的 sysret 指令使用。 同时从内核代码来看, 当用户空间应用程序执行系统调用时,MSR_STAR 的 47:32 将作为 CS and SS段选择寄存器的基地址。

第二行代码中我们将使用系统调用入口entry_SYSCALL_64 填充 MSR_LSTAR 寄存器。 entry_SYSCALL_64 在arch/x86/entry/syscall_64.S汇编文件中定义,包含系统调用执行前的准备。

void syscall_init(void)
{
    wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
    wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
  ...

 用gdbt调试,给start_kernel ,trap_init , cpu_init ,syscall_init几个函数增加断点验证初始化过程。

 系统调用执行

用户态程序发起系统调用,对于x86-64位程序应该是直接跳到entry_SYSCALL_64,,在do_syscall_64中根据系统调用号执行对应的系统调用。

SYM_CODE_START(entry_SYSCALL_64)
...
    /* IRQs are off. */
    movq    %rax, %rdi
    movq    %rsp, %rsi
    call    do_syscall_64        /* returns with IRQs disabled */
* [do_syscall_64](https://github.com/torvalds/linux/blob/ab851d49f6bfc781edd8bd44c72ec1e49211670b/arch/x86/entry/common.c#L282)

#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

3.跟踪socket相关系统调用内核处理函数

打开gdb调试,将与socket相关的函数们都打上断点

在本体系结构中,函数们如下:

输入c,继续,发现程序没到第一个断点就停住了,在Qemu中输入replyhi,不停地按回车,发现捕获到如下断点,一直到sys_accept4函数停止。说明此时服务器处于阻塞状态,一直在等待客户端连接。

在Qemu中输入hello,输入c,继续按回车继续,捕获断点,可以看到客户端发起连接,发送接收数据。

追踪完毕,结果如下图:

猜你喜欢

转载自www.cnblogs.com/qfdzztt/p/12057457.html