在内核中,用户态和内核态之间的数据拷贝主要通过copyin()和copyout()两个函数完成。与普通的数据拷贝不同,用户态和内核态之间的数据拷贝必须考虑到用户给出的地址是否有效,即该地址是否有真正的地址映射。同时又要考虑到效率。因此也不可能对用户给出地址的每个字节检查一遍。FreeBSD和Linux一样(linux中是copy_from_user()和copy_to_user()),
都是先拷贝,出错以后再进行错误处理,有着异曲同工之妙。本文所有代码,如未注明,均来自sys/i386/i386/support.scopyin()由汇编语言写成,我们逐句来看。
代码:
/* * 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用做函数返回值,这就是说,如果成功拷贝就返回0。事情总是有意外发生,如果用户给出的地址段[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
阅读(3388) | 评论(0) | 转发(0) |