static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
{
struct thread_info *ti = current_thread_info();
unsigned int nr = (unsigned int)regs->orig_ax;
#ifdef CONFIG_IA32_EMULATION
current->thread.status |= TS_COMPAT;
#endif
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY) {
/*
* Subtlety here: if ptrace pokes something larger than
* 2^32-1 into orig_ax, this truncates it. This may or
* may not be necessary, but it matches the old asm
* behavior.
*/
nr = syscall_trace_enter(regs);
}
if (likely(nr < IA32_NR_syscalls)) {
/*
* It's possible that a 32-bit syscall implementation
* takes a 64-bit parameter but nonetheless assumes that
* the high bits are zero. Make sure we zero-extend all
* of the args.
*/
regs->ax = ia32_sys_call_table[nr](
(unsigned int)regs->bx, (unsigned int)regs->cx,
(unsigned int)regs->dx, (unsigned int)regs->si,
(unsigned int)regs->di, (unsigned int)regs->bp);
}
syscall_return_slowpath(regs);
}
ENTRY(entry_INT80_32)
ASM_CLAC
pushl %eax /* pt_regs->orig_ax */
SAVE_ALL pt_regs_ax=$-ENOSYS /* save rest */
/*
* User mode is traced as though IRQs are on, and the interrupt gate
* turned them off.
*/
TRACE_IRQS_OFF
movl %esp, %eax
call do_int80_syscall_32
.Lsyscall_32_done:
SYSCALL_NAME syscall name
SYSCALL_NARGS number of arguments this call takes
SYSCALL_SYMBOL primary symbol name
SYSCALL_CANCELLABLE 1 if the call is a cancelation point
SYSCALL_NOERRNO 1 to define a no-errno version (see below)
SYSCALL_ERRVAL 1 to define an error-value version (see below)
然后将syscall-template.S文件内容#include进来生成相应系统调用代码文件。
看syscall-template.S文件中执行系统调用代码片段
/* This is a "normal" system call stub: if there is an error,
it returns -1 and sets errno. */
T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
ret
T_PSEUDO_END (SYSCALL_SYMBOL)
/* Define an entry point visible from C.
There is currently a bug in gdb which prevents us from specifying
incomplete stabs information. Fake some entries here which specify
the current source file. */
#define ENTRY(name) \
STABS_CURRENT_FILE1("") \
STABS_CURRENT_FILE(name) \
ASM_GLOBAL_DIRECTIVE C_SYMBOL_NAME(name); \
ASM_TYPE_DIRECTIVE (C_SYMBOL_NAME(name),@function) \
.align ALIGNARG(4); \
STABS_FUN(name) \
C_LABEL(name) \
cfi_startproc; \
CALL_MCOUNT
/* For Linux we can use the system call table in the header file
/usr/include/asm/unistd.h
of the kernel. But these symbols do not follow the SYS_* syntax
so we have to redefine the `SYS_ify' macro here. */
#undef SYS_ify
#define SYS_ify(syscall_name) __NR_##syscall_name
网上大多数文章都是基于linux2.x版本的i386架构代码进行分析,而目前linux已经更新到4.x了,所以该文章基于linux4.14.3 stable version源码进行分析,源码下载地址
这里我关注的是X86架构,其他处理器架构的代码原理大同小异
从系统调用表开始进行分析,系统调用表在
/arch/x86/entry/syscalls/syscall_32.tbl
文件截取该文件部分内容如下
该表定义了系统调用号,系统调用名称和入口名称
那么该表什么时候用到了,在
/arch/x86
目录下中搜索syscall_32.tbl
,发现在/arch/x86/entry/syscalls/Makefile
中使用到该文件文件内容如下
根据Makefile内容可以看出make会根据
syscall_32.tbl
文件生成相应的头文件syscalls_32.h等那么具体会生成哪些文件呢?生成的文件内容又是什么呢?
动手来试一下,我在自己的虚拟机下(Ubuntu17.10 64bit)编译这份源码
在源码根目录下Make,结果如下
这里报错的原因编译内核源码需要先配置选项,但这不是我们关注的重点,可以看到虽然报错中断了,但是相应的头文件已经生成了,目的已经达到了
最终生成了以下几个文件
unistd_64.h部分内容如下
unistd_32.h部分内容如下
syscalls_32.h部分内容如下
unistd_64.h和unistd_32.h文件定义了系统调用号,用来关联相应的系统调用,而且从unistd_64.h和unistd_32.h文件可以看出64bit和32bit系统的相同系统调用号对应的系统调用却是不同的
这里syscalls_32.h文件的内容看起来像是一些宏,具体表示什么呢?
在x86目录下全局搜索
__SYSCALL_I386
,发现被以下文件引用个人感觉
user-offsets.c
和asm-offsets_64.c
可能不是那么重要,不影响分析,先记下,有空再研究而
syscall_32.c
和sys_call_table_32.c
文件内容是类似的且sys_call_table_32.c文件开头的注释引起了我的注意
大概意思就是该文件内容是从
syscall_32.c
复制过来并专门为UML/i386做了一些修改那么UML是什么意思,经查资料,UML全称
用户态Linux(User-mode Linux)
,将Linux编译为user mode使得该内核可以跑在另外一个操作系统上,详见维基既然是针对UM的,那我们分析syscall_32.c即可
syscall_32.c内容
可以看到
__SYSCALL_I386(nr, sym, qual)
是个宏,且被定义了2次,用来分别解释unistd_32.h
文件中的内容一开始宏
__SYSCALL_I386(nr, sym, qual)
展开后是个函数声明,以unistd_32.h文件中的__SYSCALL_I386(1, sys_exit, )
为例,将被展开为extern asmlinkage long sys_exit(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long) ;
接下来宏
__SYSCALL_I386(nr, sym, qual)
被展开为[nr] = sym,
,以unistd_32.h文件中的__SYSCALL_I386(1, sys_exit, )
为例,将被展开为[1] = sys_exit,
然后定义了一个名为ia32_sys_call_table的数组,下标为系统调用号,元素为相应系统调用函数指针
接下来查看ia32_sys_call_table在哪被调用,经查找,在/arch/x86/entry/common.c中被do_syscall_32_irqs_on调用
而do_syscall_32_irqs_on被同文件的定义的do_int80_syscall_32函数调用
do_int80_syscall_32再被/arch/x86/entry/entry_32.S文件调用
而ENTRY(entry_INT80_32)就是触发0x80 号中断后的入口
到这里linux内核的系统调用流程就差不多了
那么平时我们使用的glibc库是如何调用系统调用的呢?
从github上拉取glibc源码进行分析
首先,我们需要看系统调用在glibc的具体实现,具体实现源码是由 /sysdeps/unix/make-syscalls.sh脚本生成的,make-syscalls.sh脚本会读取syscalls.list文件为每个系统调用生成如下的一些宏
然后将syscall-template.S文件内容#include进来生成相应系统调用代码文件。
看syscall-template.S文件中执行系统调用代码片段
宏T_PSEUDO 被定义展开为另一个宏PSEUDO
宏PSEUDO定义在/sysdeps/i386/sysdep.h
宏ENTRY定义在/sysdeps/i386/sysdep.h
宏DO_CALL定义在/sysdeps/unix/sysv/linux/i386/sysdep.h
可以看到DO_CALL触发0x80 号中断
宏SYS_ify定义在/sysdeps/unix/sysv/linux/i386/sysdep.h
可以看到_NR##syscall_name其实就是分析linux源码时候unistd_32.h文件所定义的系统调用号
根据代码理解如下: ENTRY(name)宏使得系统调用名称在C层是可见的,然后该系统调用具体实现就是DO_CALL (syscall_name, args);
参考: http://docs.huihoo.com/joyfire.net/6-1.html http://blog.csdn.net/gatieme/article/details/50779184 https://zhuanlan.zhihu.com/p/28984642 https://bbs.pku.edu.cn/v2/post-read.php?bid=13&threadid=16042150&page=a&postid=16206732