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,继续按回车继续,捕获断点,可以看到客户端发起连接,发送接收数据。
追踪完毕,结果如下图:
原文地址:https://www.cnblogs.com/qfdzztt/p/12057457.html