本次实验我们主要来研究SocketAPI,相应系统调用以及内核处理函数之间的关系。
实验环境
linux-5.0.1以x86_46_defconfig配置编译的内核
gdb-7.12
qemu-2.5.0
前言
SocketAPI:Socket网络编程相关的应用程序接口。所谓的应用程序接口我们可以将其视为库函数,封装底层具体实现逻辑,并提供一个用户友好的具体功能函数。
系统调用(system call):系统调用为内核提供给外界,一个用来将程序状态由用户态陷入内核态的入口。
内核处理函数:在内核态运行的函数,可以执行高权限的内核态指令。
Replyhi Socket API
我们以Replyhi函数为整个Socket调用的起始,先来看下Replyhi的代码。
1 int Replyhi() 2 { 3 char szBuf[MAX_BUF_LEN] = "\0"; 4 char szReplyMsg[MAX_BUF_LEN] = "hi\0"; 5 printf("Start InitializeService!\n"); 6 InitializeService(); 7 while (1) 8 { 9 printf("Start ServiceStart!\n"); 10 ServiceStart(); 11 printf("Start RecvMsg!\n"); 12 RecvMsg(szBuf); 13 printf("Start SendMsg!\n"); 14 SendMsg(szReplyMsg); 15 printf("Start ServicesStop!\n"); 16 ServiceStop(); 17 } 18 printf("Start ShutdownService!\n"); 19 ShutdownService(); 20 return 0; 21 }
可以看到在Replyhi中主要调用了六个方法来进行网络通信,这六个方法均以宏的形式定义在syswrapper.h中。
如SendMsg的宏定义如下所示,在宏定义中我们可以清楚地看到,该函数调用了send socket api。其他方法也分别调用了不同的socket api。
1 #define SendMsg(buf) \ 2 ret = send(newfd,buf,strlen(buf),0); \ 3 if(ret > 0) \ 4 { \ 5 printf("rely \"hi\" to %s:%d\n", \ 6 (char*)inet_ntoa(clientaddr.sin_addr), \ 7 ntohs(clientaddr.sin_port)); \ 8 }
Qemu调试
为了探明这些api背后的故事,我们利用gdb来进行内核调试。首先对跟socket相关的系统调用均打上断点。
在qemu中输入replyhi指令,可以看到分别停止于__sys_socket,__sys_bind,__sys_listen和__sys_accept4断点处,并且每次遇到__sys_xxx断点前均会与到__ia32_compat_sys_socketcall断点。
并且此时MenuOS输出了Start InitializeService!和Start ServiceStart!,表明Replyhi执行了前两个方法,而调用的系统函数也正好与syswrapper中的socket api相对应。
接下来我们来看下__ia32_compat_sys_socketcall断点出的代码,可以看到该代码位于net/compat.c的838行。
从上方的注释就可以看出这个函数调用是一个系统调用向量。下方代码也验证了该点,该函数会根据call的值来调用合适的socket相关的系统调用。
然后输入hello,断点情况如下所示,分别在__sys_socket,__sys_connect,__sys_sendto,__sys_recvfrom,...,__sys_accept4处停止,同样与replyhi后续方法调用的socket api一一对应。
内核处理函数
当程序运行都某一预设的断点处时,我们可以使用l来查看当前系统调用的内核处理函数。下图显示的是在gdb中查看__sys_sendto的内核处理函数的部分代码。
完整的处理函数代码位于net/socket.c中,其内容如下所示
/* * Send a datagram to a given address. We move the address into kernel * space and check the user space data area is readable before invoking * the protocol. */ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags, struct sockaddr __user *addr, int addr_len) { struct socket *sock; struct sockaddr_storage address; int err; struct msghdr msg; struct iovec iov; int fput_needed; err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter); if (unlikely(err)) return err; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (!sock) goto out; msg.msg_name = NULL; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_namelen = 0; if (addr) { err = move_addr_to_kernel(addr, addr_len, &address); if (err < 0) goto out_put; msg.msg_name = (struct sockaddr *)&address; msg.msg_namelen = addr_len; } if (sock->file->f_flags & O_NONBLOCK) flags |= MSG_DONTWAIT; msg.msg_flags = flags; err = sock_sendmsg(sock, &msg); out_put: fput_light(sock->file, fput_needed); out: return err; }