Socket与系统调用深度分析
一、系统调用概述
系统调用由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。当用户态进程发起一个系统调用, CPU将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。
调用流程如下图所示:
1.当应用程序执行到xyz()这个函数时,该函数是一个包装了系统调用的库函数
2.xyz()负责准备好需要向内核传递的参数,并且触发软中断从用户态切换到内核态
3.CPU被软中断打断后,执行中断处理函数,即系统调用处理函数(system_call)
4.系统调用处理函数调用系统调用服务例程(sys_xyz),开始处理系统调用
5.处理结束后,再将处理结果返回给用户态,继续后面程序的执行
二、Socket概述
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。工作流程如下图所示:
Socket():创建套接字。
qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append nokaslr -s -S
连接gdb server,如下图所示:
执行完后QEMU虚拟机会启动
重开一个终端依次执行下面的命令
gdb file ~/LinuxKernel/linux-5.0.1/vmlinux break sys_socketcall
target remote:1234 c
执行完成后如下图所示:
在QEMU窗口中输入以下命令:
replyhi
gdb调试终端界面显示如下:
观察上图倒数第二行,gdb跟踪到了 sys_socketcall函数,对应的系统调用处理函数为SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),倒数第三行指出了该函数所在文件及位置,再net目录下socket.c的第2527行。
打开socket.c文件,可以看到SYSCALL_DEFINE2函数的定义,如下面所示:
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) { unsigned long a[AUDITSC_ARGS]; unsigned long a0, a1; int err; unsigned int len; if (call < 1 || call > SYS_SENDMMSG) return -EINVAL; call = array_index_nospec(call, SYS_SENDMMSG + 1); len = nargs[call]; if (len > sizeof(a)) return -EINVAL; /* copy_from_user should be SMP safe. */ if (copy_from_user(a, args, len)) return -EFAULT; err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); if (err) return err; a0 = a[0]; a1 = a[1]; switch (call) { case SYS_SOCKET: err = __sys_socket(a0, a1, a[2]); break; case SYS_BIND: err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_CONNECT: err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]); break; case SYS_LISTEN: err = __sys_listen(a0, a1); break; case SYS_ACCEPT: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0); break; case SYS_GETSOCKNAME: err = __sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_GETPEERNAME: err = __sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; case SYS_SOCKETPAIR: err = __sys_socketpair(a0, a1, a[2], (int __user *)a[3]); break; case SYS_SEND: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], NULL, 0); break; case SYS_SENDTO: err = __sys_sendto(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], a[5]); break; case SYS_RECV: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], NULL, NULL); break; case SYS_RECVFROM: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], (struct sockaddr __user *)a[4], (int __user *)a[5]); break; case SYS_SHUTDOWN: err = __sys_shutdown(a0, a1); break; case SYS_SETSOCKOPT: err = __sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]); break; case SYS_GETSOCKOPT: err = __sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]); break; case SYS_SENDMSG: err = __sys_sendmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_SENDMMSG: err = __sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], true); break; case SYS_RECVMSG: err = __sys_recvmsg(a0, (struct user_msghdr __user *)a1, a[2], true); break; case SYS_RECVMMSG: if (IS_ENABLED(CONFIG_64BIT) || !IS_ENABLED(CONFIG_64BIT_TIME)) err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], (struct __kernel_timespec __user *)a[4], NULL); else err = __sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], NULL, (struct old_timespec32 __user *)a[4]); break; case SYS_ACCEPT4: err = __sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], a[3]); break; default: err = -EINVAL; break; } return err; }
可以看出,函数有一个参数call,在函数内部会根据call的不同值来确定返回不同的处理方式,在socket中有不同的函数如Socket():创建套接字,Bind():指定本地地址,Connect():将套接字连接到目的地址等,根据传入参数的不同进行相应的处理。
增加断点继续执行,如下图所示:
不断输入c命令,结果如下:
Breakpoint 1, __se_sys_socketcall (call=1, args=-1079671440) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 2, __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327 1327 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=2, args=-1079671452) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 3, __sys_bind (fd=4, umyaddr=0xbfa585bc, addrlen=16) at net/socket.c:1469 1469 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=1, args=-1079671424) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 2, __sys_socket (family=2, type=1, protocol=0) at net/socket.c:1327 1327 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=3, args=-1079671424) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 5, __sys_connect (fd=4, uservaddr=0xbfa585dc, addrlen=16) at net/socket.c:1646 1646 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=10, args=-1079671444) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 8, __sys_recvfrom (fd=5, ubuf=0xbfa585dc, size=1024, flags=0, addr=0x0, addr_len=0x0) at net/socket.c:1819 1819 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=9, args=-1079671428) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 10, __sys_sendto (fd=4, buff=0xbfa589fc, len=5, flags=0, addr=0x0, addr_len=0) at net/socket.c:1758 1758 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=9, args=-1079671444) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 10, __sys_sendto (fd=5, buff=0xbfa589dc, len=2, flags=0, addr=0x0, addr_len=0) at net/socket.c:1758 1758 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=10, args=-1079671428) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 8, __sys_recvfrom (fd=4, ubuf=0xbfa585fc, size=1024, flags=0, addr=0x0, addr_len=0x0) at net/socket.c:1819 1819 { (gdb) c Continuing. Breakpoint 1, __se_sys_socketcall (call=5, args=-1079671440) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) (gdb) c Continuing. Breakpoint 6, __sys_accept4 (fd=4, upeer_sockaddr=0xbfa585cc, upeer_addrlen=0xbfa585ac, flags=0) at net/socket.c:1542 1542 { (gdb) c Continuing.
call的值依次为1,2,1,3,10,9,9,10,5,分别对应不同的网络系统调用,由此可以总结出下表:
call值 | 对应的系统调用 |
1 | __sys_socket |
2 | __sys_bind |
3 | __sys_connect |
5 | __sys_accept4 |
9 | __sys_sendto |
10 | __sys_recvfrom |
实验结束