前略

不同指令集架构下的跳转

调用门

门调用过程和 CALL 类型的特权检查差不多

多了一层跳转,所以也多了一层检查

门描述符的 DPL 字段描述了能够访问的数字最大(也就是权限最小)的 Current 特权

对于 CALL 和 JMP 权限检查也有区别:下面比较的是数值

  • 对于两者都要求 CPLDPL(门的),RPLDPL(门的)
  • CALL 对一致或者非一致都只要求 DPLCPL
  • JMP 对一致性的代码段要求 DPLCPL,但是非一致性要求 DPL=CPL(也就是不能转移到更高权限去了)

一致代码段

堆栈转换

每当调用门将程序控制转移到一个更高级别的非一致性代码段中的时候,CPU 会自动切换到目的代码段特权级的堆栈去。

每个任务必须定义最多 4 个栈(就是从 0 到 3)。r3 切换的时候段选择符和栈指针会被存在被调用过程的栈上。r0 等高权的堆栈初始指针放在 TSS 段中(只读),切换的时候创建,返回的时候销毁,在调用的时候栈上也会存调用过程的 SS,esp,CS,eip 等等,还有调用参数和 ELFLAG(处理器状态如中断),可能产生的出错码(#PF,#GF)

高地址 → 用户态堆栈(SS3:ESP3)
          ------------
          EFLAGS      ← 中断前的处理器状态
          CS:EIP      ← 返回地址
          出错码(若有)
低地址 → 内核态堆栈(SS0:ESP0)

TSS

一个典型的改变特权的调用过程如下:

  1. 使用目的代码段 DPL 从 TSS 相应权限中选择新的栈的指针(从 TSS 中读取新栈的指针)。在此期间越界会导致 TSS 无效异常
  2. 检查段描述符特权和类型是否有效,若无效也返回 TSS 无效异常
  3. 压栈 SS 和 ESP,加载新的
  4. 把调用门描述符中指定个数的参数从调用过程栈复制到新栈中
  5. 把返回指令指针 CS 和 EIP 压栈,加载新的,然后开始执行

从调用过程返回

  • 近返回仅进行界限检查
  • 对于相同特权级的远返回,从栈中弹出代码段的选择符和返回指令指针(虽然是有效的,但是还是会进行特权检查)
  • 对于不同特权级的返回(仅允许返回到低特权级):
    1. 检查栈上保存的 CS 中的 RPL 确定是否要返回低特权级
    2. 弹出 CS 和 EIP,检查
    3. 如果 RET 包含一个参数个数操作符并且返回操作会改变特权级,那就把参数那些位置跳过
    4. 弹出 SS 和 ESP,加载
    5. 同 3(由调用者清理栈)把参数从原来的栈上弹出
    6. 检查 DS,ES,FS,GS 内容,如果其中有指向 DPL 小于新 CPL 的段,CPU 会用 NULL 选择符加载这个段寄存器(越权了)(一致代码除外)