记录socket函数调用分析

来源:互联网 发布:java程序设计案例教程 编辑:程序博客网 时间:2024/05/19 03:20

需要下载 glibc, linux-2.6.26源码,使用source insight建立相应工程。

http://ftp.gnu.org/gnu/libc/

http://www.kernel.org/pub/linux/kernel/v2.6/


先看glibc中的库函数socket(),socket.c

#include <errno.h>
#include <sys/socket.h>

/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
int
__socket (domain, type, protocol)
     int domain;
     int type;
     int protocol;
{
  __set_errno (ENOSYS);
  return -1;
}

weak_alias (__socket, socket)
stub_warning (socket)
#include <stub-tag.h>

Weak Alias是GCC编译器扩展内容,指定了函数的weak属性。编译glibc时将“函数别名”属性保存在weak symbol中,真实的定义在其他地方。这里的__socket就是库函数socket的别名,真实的定义是用汇编语言实现,见glibc/sysdeps/unix/sysv/linux/i386/socket.S

这里只列出和我们讨论相关的部分内容

//I386系统内核

#define ENTER_KERNEL int $0x80

#define SYS_ify(syscall_name) __NR_##syscal_name

#define P(a, b) P2(a, b)

#define P2(a, b) a##b

......

ENTRY (__socket)
......
movl $SYS_ify(socketcall), %eax/* System call number in %eax.  */
/* Use ## so `socket' is a separate token that might be #define'd.  */
movl $P(SOCKOP_,socket), %ebx/* Subcode is first arg to syscall.  */
lea 4(%esp), %ecx/* Address of args is 2nd arg.  */
......
        /* Do the system call trap.  */
ENTER_KERNEL

......


展开宏之后

......

ENTRY (__socket)
......
movl $__NR_socketcall, %eax /* System call number in %eax.  */
/* Use ## so `socket' is a separate token that might be #define'd.  */
movl $SOCKOP_socket, %ebx/* Subcode is first arg to syscall.  */
lea 4(%esp), %ecx/* Address of args is 2nd arg.  */
......
        /* Do the system call trap.  */
int $0x80

......

当 glibc 在Linux 系统中编译时,__NR_socketcall会从内核include/asm-x86/unistd_32.h中找到它的定义作为系统调用号

#define __NR_socketcall 102

SOCKOP_socket的定义在glibc/sysdeps/unix/sysv/linux/socketcall.h

#define SOCKOP_socket 1
#define SOCKOP_bind 2
#define SOCKOP_connect 3
#define SOCKOP_listen 4
#define SOCKOP_accept 5
#define SOCKOP_getsockname 6
#define SOCKOP_getpeername 7
#define SOCKOP_socketpair 8
#define SOCKOP_send 9
#define SOCKOP_recv 10
#define SOCKOP_sendto 11
#define SOCKOP_recvfrom 12
#define SOCKOP_shutdown 13
#define SOCKOP_setsockopt 14
#define SOCKOP_getsockopt 15
#define SOCKOP_sendmsg 16
#define SOCKOP_recvmsg 17
#define SOCKOP_paccept 18

汇编代码将这个宏值作为socket的调用号保存到寄存器ebx中,代码lea 4 (%esp),%ecx是将调用socket时的参数地址保存到寄存器ecx中。


当服务器程序运行后,调用socket函数就会执行glibc中的上述代码,从而将系统调用号102保存到寄存器eax中,然后执行软件中断指令int $0x80到达系统调用的总入口system_call;汇编实现,

见linux-2.6.26/arch/x86/kernel/entry_32.S

ENTRY(system_call)
......
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp)# store the return value

见linux-2.6.26/arch/x86/kernel/syscall_table_32.S

ENTRY(sys_call_table)

.......

.long sys_socketcall/*102*/

见linux-2.6.26/net/socket.c


asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0, a1;
int err;


if (call < 1 || call > SYS_RECVMSG)
return -EINVAL;


/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
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;

......

sys_socketcall是Linux内核socket的函数调用入口,函数的两个参数分别通过ebx和ecx来传递,参数call是具体的socket的调用号,ebx寄存器保存值为1,也是SYS_SOCKET的值。见linux-2.6.26/include/linux/net.h

copy_from_user将参数从用户空间拷贝到内核空间,是因为系统调用函数在内核空间而服务器程序在用户空间。


nargs[]数组定义,见linux-2.6.26/net/socket.c

#define AL(x) ((x)*sizeof(unsigned long))

static const unsigned char nargs[18] = {

AL(0), AL(3),.....

};

对应处为AL(3),所以大小为12字节。args3个参数AF_INET,SOCK_STREAM,0。

注意,内核include/linux/net.h和glibc中sysdeps/unix/sysv/linux/socketcall.h中对调用号定义完全一致

#define SYS_SOCKET 1/* sys_socket(2)*/
#define SYS_BIND 2/* sys_bind(2)*/
#define SYS_CONNECT 3/* sys_connect(2)*/
#define SYS_LISTEN 4/* sys_listen(2)*/
#define SYS_ACCEPT 5/* sys_accept(2)*/
#define SYS_GETSOCKNAME 6/* sys_getsockname(2)*/
#define SYS_GETPEERNAME 7/* sys_getpeername(2)*/
#define SYS_SOCKETPAIR 8/* sys_socketpair(2)*/
#define SYS_SEND 9/* sys_send(2)*/
#define SYS_RECV 10/* sys_recv(2)*/
#define SYS_SENDTO 11/* sys_sendto(2)*/
#define SYS_RECVFROM 12/* sys_recvfrom(2)*/
#define SYS_SHUTDOWN 13/* sys_shutdown(2)*/
#define SYS_SETSOCKOPT 14/* sys_setsockopt(2)*/
#define SYS_GETSOCKOPT 15/* sys_getsockopt(2)*/
#define SYS_SENDMSG 16/* sys_sendmsg(2)*/
#define SYS_RECVMSG 17/* sys_recvmsg(2)*/


总结

函数调用

sys_socketcall (glibc) ->sys_socket (linux kernel)

asmlinkage long sys_socket(int family, int type, int protocol)//见linux-2.6.26/net/socket.

{

.......

retval = sock_create(family, type, protocol, &sock);

......

retval = sock_map_fd(sock);

.......

}

sock_create创建socket,sock_map_fd为新建的socket在网络文件系统中申请文件号和文件描述符结构。

原创粉丝点击