两个函数完成。与普通的数据拷贝不同,用户态和内核态之间的数据拷贝必须
考虑到用户给出的地址是否有效,即该地址是否有真正的地址映射。同时又要
考虑到效率。因此也不可能对用户给出地址的每个字节检查一遍。
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用做函数返回值,这就是说,如果成功拷贝就返回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
标签: