Linux kernel 分析之八:系统调用参数

来源:互联网 发布:js输入框输入触发事件 编辑:程序博客网 时间:2024/05/09 05:46

内核支配了整个计算机的硬件资源,好像一位独裁者,高高在上。他有时候必须像法官一样公正,有时候则必须像狐狸一样狡猾。伪装现场就是他的拿手好戏。
系统调用是很特别的函数,因为它里面实现了用户态到内核态的转换。应用程序要创建新进程,不可能在用户态直接调用sys_fork()。这就需要内核为sys_fork()伪装一下调用现场。
比如fork()系统调用,它有一个简洁得不能再简洁的接口。不过它在内核中的对应函数中的声明却是:
asmlinkage int sys_fork(struct pt_regs regs)
如果有兴趣看早期的Linux源代码版本(0.95),它的声明是这样的:int sys_fork(long ebx,long ecx,long edx,long esi, long edi, long ebp, long eax, long ds,long es, long fs, long gs, long orig_eax,
long eip,long cs,long eflags,long esp,long ss)
这些参数是如何来的呢。为什么用户态参数与内核态参数不一致?

又如clone(),vfork()这几个系统调用在用户态的参数个数和类型都不一样。但在内核态都致。
asmlinkage int sys_clone(struct pt_regs regs)asmlinkage int sys_vfork(struct pt_regs regs)
接下去,我们可以看到,内核是多么巧妙地设置堆栈,让内核的函数感觉就好像上层函数在调用它一样。
我们很容易得知,这些参数是在系统调用进入内核时由int 0x80指令和SAVE_ALL宏把一些寄存器压入内核堆栈的。压入的寄存器数量和顺序是一致的,它们恰好与struct pt_regs一一对应。从这个角度讲,所有的系统调用获得的参数是形式是一样的。026 struct pt_regs {027         long ebx;028         long ecx;029         long edx;030         long esi;031         long edi;032         long ebp;033         long eax;034         int  xds;035         int  xes;036         long orig_eax;037         long eip;038         int  xcs;039         long eflags;040         long esp;041         int  xss;
042 };
其中xss到eip由int指令压入内核堆栈。orig_eax到ebx为ENTRY(system_call)压入堆

栈。orig_eax为系统调用号。eax作为返回值。ebp~ebx作为系统调用的参数。
linux的系统调用在内核中对应函数如sys_fork()的声明前都有一个asmlinage的宏。它被定义为:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
也就是说,所有的参数都通过堆栈传递。注意,这里的堆栈传递已经在内核态了,与系统调用参数通过寄存器传递并不矛盾(那个是之前在用户态到内核态的切换过程中)。
这样就意味着我们只要巧妙地设置好堆栈,让sys_fork()产生一种错觉,好像是上层的一个函数直接调用sys_fork()一样。事实上sys_fork()也的确可以在内核态直接调用。
我们又观察到:内核中_syscall0,_syscall1,_syscall2,。。。这些宏用来封装系统调用(只是内核自己用,glibc不用这些宏而已。)传递参数的方式是这样的:参数1~参数6正好放在ebx ecx edx esi edi ebp中,这恰好与pt_regs中的顺序相对应。为什么采用这样的一种顺序呢?
1.这与编译器的编译规则有关。标准的C函数编译时,总是从右往左依次把参数压入堆栈。执行到函数内部时,就根据这样的规则依次从堆栈中取出参数。所以在内核中也不例外。当应用程序执行系统调用,进入到内核态中的ENTRY(system_call)调用
call *sys_call_table(,%eax,4)
时,所有的参数都在堆栈中准备就绪。
具体这些参数怎么理解,就取决于函数的定义了。
如果认为内核堆栈中放的是个结构体pt_regs,就可以定义为asmlinkage int sys_fork(struct pt_regs regs)如果认为内核堆栈中放的是一个个整数,就可以定义为asmlinkage int sys_fork(long ebx,long ecx,long edx,long esi, long edi, long ebp, long eax, long ds,long es, long fs, long gs, long orig_eax,
long eip,long cs,long eflags,long esp,long ss)
其它的系统调用也类似。
通过巧妙的构造堆栈,达到调用内核函数的目的。
除了系统调用,还有其它函数如do_page_fault()等等,也是用类似的手段。

0 0
原创粉丝点击