这是我的第四篇博客,写博客渐渐成为了日常。
本博客将深入分析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接口内核函数,只是在第二部分给了个大概的分析。第一部分详细介绍了系统调用的过程。第三部分其实和实验二类似,因此也没有过多赘述。