1. Protection Overview

使用保护机制时 , 每个内存引用的检查都在内存 cycle 之前进行 . 由于检查和地址转换过程并行执行 , 没有性能开销 . 执行的保护检查可以分成下面几类 :

  • 限制检查
    限制检查根据段描述符内的一些标志位和段限制完成 .
    64 位模式 , 处理器不会对代码段和数据段执行运行时检查 .

  • 类型检查
    处理器操作段选择器和段描述符时多次检查类型信息 :

    • 加载一个段选择器到段寄存器时 , 特定类型的段选择器只能加载到特定的段寄存器
    • 加载一个段选择器到 LDTR 或 TR
    • 指令访问描述符已经加载到段寄存器中的段
    • 指令的操作数包含一个段选择器 , 这些指令只能访问特定类型的段或门
    • 某些内部操作期间
  • 特权级检查
    处理器的段保护机制识别 4 个特权级 , 0-3 , 用于阻止较低特权级的程序访问较高特权级中的段 . 处理器支持三种类型的特权级 :

    1. current privilege level ( CPL ) , 当前执行程序的特权级 , 保存在 CS 和 SS 段寄存器的 bit 0 和 1 .
    2. descriptor privilege level ( DPL ) , 段或门的特权级 , 保存在段或门描述符的 DPL 域 . 当前执行的代码访问一个段或门时 , 当前代码的段或门的 DPL 和要访问的段或门的选择器的 CPL , RPL 比较 , 使用方法取决于被访问的段或门的类型 .
    3. requested privilege level ( RPL ) , 如果段选择器的 RPL 高于 CPL , RPL 会覆盖 CPL ; 反之亦然 .

    加载一个段描述符的段选择器到段寄存器时检查特权级 , 数据访问执行的检查和程序控制导致的代码段切换执行的检查是不同的 .

    下列情况都会检查特权级 :

    1. 访问数据段时
      CPL 影响程序可以访问的数据段特权级 , 段选择器的 RPL 可以覆盖程序的 CPL , 进而重载程序可以访问的数据段特权级 .
      段选择器的 RPL 可以由软件设置 , CPL 为 3 的应用程序可以设置一个段选择器的 RPL 为 0 .

      要访问代码段中的数据 , 可以将代码段的段选择器加载到数据段寄存器 , 或者使用 CS 覆盖前缀读取已经加载到 CS 寄存器的代码段 .

    2. 加载 SS 寄存器时
      和栈段相关的特权级必须和 CPL 匹配 , 即程序的 CPL , 栈段选择器的 RPL , 栈段描述符的 DPL 必须相同 , 否则产生 #GP 异常 .

    3. 在代码段之间切换程序控制时
      要从一个代码段切换到另一个 , 必须加载目的代码段的段选择器到 CS , 加载过程的一部分就是特权级检查 . 检查成功加载 CS , 切换到新的代码段 , 执行 EIP 指向的指令 .

      代码段的切换通过 JMP , CALL , RET , SYSENTER , SYSCALL , SYSRET , INT n , IRET 指令 , 以及异常和中断机制完成 .

      1. 近的 JMP , CALLRET 指令在当前的代码段跳转 , 不执行特权级检查 . 远的形式跳转到其他特权级 , 执行特权级检查 .

        1. 访问 nonconforming 代码段时 , 调用程序的 CPL 必须等于目标代码段的 DPL ; 否则产生 #GP .
          指向 nonconforming 代码段的段选择器的 RPL 对特权级检查的影响有限 , 要成功进行代码跳转 , RPL 的值必须小于等于调用程序的 CPL .
        2. 访问 conforming 代码段时 , 调用程序的 CPL 必须大于等于目标代码段的 DPL , 否则产生 #GP .
          对于代码段而言 , DPL 代表调用程序成功调用代码段必须具有的最低特权级 .
      2. 调用门用于在不同的特权级之间切换 , 包含下列功能 :

        • 指明要访问的代码段
        • 定义代码段中的程序入口点
        • 指明调用者访问程序所需的特权级
        • 若发生栈切换 , 指明在栈之间需要拷贝的参数个数
        • 定义压入目标栈的值的大小 : 16-bit 门强制进行 16-bit 压栈 ; 32-bit 门进行 32-bit 压栈
        • 指明调用门描述符是否有效

        通过调用门访问代码段时 , 特权级检查规则根据代码跳转是由 CALL 还是 JMP 触发有所不同 .

      3. 通过 RET 指令从被调用程序返回
        只有 CALL 指令调用的程序可以通过 RET 指令返回 , 因为 JMP 指令不会保存返回指令的指针到栈 .
        处理器使用调用程序保存的 CS 寄存器的值的 RPL 域的值大于 ( 特权级小于 ) CPL , 就发生了特权级间的返回 .

      4. SYSENTER 供特权级 3 的用户代码访问特权级 0 的 OS . SYSEXIT 供 OS 快速返回到用户代码 . 前者可以从特权级 3 , 2 , 1 或 0 执行 ; 后者只能从特权级 0 执行 .
        CALL / RET 不同 , SYSENTER 不会保存任何供 SYSEXIT 使用的状态信息 .
        SYSENTERSYSEXIT 指令使用的栈指针都预先定义在 MSR 和通用寄存器 , 因此能够实现快速的调用和返回操作 .

      5. 64 位模式下的 SYSCALLSYSRET 功能类似 , 但是不会通过 MSR 指明栈指针 .
        SYSCALL 不会保存栈指针 , SYSRET 也不会恢复 , 更可能的是 OS 的系统调用处理函数将栈指针从用户栈切换到 OS 栈 . 而且由于 SYSRET 不会修改栈指针 , 软件需要切换回用户栈 .

    4. 执行一些特权指令
      一些特权指令只有 CPL 为 0 时才可以执行 , 通常控制系统的功能 .

  • 可寻址域限制
    工作在保护模式下 , 处理器检查所有的指针加长段之间的保护和特权级之间的隔离 , 包括下列检查 :

    1. 检查访问权限 , 确定段类型和使用方式兼容 .
    2. 检查读写权限 .
    3. 检查指针的偏移是否超出段限制 .
    4. 检查指针的提供者是否被允许访问段 .
    5. 检查偏移的对齐 .

    处理器在执行指令时自动检查前三项 , 软件必须通过 ARPL 指令执行第四项检查 , 第五项在特权级 3 下开启对齐检查时自动进行 .

    ARPL 指令用于保证调用者提供的 RPL 和其 CPL 相同 , 防止应用自行修改 RPL 访问特权级更高的段 .

  • 程序入口点限制

  • 指令集限制

保护模式下设置所有段选择器和描述符的特权级为 0 可以关闭基于特权级的段保护机制 .
保护模式下分页的保护机制可以通过清除 CR0.WP 标志 , 设置每一个页目录项和页表项的 R / W 和 U / S 标志关闭 —- 每个页都是一个可写的用户页 .

基于分段的保护在基于分页的保护之前生效 ; 页级的保护无法覆盖段级的保护 .

PAE 分页 , 4 级分页 , 5 级分页提供的执行关闭位为数据页提供了额外的保护 , XD 缺页可能发生在所有特权级 , 任何取指操作 .

64 位模式代码段仍然存在 , 虽然基地址在进行地址计算时当做 0 处理 . 代码段描述符中的一些域 ( 基地址和段限制 ) 被忽略 , 其余的域正常工作 ( 除了类型域内的可读 bit ) .

IA-32e 模式使用了 CS 描述符中的 bit 53 , 选择工作在 64 位模式还是兼容模式 .