Socket与系统调用深度分析
上次Linux内核的实验,这次我们要在上次的基础上,做一个Socket与系统调用的深度分析。本次实验在X86 64环境下Linux5.0.1的内核中进一步跟踪验证。
首先,上次实验我们用vim更改了menu文件夹下的Makefile文件中的一行,这次实验我们还需要对其做更改,删除那一行最后的-S因为这个命令会在qemu打开时暂时挂起在cpu,别忘了:wq保存。
改完就make rootfs,出现menuos。
和上次实验一样,不要关闭这个终端和qemu窗口,另开一终端,gdb调试。
cd ../linux-5.0.1
gdb
file ./vmlinux
target remote:1234
b __ia32_compat_sys_socketcall
b __x64_sys_bind
b __x64_sys_listen
b __x64_sys_connect
b __x64_sys_accept4
通过截图,可以看出我们在gdb中分别找到了函数的路径信息。根据信息,我们找到函数的定义。
__ia32_compat_sys_socketcall实际的函数是compat.c中838行的COMPAT_SYSCALL_DEFINE2,定义如下:
COMPAT_SYSCALL_DEFINE2(socketcall, int, call, u32 __user *, args)
{
u32 a[AUDITSC_ARGS];
unsigned int len;
u32 a0, a1;
int ret;
if (call < SYS_SOCKET || call > SYS_SENDMMSG)
return -EINVAL;
len = nas[call];
if (len > sizeof(a))
return -EINVAL;
if (copy_from_user(a, args, len))
return -EFAULT;
ret = audit_socketcall_compat(len / sizeof(a[0]), a);
if (ret)
return ret;
a0 = a[0];
a1 = a[1];
switch (call) {
case SYS_SOCKET:
ret = __sys_socket(a0, a1, a[2]);
break;
case SYS_BIND:
ret = __sys_bind(a0, compat_ptr(a1), a[2]);
break;
case SYS_CONNECT:
ret = __sys_connect(a0, compat_ptr(a1), a[2]);
break;
case SYS_LISTEN:
ret = __sys_listen(a0, a1);
break;
case SYS_ACCEPT:
ret = __sys_accept4(a0, compat_ptr(a1), compat_ptr(a[2]), 0);
break;
case SYS_GETSOCKNAME:
ret = __sys_getsockname(a0, compat_ptr(a1), compat_ptr(a[2]));
break;
case SYS_GETPEERNAME:
ret = __sys_getpeername(a0, compat_ptr(a1), compat_ptr(a[2]));
break;
case SYS_SOCKETPAIR:
ret = __sys_socketpair(a0, a1, a[2], compat_ptr(a[3]));
break;
case SYS_SEND:
ret = __sys_sendto(a0, compat_ptr(a1), a[2], a[3], NULL, 0);
break;
case SYS_SENDTO:
ret = __sys_sendto(a0, compat_ptr(a1), a[2], a[3],
compat_ptr(a[4]), a[5]);
break;
case SYS_RECV:
ret = __compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3],
NULL, NULL);
break;
case SYS_RECVFROM:
ret = __compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3],
compat_ptr(a[4]),
compat_ptr(a[5]));
break;
case SYS_SHUTDOWN:
ret = __sys_shutdown(a0, a1);
break;
case SYS_SETSOCKOPT:
ret = __compat_sys_setsockopt(a0, a1, a[2],
compat_ptr(a[3]), a[4]);
break;
case SYS_GETSOCKOPT:
ret = __compat_sys_getsockopt(a0, a1, a[2],
compat_ptr(a[3]),
compat_ptr(a[4]));
break;
case SYS_SENDMSG:
ret = __compat_sys_sendmsg(a0, compat_ptr(a1), a[2]);
break;
case SYS_SENDMMSG:
ret = __compat_sys_sendmmsg(a0, compat_ptr(a1), a[2], a[3]);
break;
case SYS_RECVMSG:
ret = __compat_sys_recvmsg(a0, compat_ptr(a1), a[2]);
break;
case SYS_RECVMMSG:
ret = __sys_recvmmsg(a0, compat_ptr(a1), a[2],
a[3] | MSG_CMSG_COMPAT, NULL,
compat_ptr(a[4]));
break;
case SYS_ACCEPT4:
ret = __sys_accept4(a0, compat_ptr(a1), compat_ptr(a[2]), a[3]);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
__sys_blind代码:
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, &address);
if (!err) {
err = security_socket_bind(sock,
(struct sockaddr *)&address,
addrlen);
if (!err)
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
__sys_listen代码:
int __sys_listen(int fd, int backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
剩下函数代码不再赘述,总之,这两个例子是说明我们通过断点可以追踪到函数定义。
接下来一次次执行c命令,第一次执行后在qemu中输入replyhi,qemu又停止:
接着执行c,直到qemu再次运行:
直到__sys_accept4执行过后,才能继续输入hello,说明replyhi的执行一直到accept4,并且,我们可以看到尽管为connect设置了断点,但是却没有出现conect函数的中断,这是因为在这个过程中没有执行__sys_connect函数,我们只运行了一个menuos。我们为使用系统调用的__COMPAT_SYSCALL_DEFINE设置了断点,在执行__sys_bind,__sys_listen,__sys_acept4之前,明显看到这个函数都执行了一次,显然,套接口的bind,listen,accept功能都使用了系统调用。据此,我猜套接口绝大部分功能的实现都借用了系统调用。
通过这次实验,我懂得了gdb调试真是太好用了,可以很方便的观察程序运行的状态,并且对系统调用,socket套接口有了更深地认识。本次实验运用到了套接口的各种知识,比如套接口各个功能对应的函数等,还有系统调用的概念,gdb的各种命令的功能,这些东西都是随处可见的基本知识,不再赘述。