您的位置:首页 > 运维架构 > Linux

Linux内核分析-异常处理

2010-10-09 13:15 127 查看
对IDT的初始化就懒得记录了.以后有心情就补.

文件:arch/x86/kernel/entry_32.S

CPU异常的时候有的异常会压入错误码有的不,为了保证栈格式统一,对于没有压入错误码的异常内核会自己压入一个0.

]ENTRY(overflow)
RING0_INT_FRAME
pushl $0
CFI_ADJUST_CFA_OFFSET 4
pushl $do_overflow
CFI_ADJUST_CFA_OFFSET 4
jmp error_code
CFI_ENDPROC
END(overflow)


对于有错误吗的就略过压0这一步.代码中的宏基本不用看,貌似不太影响阅读,不过我也真的想弄清这些宏的作用,有谁知道麻烦指导一下.

]ENTRY(invalid_TSS)
RING0_EC_FRAME
pushl $do_invalid_TSS
CFI_ADJUST_CFA_OFFSET 4
jmp error_code
CFI_ENDPROC
END(invalid_TSS)


我们知道对x86CPU会在异常的时候自动向栈中顺序压入ss, esp, eflags, cs, eip,所以就可以得出当前内核栈的情况如下

高地址
+-----------+
|    ss     |
+-----------+
|    esp    |
+-----------+
|   eflags  |
+-----------+
|    cs     |
+-----------+
|    eip    |
+-----------+
| errcode|0 |
+-----------+
|  retaddr  |
+-----------+<--esp
|    xxx    |
+-----------+
|    xxx    |
+-----------+
低地址


上面的代码接着又调往error_code处执行,在跳往error_code前压入了一个地址,为什么要在jmp前压入这个地址一会儿再讲,现在先来看看error_code处的代码.可以看到其实error_code的代码是嵌在处理page_fault异常的代码内部的,个人觉得这绝不是写这段代码的人偷懒为了在page_fault中少写一个jmp error_code.我认为因为page_fault这个异常应该是内核中最经常发生的一个异常,所以省去的这一条指令也体现了Linux内核设计者们在细微之处的周到用心.

]ENTRY(page_fault)
RING0_EC_FRAME
pushl $do_page_fault
CFI_ADJUST_CFA_OFFSET 4
ALIGN
error_code:
/* the function address is in %gs's slot on the stack */
pushl %fs
CFI_ADJUST_CFA_OFFSET 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
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
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
movl %ecx, %es
TRACE_IRQS_OFF
movl %esp,%eax                  # pt_regs pointer
call *%edi
jmp ret_from_exception
CFI_ENDPROC
END(page_fault)


好了我们直接来看error_code处的代码,从1218行到1245行都是一系列的push操作,非常简单.这个时候栈的变化情况我们也能了解清楚了.

高地址
+-----------+
|    ss     |
+-----------+
|    esp    |
+-----------+
|   eflags  |
+-----------+
|    cs     |
+-----------+
|    eip    |
+-----------+
| errcode|0 |
+-----------+
|  retaddr  |
+-----------+
|    fs     |
+-----------+
|    es     |
+-----------+
|    ds     |
+-----------+
|    eax    |
+-----------+
|    ebp    |
+-----------+
|    edi    |
+-----------+
|    esi    |
+-----------+
|    edx    |
+-----------+
|    ecx    |
+-----------+
|    ebx    |
+-----------+ <--esp
|    xxx    |
+-----------+
低地址


1249-1250:把__KERNEL_PERCPU存入到fs中,__KERNEL_PERCPU是GDT中第27(offset to per-cpu data area)个描述符的偏移值,貌似是给SMP用的.

1252:把gs存入ecx中

1253:意思是把栈中gs的位置的值存入edi中,你也许会说你在栈上找不到gs,其实存gs的位置就是funcaddr的位置.

1254-1255:把栈上的错误码(errcode)存入edx中,并把栈上的该值改写为-1.

1256:把ecx中的值存入栈上的gs的位置,即funcaddr的位置,通过上面分析,ecx中的值其实就是gs.

1258-1260:将ds和es设为用户数据段.

1262:就是将栈的位置存入eax.

现在我们整理一下:

栈上的errcode已经被设为-1,funcaddr已经被设为gs,而errcode和funcaddr分别被存入了edx和edi中,1263行就是要跳到edi指向的地方也就是funcaddr处执行.我们想如果funcaddr处理的函数需要访问这个栈怎么办呢?没事,已经通过eax传递这个参数了.不难看出funcaddr就是在压错误码之后压入的那个参数,当funcaddr完成后会返回到1264行执行jmp ret_from_exception.

To Be Continued...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: