这是我的第四篇博客,写博客渐渐成为了日常。

本博客将深入分析Socket接口函数与系统调用的关系,并且将Socket API编程接口、系统调用机制及内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,

最后在X86 64环境下Linux5.0以上的内核中进行实验,来进行跟踪验证。

一、系统调用过程分析

首先直接上图分析用户态执行open函数(与分析socket相关函数是一致的)时64位系统调用的过程图。

图片引用自:https://www.cnblogs.com/JaPer/p/10810096.html

该博客分析的很透彻,总结几点如下:

1.中断转为真正调用:系统调用名称转换为系统调用号,放在寄存器rax中 。

2.改用syscall 指令,并且传递参数的寄存器也变了。  syscall 指令使用了特殊的寄存器,叫特殊模块寄存器(MSR)

3.跳转到entry_SYSCALL_64,entry_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)

#ifdef CONFIG_X86_64
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
...
    if (likely(nr < NR_syscalls)) {
        nr = array_index_nospec(nr, NR_syscalls);
        regs->ax = sys_call_table[nr](regs);
...
}
#endif

检查位于rax寄存器中的系统调用号,在sys_call_table中寻找对应的处理函数地址,然后执行它。如果系统调用号出错,那就从系统调用中退出
在系统调用结束之后,使用sysretq指令,恢复调用之前的处理器现场,包括之前的栈、通用寄存器值、标志位等,然后将系统调用处理程序的返回值放入eax寄存器供用户程序使用,然后从entry_SYSCALL_64中退出。

到此系统调用的过程完毕。

4.可能看过以上过程很多同学还是很多地方不理解,下面给出部分相关知识供大家参考:

系统初始化过程:

  对于x86-32位系统:start_kernel --> trap_init --> idt_setup_traps --> 0x80--entry_INT80_32,在5.0内核int0x80对应的中断服务例程是entry_INT80_32,而不是原来的名称system_call了。

  对于x86-64位系统:start_kernel --> trap_init --> cpu_init --> syscall_init

贴出syscall_init代码:其中rdmsr 和 wrmsr 是用来读写特殊模块寄存器的(MSR),即对应了上面系统调用的2过程。

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

系统调用表 sys_call_table 的形成: 

  32位的系统调用表定义在面 arch/x86/entry/syscalls/syscall_32.tbl 文件里 

  64位的系统调用表定义在面 arch/x86/entry/syscalls/syscall_64.tbl 文件里

二、Socket接口函数

由上面介绍的系统调用过程分析可知,我们知道了系统调用号即可陷入到调用相对应的内核函数,socket相关函数也不例外。

在我们构建的环境中,通过112号系统调用socketcall(它的内核处理函数为sys_socketcall)来实现socket通信。该函数的实现位于linux-5.0.1/net/socket.c下

为了不给老师阅读太大的负担,这里不再贴出过多的代码和图片。

大致分析代码可得到以下几点:

1.从该函数的结构就可以看出:传入参数不同,switch来选择具体调用哪个方法。

2.linux里面的系统调用是靠一些宏,一张系统调用表,一个系统调用入口来完成的。

3.socket接口的调用是通过给socket接口函数编号的方式通过112号系统调用来处理的,这些socket接口函数编号的宏定义见linux-5.0.1/include/uapi/linux/net.h中

在老师的主页上已经很好的分析了socket接口的内核处理函数__x64_sys_socket、bind的内核处理函数__x64_sys_bind等有关socket通信的函数了,这里不再过多赘述。

详情可参考:https://docs.huihoo.com/joyfire.net/6-1.html 和老师主页:https://github.com/mengning/net/blob/master/doc/socketSourceCode.md

三、实验环节-gdb跟踪验证

环境搭建:按道理在之前的32位menuOS上重新编译为64位的,稍微修改一下就行。但是事与愿违,最后还是重新做一遍实验二的搭建环境。

跟踪验证首先给会调用到的内核函数一一设置断点

我给__x64_sys_socket等加上x64的接口函数设了断点,也给没加x64的设了断点,观察执行情况

没有贴太多过程图,和上个实验类似。

观察发现在64位的环境下,执行的是加x64的内核函数。

遇到的问题:此处碰到一个极其棘手的问题,Remote 'g' packet reply is too long

尝试了网上两类解决方案:

1.修改gdb下的remote.c代码process_g_packet函数中的判断语句注释掉

2.在使用gdb之前设置set architecture i386:x86-64:intel

为了老师看的方便不过多赘述:详情可以参考网址:https://blog.csdn.net/manfeel/article/details/38755693

本文内容看上去不多,确实还没有很细致的分析socket接口内核函数,只是在第二部分给了个大概的分析。第一部分详细介绍了系统调用的过程。第三部分其实和实验二类似,因此也没有过多赘述。

12-25 07:48