系统调用
每个程序在运行时都可以调用一系列的特殊“函数”。这些特殊的“函数”能够完成各种不同的系统任务——获取当前时间,打开读写文件等。这些特殊的函数有一个特殊的名字——系统调用。
调用系统调用
系统调用不同于普通函数,调用过程十分特殊。(以下讨论基于linux系统)其调用过程是:一 设置系统调用号。每个系统调用都有一个独特的系统调用号。通过系统调用号可以在几百个系统调用中确定系统调用。设置系统调用号要用到EAX寄存器。当调用open系统调用时,便是置EAX寄存器为5。movl %eax,$5。二 设置参数。系统调用都有参数。参数从0个到6个不等。每个参数都对应一个寄存器。参数一对应EBX,参数二对应ECX,参数三对应EDX,参数四对应ESI,参数五对应EDI,参数六对应EBP。有几个参数便设置几个寄存器。三 执行int 0x80。执行int 0x80指令,程序将切入内核执行,执行完成后返回继续执行int 0x80后一条指令。四 取返回值。返回值放在EAX寄存器中。从EAX中取出返回值,判断系统调用执行情况。
系统调用的封装
明白系统调用的调用过程后,可以简单对系统调用的调用过程进行封装。
以下为一段系统调用的封装代码。
#ifndef _SYSCALL_SYSDEP_H
#define _SYSCALL_SYSDEP_H
//使用宏生成函数
#define __syscall_return(type, res) \
do { \
if ((unsigned long)(res) >= (unsigned long)(-4095)) { \
errno = -(res); \
res = -1; \
} \
return (type) (res); \
} while (0)
#define _syscall0(syscall_name,type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name)); \
__syscall_return(type,__res); \
}
#define _syscall1(syscall_name,type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name),"b" ((long)(arg1))); \
__syscall_return(type,__res); \
}
#define _syscall2(syscall_name,type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
__syscall_return(type,__res); \
}
#define _syscall3(syscall_name,type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
#define _syscall4(syscall_name,type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
#define _syscall5(syscall_name,type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5))); \
__syscall_return(type,__res); \
}
#define _syscall6(syscall_name,type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5,type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) \
{ \
long __res; \
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
"0" ((long)(arg6))); \
__syscall_return(type,__res); \
}
#endif
_syscall0用于封装参数为0的系统调用;_syscall1用于封装参数为1的系统调用,以此类推。
我们以_syscall6为例来说明代码是如何封装系统调用的。
_syscall6是一个宏定义,其功能用于构建一个函数,函数名字是name,返回值类型为type。函数有6个参数,分别为arg1,arg2,arg3,arg4,arg5,arg6。参数类型分别是type1,type2,type3,type4,type5,type6。构建的函数为:
type name(type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6)
{
...
}
函数内是嵌入式汇编代码。
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##syscall_name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
"0" ((long)(arg6))); \
3-5行是执行汇编语句前的操作,2行是执行汇编语句后的操作。1行是执行的汇编语句。
3-5行中”b” ((long)(arg1))指将arg1的值放入EBX寄存器中,b对应EBX寄存器。”c” ((long)(arg2))指将arg2的值放入ECX寄存器中,c对应ECX寄存器。”d” ((long)(arg3))指将arg3的值放入EDX寄存器中,d对应EDX寄存器。”S” ((long)(arg4))指将arg4的值放入ESI寄存器中,S对应ESI寄存器。”D” ((long)(arg5))指将arg5的值放入EDI寄存器中,D对应EDI寄存器。”0” ((long)(arg6))指将arg6的值放入EAX寄存器中,0对应EAX寄存器(”=a”是0个出现的寄存器,a指EAX寄存器)。
第1行中指令将EAX寄存器的值放入EBP寄存器中,并置EAX的值为系统调用号。其中movl %1,%%eax指将_NR##syscall_name的值放入EAX中。##把参数变成字符串,_NR和syscall_name为##的参数。若syscall_name为open,则其_NR##syscall_name为__NR_open。__NR_open是定义在内核头文件unistd.h文件中的宏,对应数字5。参数设置完毕,执行int 0x80正式切入内核执行系统调用。
第2行”=a” (__res)指将EAX寄存器的值赋值给__res。也就是将系统调用的返回值给__res。
__syscall_return(type,__res)也是宏定义,它主要用于判断系统调用执行是否出错。若出错则置errno为-res,并返回-1。判断出错的方式是判断res作为无符号长整型是否大于-4095。若大于则出错。这是linux内核做出的规定。