【原创】x86下保存错误现场原理

. 如果发生异常或中断,cpu将做以下4件事:

1.确定所发生的中断或异常向量i(0~255之间)

2.通过 IDTR(中断描述表)寄存器,读取IDT表的第i项(或者叫第i个门)。

3.有效性检查:

a) “级检查,IDT 中第i 项段选择符中的DPLDPLCPL做比较

   DPL(3) > CPL(0),产生一个“通用保护异常(中断向量13),

   因为中断处理程序的特权级要大于引起中断的程序的特权级。

   但是这种可能性不大,因为中断处理程序一般运行在内核态,其特权级为0

b) “门”级检查,把CPL IDT 中第i 个门的DPL 相比较,如果CPL 大于DPL

    也就是当前特权级(3)小于这个门的特权级(0),CPU 就不能“穿过”这个门, 于是产生一个“通用保护”异常,这是为了避免用户应用程序访问特殊的陷阱门或 中断门。但是请注意,这种“门”级检查是针对一般的用户程序,而不包括外部 I/O 产生的中断或因CPU内部异常而产生的异常,也就是说,如果产生了中断或 异常,就免去了“门”级检查。

4.检测检测特权级是否变化

a) 当中断发生在用户态(特权级为3),而中断处理程序运行在内核态(特权级为0),

   特权级发生了变化,所以会引起堆栈的更换

b) 从用户堆栈切换到内核堆栈。而当中断发生在内核态时,

   CPU 在内核中运行时,则不会更换堆栈.

 

 

二. 由异常引起的中断,进入中断服务程序时,cpu要将当前EFLAGS寄存器的内容以及返回地址压入堆栈,返回地址是由段寄存器cs的内容和取指针EIP的内容共同组成的,还要把异常原因出错代码也压入堆栈。

用户栈的SS

用户栈的ESP

EFLAGS

用户空间的CS

EIP

错误码或0

函数地址

GS

FS

ES

DS

EAX

EBP

EDI

ESI

EDX

ECX

EBX

 

ENTRY(name)

RING0_EC_FRAME

pushl $do_handler_name //将异常处理函数压入栈中

CFI_ADJUST_CFA_OFFSET 4

jmp error_code //为异常处理程序调用和返回进行相关操作

CFI_ENDPROC

END(name)

 

 

#==================================================================

error_code:

/* the function address is in %gs’s slot on the stack */

pushl %fs

CFI_ADJUST_CFA_OFFSET 4  //4字节对齐  

/*CFI_REL_OFFSET fs, 0*/

pushl %es

CFI_ADJUST_CFA_OFFSET 4

/*CFI_REL_OFFSET es, 0*/

pushl %ds

CFI_ADJUST_CFA_OFFSET 4

/*CFI_REL_OFFSET ds, 0*/

pushl %eax

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET eax, 0

pushl %ebp

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET ebp, 0

pushl %edi      //C 函数可能用到的寄存器都保存在栈中

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET edi, 0

pushl %esi

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET esi, 0

pushl %edx

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET edx, 0

pushl %ecx

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET ecx, 0

pushl %ebx

CFI_ADJUST_CFA_OFFSET 4

CFI_REL_OFFSET ebx, 0

Cld // eflags 的方向标志,以确保edi 和esi 寄存器的值自动增

movl $(__KERNEL_PERCPU), %ecx

movl %ecx, %fs

UNWIND_ESPFIX_STACK

GS_TO_REG %ecx

movl PT_GS(%esp), %edi # get the function address

movl PT_ORIG_EAX(%esp), %edx # get the error code

movl $-1, PT_ORIG_EAX(%esp) # no syscall to restart

REG_TO_PTGS %ecx

SET_KERNEL_GS %ecx

movl $(__USER_DS), %ecx

movl %ecx, %ds //把内核数据段选择符装入ds 寄存器

movl %ecx, %es

TRACE_IRQS_OFF

movl %esp,%eax //中存放当前进程task_struct 结构的地址

call *%edi //调用这个异常处理程序

jmp ret_from_exception    

CFI_ENDPROC

 

#==================================================================

 

ret_from_exception:

preempt_stop(CLBR_ANY)

ret_from_intr:

GET_THREAD_INFO(%ebp) //获取当前线程数据结构

check_userspace: //检测是否是特权级别

movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS

movb PT_CS(%esp), %al

andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax

cmpl $USER_RPL, %eax

jb resume_kernel # not returning to v8086 or userspace

 

ENTRY(resume_userspace)

LOCKDEP_SYS_EXIT

  DISABLE_INTERRUPTS(CLBR_ANY) # make sure we dont miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

TRACE_IRQS_OFF

movl TI_flags(%ebp), %ecx

andl $_TIF_WORK_MASK, %ecx # is there any work to be done on

# int/exception return?

jne work_pending  //处理异常

jmp restore_all

END(ret_from_exception)

 

 

#==================================================================   

# perform work that needs to be done immediately before resumption

ALIGN

RING0_PTREGS_FRAME # cant unwind into user space anyway

   

work_pending:

testb $_TIF_NEED_RESCHED, %cl

jz work_notifysig

work_resched:

call schedule 

LOCKDEP_SYS_EXIT

DISABLE_INTERRUPTS(CLBR_ANY) # make sure we dont miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

TRACE_IRQS_OFF

movl TI_flags(%ebp), %ecx

andl $_TIF_WORK_MASK, %ecx # is there any work to be done other

# than syscall tracing?

jz restore_all

testb $_TIF_NEED_RESCHED, %cl

jnz work_resched

 

work_notifysig: # deal with pending signals and

        # notify-resume requests

xorl %edx, %edx

call do_notify_resume  // 用于通知当前线程恢复用户空间执行。

jmp resume_userspace_sig

END(work_pending)

 

#==================================================================   

# 在调度信号处理函数之前,把错误现场存到用户空间栈,然后在执行信号处理函数

#==================================================================   

 

 

do_signal –>do_signal –>handle_signal–>setup_rt_frame 

 

setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,

       sigset_t *set, struct pt_regs *regs)

{

int usig = signr_convert(sig);

int ret;

 

/* Set up the stack frame */

if (is_ia32) {

if (ka->sa.sa_flags & SA_SIGINFO)  //sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

ret = ia32_setup_rt_frame(usig, ka, info, set, regs);

else

ret = ia32_setup_frame(usig, ka, set, regs);

} else

ret = __setup_rt_frame(sig, ka, info, set, regs);

 

if (ret) {

force_sigsegv(sig, current);

return -EFAULT;

}

 

return ret;

}

 

 

ia32_setup_rt_frame() –>get_sigframe()


未经许可,不得转载!

发表评论

邮箱地址不会被公开。 必填项已用*标注