在读代码的时候,Jump指令用的特别多, 在中断/异常, 虚实地址转换, 任务切换等等等等,都用到了Jump指令,今天我们来讨论一下究竟Jump都做了什么事情.
下面用伪指令来描述Jump做了什么事情, 又是怎么区分这些事件的
JMP(SelectorType Selector, int Offset) { SegAttributes Attributes; SelectorType GSelector; int Base, Limit, GOffset; if((Selector & 0FFFCh) == 0) SegmentException($GP, 0); ReadDescriptor(Selector,&Attributes,&Base,&Limit,&GSelector,&GOffset); if(Attributes.DType){ CSDescriptorLoad(Selector, Attributes,Base,Limit, $GP); if(Offset > CS.Limit) SegmentException($GP,0); CS.Selector= Selector; CS.Selector.RPL = CPL; EIP = Offset; } else{ if((Attributes.DPL < CPL) || (Attributes.DPL < Selector.RPL)) SegmentException($GP, Selector); switch(Attributes.Type){ case 1: if(Attributes.P == 0) SegmentException($NP, Selector); TaskSwitch286(Selector, Attributes, Base, Limit, 0); break; case 5: if(Attributes.P == 0) SegmentException($NP, Selector); TaskGate(GSelector, 0); break; case 9: if(Attributes.P == 0) SegmentException($NP, Selector); TaskSwitch(Selector, Attributes, Base, Limit, 0); break; case 4: if(Attributes.P == 0) SegmentException($NP, Selector); JumpGate286(GSelector, GOffset, $GP); break; case 12: if(Attributes.P == 0) SegmentException($NP, Selector); JumpGate386(GSelector, GOffset, $GP); break; Default: SegmentException($GP, Selector); } } }
1. 装入的参数为Selector 和 Offset
3. 定义变量Attribute, 对应段描述符中的属性字段
4. 定义变量Gselector, 如果是类型为门, 则此描述符存放的是选择子Selector和偏移Offset, 再根据Gselector找到对应的段描述符.
5. 定义基地址, 界限和Goffset(偏移, 用于系统段或门)
6-7 选择子为空, 产生段异常.
8. 根据选择子, 返回段描述符中的属性, 基地址和段界限. 如果为门, 则返回门选择子和偏移.
9. 如果描述符为存储段
10. CSDescriptorLoad函数会进行特权级检查,如果出错,则会出现段异常. 再把Attribute,Base, Limit装入到CS 投影寄存器.(经常说的清CPU的prefetch queue)
11-12检查是否越界, 是则产生段异常
13. 单独装入Selector.(MOV指令无法修改CS寄存器,只能靠JUMP, CALL和IRET指令)
14. RPL装入CPL, 可以仔细思考一下为什么这么做?
15. EIP装入Offset. 和上面的CS组合起来, 就是CS:EIP, 这就实现了跳转了.
17. 如果描述符类型为系统段或门
18-19特权级检查, 出错则调用段异常函数
20. 根据属性中的类型, 进行不同的处理
21-25 调用TaskSwitch286进行任务切换, 参数0表示无链接
26-30 调用任务门TaskGate() 函数, 0 表示 无链接
31-35 调用任务切换TaskSwitch() 函数
36-40 调用286 调用门JumpGate286()
41-45调用386调用门JumpGate386()