操作系统  办公  实用知识  设计  开发  WEB开发  移动开发  数据库  软件工程  网管  安全  管理  信息化  答疑  渠道 

FreeBSD 5内核源代码分析之copyin()实现原理

2007-6-18 网友评论 0 条 点击进入论坛

在内核中,用户态和内核态之间的数据拷贝主要通过copyin()和copyout() 两个函数完成。与普通的数据拷贝不同,用户态和内核态之间的数据拷贝必须考虑到用户给出的地址是否有效,即该地址是否有真正的地址映射。同时又要考虑到效率。因此也不可能对用户给出地址的每个字节检查一遍。

FreeBSD和Linux一样(linux中是copy_from_user()和copy_to_user()),都是先拷贝,出错以后再进行错误处理,有着异曲同工之妙。

本文所有代码,如未注明,均来自sys/i386/i386/support.s

copyin()由汇编语言写成,我们逐句来看。

代码: 

/*
* copyin(from_user, to_kernel, len) - MP SAFE
*/
ENTRY(copyin)
MEXITCOUNT
jmp *copyin_vector
 

copyin()有三个参数,from_user为用户态数据地址,to_kernel为内核缓冲区地址,len为数据长度。它们分别位于堆栈的位置是:
代码: 

from_user: 12(%esp)
to_kernel: 16(%esp)
len: 20(%esp)

这里copyin_vector定义为
代码: 

.globl copyin_vector
copyin_vector:
.long generic_copyin
 
定义一个数据copyin_vector,其值是generic_copyin

这里*copyin_vector的值就是generic_copyin,因此将跳转到generic_copyin。

代码: 

ENTRY(generic_copyin)
movl PCPU(CURPCB),%eax
movl $copyin_fault,PCB_ONFAULT(%eax)

先将curpcb地址存入%eax,然后将curpcb->pcb_onfault置为copyin_fault,这里copyin_fault也是一个程序标号,拷贝出错时将跳转到这里。下面我们将看到。
代码: 

pushl %esi
pushl %edi
movl 12(%esp),%esi /* CADdr_t from */
movl 16(%esp),%edi /* caddr_t to */
movl 20(%esp),%ecx /* size_t len */
 
分别将from_user, to_kernel, len存入寄存器%esi, %edi, %ecx

代码: 

/*
* make sure address is valid
*/
movl %esi,%edx
addl %ecx,%edx
jc copyin_fault
 
这几句看from_user+len是否有整数溢出。

代码: 

cmpl $VM_MAXUSER_ADDRESS,%edx
ja copyin_fault
 
相加之和是否在用户有效地址空间内。

代码: 

#if defined(I586_CPU) && defined(DEV_NPX)
ALIGN_TEXT
slow_copyin:
#endif
movb %cl,%al
shrl $2,%ecx /* copy longWord-wise */
cld
rep
movsl
 
先以4字节为单位拷贝。

代码: 

movb %al,%cl
andb $3,%cl /* copy remaining bytes */
rep
movsb

再拷贝剩余的字节(最多3字节),如果有的话。

代码: 

#if defined(I586_CPU) && defined(DEV_NPX)
ALIGN_TEXT
done_copyin:
#endif
popl %edi
popl %esi
xorl %eax,%eax
movl PCPU(CURPCB),%edx
movl %eax,PCB_ONFAULT(%edx)
ret
 

拷贝完成,恢复寄存器,并清除curpcb->pcb_onfault,返回。由于%eax用做函数返回值,这就是说,如果成功拷贝就返回事情总是有意外发生,如果用户给出的地址段
[from_user, from_user+len]
有问题的话,copyin()将发生异常,进入异常处理函数,

代码: 

(sys/i386/i386/trap.c)
---------------------------
/*
* Exception, fault, and trap interface to the FreeBSD kernel.
* This common code is called from assembly language IDT gate entry
* routines that prepare a suitable stack frame, and restore this
* frame after the exception has been processed.
*/

void
trap(frame)
struct trapframe frame;
{
struct thread *td = curthread;
struct proc *p = td->td_proc;
......

type = frame.tf_trapno;
......

if(it is user trap){
......

}else{ /* kernel trap */
......
switch (type) {
......

case T_PROTFLT: /* general protection fault */
case T_STKFLT: /* stack fault */
......
/* FALL THROUGH */
case T_SEGNPFLT: /* segment not present fault */
......
if (PCPU_GET(curpcb) != NULL &&
PCPU_GET(curpcb)->pcb_onfault != NULL) {
frame.tf_eip =
(int)PCPU_GET(curpcb)->pcb_onfault;
goto out;
}
break;
......
} /* end switch */
......
} /* end else */
......

out:
return;
}

对于copyin()产生的错误,"general protection fault"或"stack fault" 或"segment not present fault",都由这段代码处理。
由于在进入copyin()时设置了curpcb->pcb_onfault,这里将异常处理程序退出时继续运行的eip指针设置为copyin_fault,于是,当异常返回后,程序控制将到达copyin_fault。

代码: 

ALIGN_TEXT
copyin_fault:
popl %edi
popl %esi
movl PCPU(CURPCB),%edx
movl $0,PCB_ONFAULT(%edx)
movl $EFAULT,%eax
ret
 
在这里,恢复寄存器,清除curpcb->pcb_onfault,返回EFAULT.

注意,此时的核心栈与拷贝成功时的核心栈是相同的,这是因为前述trap()函数修改%eip后,程序只是将本来应该继续执行拷贝错误语句改为执行copyin_fault,异常处理程序返回后的核心栈并没有变化。因此,内核将顺着copyin()后的代码执行, 就好象根本没有发生过异常一样。

参考文献:
[1] Sinan "noir" Eren, "Smashing The Kernel Stack For Fun And Profit", phrack60-06

已有 0 位对此文章感兴趣的网友发布了看法    
我来评两句 用户名: 密码:
  匿名发表
今日推荐
技术文库(共有 45972 篇文章)
操作系统
办公软件
实用知识
网络管理
软件开发
WEB开发
软件工程
数据库
设计在线
信息安全
行业信息化
管理信息化
移动开发
重点推荐
电子杂志订阅
点击电子杂志名称查看样刊
输入E-mail地址即可订阅
E-mail