Socket与系统调用深度分析
Posted xpeng2333
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket与系统调用深度分析相关的知识,希望对你有一定的参考价值。
一、系统调用
系统调用由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(API),是应用程序同系统之间的接口。当用户态进程发起一个系统调用, CPU将切换到 内核态 并开始执行一个 内核函数 。 内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。
二、内核跟踪分析
在qemu中启动gdb server
首先切入到目录linuxnet/lab3,然后运行下面的命令,即可启动虚拟机:
qemu -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append nokaslr -s
重开一个终端依次执行下面的命令:
gdb file ~/Desktop/net/h3/linux-5.0.1/vmlinux //换成自己的路径 break sys_socketcall target remote:1234 c
可以看到:
在QEMU窗口中输入命令replyhi,查看gdb终端输出信息:
Breakpoint 1, __se_sys_socketcall (call=2, args=-1076926636) at net/socket.c:2527 2527 SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
可以看到gdb跟踪到了 sys_socketcall函数,对应的系统调用处理函数为SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args),查看socket.c代码:
打开socket.c文件,可以看到SYSCALL_DEFINE2函数的定义,如下面所示:
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args){}
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.c代码中我们可以看到依次运行了下面的代码:
call=1,2,4,5
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_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;
此时系统不再处于阻塞状态,可以输入hello命令,查看gdb输出:
对应到socket.c代码中我们可以看到依次运行了下面的代码:
call=1,3,10,9,9,10,5
case SYS_BIND: err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]); break;
case SYS_LISTEN: err = __sys_listen(a0, a1); break;
case SYS_RECV: err = __sys_recvfrom(a0, (void __user *)a1, a[2], a[3], NULL, NULL); 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_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);
case SYS_GETSOCKNAME: err = __sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
以上是关于Socket与系统调用深度分析的主要内容,如果未能解决你的问题,请参考以下文章