读Kernel感悟-伪装现场-系统调用参数

文章来源:http://www.top-e.org/jiaoshi/class/


内核支配了整个计算机的硬件资源,好像一位独裁者,高高在上。他有时候必须像法官一样公正,有时候则必须像狐狸一样狡猾。伪装现场就是他的拿手好戏。

系统调用是很特别的函数,因为它里面实现了用户态到内核态的转换。应用程序要创建新进程,不可能在用户态直接调用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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值