发信人: luohandsome (苹果王子), 信区: KernelTech
标 题: [yc]读核感悟-伪装现场-系统调用参数
发信站: 水木社区 (Wed Dec 19 10:05:16 2007), 转信
内核支配了整个计算机的硬件资源,好像一位独裁者,高高在上。他有时候必须
像法官一样公正,有时候则必须像狐狸一样狡猾。伪装现场就是他的拿手好戏。
系统调用是很特别的函数,因为它里面实现了用户态到内核态的转换。应用程序
要创建新进程,不可能在用户态直接调用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
宏把一些寄存器压入内核堆栈的。压入的寄存器数量和顺序是一致的,它们恰好与s
truct 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()等等,也是用类似的手段。
--
联系方式:
QQ:61347014(Linux下上不了。。。)
MSN:luohandsome@hotmail.com(经常挂着)
※ 来源:·水木社区 newsmth.net·[FROM: 60.176.204.*]
===================================================
发信人: fishhunter (fishhunter), 信区: KernelTech
标 题: Re: [yc]读核感悟-伪装现场-系统调用参数
发信站: 水木社区 (Wed Dec 19 11:44:06 2007), 转信
流程是不是这个样子的:
用户空间程序将系统调用的参数,存入寄存器中。
int 0x80指令陷入到内核态,并将保存参数的这几个寄存器压栈(内核栈);
ENTRY(system_call)调用SAVE_ALL,将其他的一些寄存器压栈。
从而构建了系统调用的内核栈的映像。
还有一篇文章可以参考:
linux内核分析---系统调用实现代码分析
阅读(286) | 评论(0) | 转发(0) |