本文所有描述都是基于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源码分析之系统调用(二) | 求索阁