现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

glibc源码分析之系统调用(一)

2019-10-21 18:53 工业·编程 ⁄ 共 8749字 ⁄ 字号 评论 1 条

本文所有描述都是基于glibc-2.26。

系统调用

系统调用是程序员接触到的最底层的构建程序的组件。它们由内核实现,提供给程序调用。用户按照其调用规则可以实现调用。

glibc的封装方式

glibc使用了两种方式来封装系统调用。一种是由脚本生成。一种是.c文件

使用.c文件封装系统调用,是因为封装过程比较复杂,除了按系统调用的调用规则来封装外,还要进行其他处理。而脚本生成则十分简单。只要按照系统调用的调用规则来封装即可。由于系统调用的调用规则相对简单且有相似性。所以glibc使用脚本生成封装文件。

脚本封装涉及了三种不同类型的文件

1)make-syscall.sh文件

  make-syscall.sh文件是shell脚本文件。该脚本文件读取syscalls.list文件内容,对syscalls.list文件中每一行数据进行解析。根据每一行数据生成一个.S文件。

2)syscall-template.S文件

.S汇编文件封装了系统调用。

3)syscalls.list文件。

  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系统调用有所区别,读者可以自行查看。

目前有 1 条留言    访客:0 条, 博主:0 条 ,引用: 1 条

    外部的引用: 1 条

    • glibc源码分析之系统调用(二) | 求索阁

    给我留言

    留言无头像?