您的位置:首页 > 其它

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文件的内容格式如下:

# 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系统调用有所区别,读者可以自行查看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: