glibc源码分析之系统调用(一)
2017-08-10 20:22
330 查看
在编写本文时,glibc的最新版本是glibc-2.26。本文所有描述都是基于glibc-2.26。
系统调用
系统调用是程序员接触到的最底层的构建程序的组件。它们由内核实现,提供给程序调用。用户按照其调用规则可以实现调用。
glibc的封装方式
glibc使用了两种方式来封装系统调用。一种是由脚本生成。一种是.c文件。
使用.c文件封装系统调用,是因为封装过程比较复杂,除了按系统调用的调用规则来封装外,还要进行其他处理。而脚本生成则十分简单。只要按照系统调用的调用规则来封装即可。由于系统调用的调用规则相对简单且有相似性。所以glibc使用脚本生成封装文件。
脚本封装涉及了三种不同类型的文件。一,make-syscall.sh文件。二,syscall-template.S文件。三,syscalls.list文件。make-syscall.sh文件是shell脚本文件。该脚本文件读取syscalls.list文件内容,对syscalls.list文件中每一行数据进行解析。根据每一行数据生成一个.S文件。.S汇编文件封装了系统调用。syscall-template.S文件是系统调用封装的模板文件,包含了封装代码。每个生成的.S文件都将包含该文件的内容。
脚本封装
glibc支持不同的体系结构,不同的操作系统。本文只讨论i386机型下的linux操作系统。
i386机型linux操作系统下,make-syscall.sh文件在sysdeps/unix/make-syscall.sh。syscall-template.S文件在sysdeps/unix/syscall-template.S。syscalls.list文件则有多个,分别在sysdeps/unix/syscalls.list,sysdeps/unix/sysv/linux/syscalls.list,sysdeps/unix/sysv/linux/generic/syscalls.list,sysdeps/unix/sysv/linux/i386/syscalls.list。
make-syscall.sh文件首先读取syscalls.list文件中每一行内容。
syscalls.list文件的内容格式如下:
syscalls.list文件由许多行组成,每一行都对应一个.S文件。每一行可分为6列。其中File name列指定生成.S文件的文件名。Caller列指定调用者。Syscall name列指定系统调用的名字。Args列指定系统调用的参数类型和个数以及返回值的类型。Strong name列指定系统调用对应函数的名字。Weak names列指定系统调用对应函数的名字的别称。可以使用别称来调用函数。
make-syscall.sh文件在解析完一行内容后将输出一个.S文件。以chdir为例,生成的.S文件的内容为:
SYSCALL_NAME宏定义系统调用的名字。可以从Syscall name列获取。
SYSCALL_NARGS宏定义系统调用的参数个数。可以通过解析Args列获取。
SYSCALL_SYMBOL宏定义系统调用的符号名称。可以从Strong name列获取。
SYSCALL_CANCELLABLE宏在生成的所有.S文件中它都定义为0。
SYSCALL_NOERRNO宏定义为1,则封装代码没有出错返回。用于getpid这些没有出错返回的系统调用。可以通过解析Args列设置。
SYSCALL_ERRVAL宏定义为1,则封装代码直接返回错误号,不是返回-1并将错误号放入errno中。生成的所有.S文件中它都定义为0。
weak_alias (__chdir, chdir)定义了__chdir函数的别称,我们可以调用chdir来调用__chdir。 chdir从Weak names列获取。
.S文件中引用了模板文件syscall-template.S。
syscall-template.S文件内容如下:
我们以chdir系统调用为例,来分析syscall-template.S文件的内容。
由于SYSCALL_CANCELLABLE宏为0,所以汇编文件引用sysdep.h文件的内容。此处sysdep.h文件指sysdeps/unix/sysv/linux/i386/sysdep.h文件。SYSCALL_NOERRNO宏定义为0且SYSCALL_ERRVAL宏定义为0,所以真正执行的是30-32行。
PSEUDO
T_PSEUDO宏调用了PSEUDO宏。PSEUDO宏定义在sysdep.h文件中。我们来看一下它的定义:
ENTRY (name)宏定义如下:
ENTRY (name)定义了函数名,并声明该函数名是全局的。
DO_CALL (syscall_name, args)宏定义如下:
DO_CALL宏根据命令行参数个数的不同调用不同的宏。由于系统调用参数个数最多只有6个,所以有6种情况。
参数为0:
参数为1:
参数为2:
参数为3:
参数为4:
参数为5:
参数为6:
执行系统调用后,系统调用返回值放入eax寄存器中。此处比较eax寄存器值是否大于-4095,如果大于则表示系统调用执行错误,跳转到SYSCALL_ERROR_LABEL标签处。(为什么是-4095?这是linux操作系统的规定)
PSEUDO_END
T_PSEUDO_END宏调用了PSEUDO_END宏。PSEUDO_END宏定义在sysdep.h文件中。我们来看一下它的定义:
PSEUDO_END结束了整个汇编代码。
以上就是对chdir系统调用封装代码的分析。
其他系统调用的封装可能与chdir系统调用有所区别,读者可以自行查看。
系统调用
系统调用是程序员接触到的最底层的构建程序的组件。它们由内核实现,提供给程序调用。用户按照其调用规则可以实现调用。
glibc的封装方式
glibc使用了两种方式来封装系统调用。一种是由脚本生成。一种是.c文件。
使用.c文件封装系统调用,是因为封装过程比较复杂,除了按系统调用的调用规则来封装外,还要进行其他处理。而脚本生成则十分简单。只要按照系统调用的调用规则来封装即可。由于系统调用的调用规则相对简单且有相似性。所以glibc使用脚本生成封装文件。
脚本封装涉及了三种不同类型的文件。一,make-syscall.sh文件。二,syscall-template.S文件。三,syscalls.list文件。make-syscall.sh文件是shell脚本文件。该脚本文件读取syscalls.list文件内容,对syscalls.list文件中每一行数据进行解析。根据每一行数据生成一个.S文件。.S汇编文件封装了系统调用。syscall-template.S文件是系统调用封装的模板文件,包含了封装代码。每个生成的.S文件都将包含该文件的内容。
脚本封装
glibc支持不同的体系结构,不同的操作系统。本文只讨论i386机型下的linux操作系统。
i386机型linux操作系统下,make-syscall.sh文件在sysdeps/unix/make-syscall.sh。syscall-template.S文件在sysdeps/unix/syscall-template.S。syscalls.list文件则有多个,分别在sysdeps/unix/syscalls.list,sysdeps/unix/sysv/linux/syscalls.list,sysdeps/unix/sysv/linux/generic/syscalls.list,sysdeps/unix/sysv/linux/i386/syscalls.list。
make-syscall.sh文件首先读取syscalls.list文件中每一行内容。
syscalls.list文件的内容格式如下:
# File name Caller Syscall name Args Strong name Weak names accept - accept Ci:iBN __libc_accept accept access - access i:si __access access acct - acct i:S acct adjtime - adjtime i:pp __adjtime adjtime bind - bind i:ipi __bind bind chdir - chdir i:s __chdir chdir ......
syscalls.list文件由许多行组成,每一行都对应一个.S文件。每一行可分为6列。其中File name列指定生成.S文件的文件名。Caller列指定调用者。Syscall name列指定系统调用的名字。Args列指定系统调用的参数类型和个数以及返回值的类型。Strong name列指定系统调用对应函数的名字。Weak names列指定系统调用对应函数的名字的别称。可以使用别称来调用函数。
make-syscall.sh文件在解析完一行内容后将输出一个.S文件。以chdir为例,生成的.S文件的内容为:
#define SYSCALL_NAME chdir #define SYSCALL_NARGS 1 #define SYSCALL_SYMBOL __chdir #define SYSCALL_CANCELLABLE 0 #define SYSCALL_NOERRNO 0 #define SYSCALL_ERRVAL 0 #include <syscall-template.S> weak_alias (__chdir, chdir) hidden_weak (chdir)
SYSCALL_NAME宏定义系统调用的名字。可以从Syscall name列获取。
SYSCALL_NARGS宏定义系统调用的参数个数。可以通过解析Args列获取。
SYSCALL_SYMBOL宏定义系统调用的符号名称。可以从Strong name列获取。
SYSCALL_CANCELLABLE宏在生成的所有.S文件中它都定义为0。
SYSCALL_NOERRNO宏定义为1,则封装代码没有出错返回。用于getpid这些没有出错返回的系统调用。可以通过解析Args列设置。
SYSCALL_ERRVAL宏定义为1,则封装代码直接返回错误号,不是返回-1并将错误号放入errno中。生成的所有.S文件中它都定义为0。
weak_alias (__chdir, chdir)定义了__chdir函数的别称,我们可以调用chdir来调用__chdir。 chdir从Weak names列获取。
.S文件中引用了模板文件syscall-template.S。
syscall-template.S文件内容如下:
#if SYSCALL_CANCELLABLE # include <sysdep-cancel.h> #else # include <sysdep.h> #endif #define syscall_hidden_def(SYMBOL) hidden_def (SYMBOL) #define T_PSEUDO(SYMBOL, NAME, N) PSEUDO (SYMBOL, NAME, N) #define T_PSEUDO_NOERRNO(SYMBOL, NAME, N) PSEUDO_NOERRNO (SYMBOL, NAME, N) #define T_PSEUDO_ERRVAL(SYMBOL, NAME, N) PSEUDO_ERRVAL (SYMBOL, NAME, N) #define T_PSEUDO_END(SYMBOL) PSEUDO_END (SYMBOL) #define T_PSEUDO_END_NOERRNO(SYMBOL) PSEUDO_END_NOERRNO (SYMBOL) #define T_PSEUDO_END_ERRVAL(SYMBOL) PSEUDO_END_ERRVAL (SYMBOL) #if SYSCALL_NOERRNO T_PSEUDO_NOERRNO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) ret_NOERRNO T_PSEUDO_END_NOERRNO (SYSCALL_SYMBOL) #elif SYSCALL_ERRVAL T_PSEUDO_ERRVAL (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) ret_ERRVAL T_PSEUDO_END_ERRVAL (SYSCALL_SYMBOL) #else T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) ret T_PSEUDO_END (SYSCALL_SYMBOL) #endif syscall_hidden_def (SYSCALL_SYMBOL)
我们以chdir系统调用为例,来分析syscall-template.S文件的内容。
由于SYSCALL_CANCELLABLE宏为0,所以汇编文件引用sysdep.h文件的内容。此处sysdep.h文件指sysdeps/unix/sysv/linux/i386/sysdep.h文件。SYSCALL_NOERRNO宏定义为0且SYSCALL_ERRVAL宏定义为0,所以真正执行的是30-32行。
PSEUDO
T_PSEUDO宏调用了PSEUDO宏。PSEUDO宏定义在sysdep.h文件中。我们来看一下它的定义:
#undef PSEUDO #define PSEUDO(name, syscall_name, args) \ .text; \ ENTRY (name) \ DO_CALL (syscall_name, args); \ cmpl $-4095, %eax; \ jae SYSCALL_ERROR_LABEL
ENTRY (name)宏定义如下:
#define ENTRY(name) \ .globl C_SYMBOL_NAME(name); \ .type C_SYMBOL_NAME(name),@function; \ .align ALIGNARG(4); \ C_LABEL(name) \ cfi_startproc; \ CALL_MCOUNT #ifndef C_SYMBOL_NAME # define C_SYMBOL_NAME(name) name #endif #define ALIGNARG(log2) 1<<log2 # define C_LABEL(name) name##: # define cfi_startproc .cfi_startproc #define CALL_MCOUNT /* Do nothing. */
ENTRY (name)定义了函数名,并声明该函数名是全局的。
DO_CALL (syscall_name, args)宏定义如下:
#undef DO_CALL #define DO_CALL(syscall_name, args) \ PUSHARGS_##args \ DOARGS_##args \ movl $SYS_ify (syscall_name), %eax; \ ENTER_KERNEL \ POPARGS_##args #define PUSHARGS_0 /* No arguments to push. */ #define DOARGS_0 /* No arguments to frob. */ #define POPARGS_0 /* No arguments to pop. */ #define _PUSHARGS_0 /* No arguments to push. */ #define _DOARGS_0(n) /* No arguments to frob. */ #define _POPARGS_0 /* No arguments to pop. */ #define PUSHARGS_1 movl %ebx, %edx; L(SAVEBX1): PUSHARGS_0 #define DOARGS_1 _DOARGS_1 (4) #define POPARGS_1 POPARGS_0; movl %edx, %ebx; L(RESTBX1): #define _PUSHARGS_1 pushl %ebx; cfi_adjust_cfa_offset (4); \ cfi_rel_offset (ebx, 0); L(PUSHBX1): _PUSHARGS_0 #define _DOARGS_1(n) movl n(%esp), %ebx; _DOARGS_0(n-4) #define _POPARGS_1 _POPARGS_0; popl %ebx; cfi_adjust_cfa_offset (-4); \ cfi_restore (ebx); L(POPBX1): #define PUSHARGS_2 PUSHARGS_1 #define DOARGS_2 _DOARGS_2 (8) #define POPARGS_2 POPARGS_1 #define _PUSHARGS_2 _PUSHARGS_1 #define _DOARGS_2(n) movl n(%esp), %ecx; _DOARGS_1 (n-4) #define _POPARGS_2 _POPARGS_1 #define PUSHARGS_3 _PUSHARGS_2 #define DOARGS_3 _DOARGS_3 (16) #define POPARGS_3 _POPARGS_3 #define _PUSHARGS_3 _PUSHARGS_2 #define _DOARGS_3(n) movl n(%esp), %edx; _DOARGS_2 (n-4) #define _POPARGS_3 _POPARGS_2 #define PUSHARGS_4 _PUSHARGS_4 #define DOARGS_4 _DOARGS_4 (24) #define POPARGS_4 _POPARGS_4 #define _PUSHARGS_4 pushl %esi; cfi_adjust_cfa_offset (4); \ cfi_rel_offset (esi, 0); L(PUSHSI1): _PUSHARGS_3 #define _DOARGS_4(n) movl n(%esp), %esi; _DOARGS_3 (n-4) #define _POPARGS_4 _POPARGS_3; popl %esi; cfi_adjust_cfa_offset (-4); \ cfi_restore (esi); L(POPSI1): #define PUSHARGS_5 _PUSHARGS_5 #define DOARGS_5 _DOARGS_5 (32) #define POPARGS_5 _POPARGS_5 #define _PUSHARGS_5 pushl %edi; cfi_adjust_cfa_offset (4); \ cfi_rel_offset (edi, 0); L(PUSHDI1): _PUSHARGS_4 #define _DOARGS_5(n) movl n(%esp), %edi; _DOARGS_4 (n-4) #define _POPARGS_5 _POPARGS_4; popl %edi; cfi_adjust_cfa_offset (-4); \ cfi_restore (edi); L(POPDI1): #define PUSHARGS_6 _PUSHARGS_6 #define DOARGS_6 _DOARGS_6 (40) #define POPARGS_6 _POPARGS_6 #define _PUSHARGS_6 pushl %ebp; cfi_adjust_cfa_offset (4); \ cfi_rel_offset (ebp, 0); L(PUSHBP1): _PUSHARGS_5 #define _DOARGS_6(n) movl n(%esp), %ebp; _DOARGS_5 (n-4) #define _POPARGS_6 _POPARGS_5; popl %ebp; cfi_adjust_cfa_offset (-4); \ cfi_restore (ebp); L(POPBP1): # define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off #undef SYS_ify #define SYS_ify(syscall_name) __NR_##syscall_name #ifdef I386_USE_SYSENTER # ifdef SHARED # define ENTER_KERNEL call *%gs:SYSINFO_OFFSET # else # define ENTER_KERNEL call *_dl_sysinfo # endif #else # define ENTER_KERNEL int $0x80 #endif
DO_CALL宏根据命令行参数个数的不同调用不同的宏。由于系统调用参数个数最多只有6个,所以有6种情况。
参数为0:
movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL
参数为1:
movl %ebx, %edx; movl 4(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL movl %edx, %ebx;
参数为2:
movl %ebx, %edx; movl 8(%esp), %ecx; movl 4(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL movl %edx, %ebx;
参数为3:
pushl %ebx; movl 16(%esp), %edx; movl 12(%esp), %ecx; movl 8(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL popl %ebx
参数为4:
pushl %esi; pushl %ebx; movl 24(%esp), %esi; movl 20(%esp), %edx; movl 16(%esp), %ecx; movl 12(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL popl %ebx; popl %esi;
参数为5:
pushl %edi; pushl %esi; pushl %ebx; movl 32(%esp), %edi; movl 28(%esp), %esi; movl 24(%esp), %edx; movl 20(%esp), %ecx; movl 16(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL popl %ebx; popl %esi; popl %edi;
参数为6:
pushl %ebp; pushl %edi; pushl %esi; pushl %ebx; movl 40(%esp), %ebp; movl 36(%esp), %edi; movl 32(%esp), %esi; movl 28(%esp), %edx; movl 24(%esp), %ecx; movl 20(%esp), %ebx; movl $SYS_ify (syscall_name), %eax; ENTER_KERNEL popl %ebx; popl %esi; popl %edi; popl %ebp;
cmpl $-4095, %eax; \ jae SYSCALL_ERROR_LABEL
执行系统调用后,系统调用返回值放入eax寄存器中。此处比较eax寄存器值是否大于-4095,如果大于则表示系统调用执行错误,跳转到SYSCALL_ERROR_LABEL标签处。(为什么是-4095?这是linux操作系统的规定)
#define SYSCALL_ERROR_LABEL __syscall_error int __attribute__ ((__regparm__ (1))) __syscall_error (int error) { __set_errno (-error); return -1; }
PSEUDO_END
T_PSEUDO_END宏调用了PSEUDO_END宏。PSEUDO_END宏定义在sysdep.h文件中。我们来看一下它的定义:
#undef PSEUDO_END #define PSEUDO_END(name) \ SYSCALL_ERROR_HANDLER \ END (name) #define SYSCALL_ERROR_HANDLER #undef END #define END(name) \ cfi_endproc; \ ASM_SIZE_DIRECTIVE(name) # define cfi_endproc .cfi_endproc #define ASM_SIZE_DIRECTIVE(name) .size name,.-name;
PSEUDO_END结束了整个汇编代码。
以上就是对chdir系统调用封装代码的分析。
其他系统调用的封装可能与chdir系统调用有所区别,读者可以自行查看。
相关文章推荐
- glibc源码分析之系统调用(二)
- Android 调用系统相机部分源码分析
- Linux-2.6.25 select系统调用源码分析
- linux2.6.9 poll系统调用源码分析
- 系统调用fork()在powerpc上的源码分析
- linux-kernel 3.5.3Tcp系统调用,源码分析3-inet_create
- linux-kernel 3.5.3Tcp系统调用,源码分析7-listen系统调用
- 系统调用入口函数源码分析system_call——X86_64
- 几个系统调用分析 glibc中的malloc调用和共享内存原理
- linux-kernel 3.5.3Tcp系统调用,源码分析2-sys_socket & sock_create
- linux内核mount系统调用源码分析
- select系统调用源码分析
- 《第一篇 从linux 0.11系统初始化main.c的fork()函数调用分析内核源码》
- 几个系统调用分析 glibc中的malloc调用和共享内存原理
- linux内核mount系统调用源码分析 http://blog.csdn.net/wugj03/article/details/41958029
- 从glibc源码看系统调用原理
- linux-kernel 3.5.3Tcp系统调用,源码分析4-inet_init
- Linux内核源码分析-安装普通文件系统-mount系统调用
- linux-kernel 3.5.3Tcp系统调用,源码分析1-宏SYSCALL_DEFINE