Intel x86 Assembly& Microarchitecture 保护模式

示例

介绍

发明80286时,它支持旧的8086分段(现在称为“实模式”),并添加了一个称为“保护模式”的新模式。此模式在每个x86处理器中都存在,尽管通过各种改进(例如32位和64位寻址)进行了增强。

设计

在保护模式下,完全取消了简单的“将地址添加到移位段寄存器值”。他们保留了段寄存器,但没有使用它们来计算地址,而是使用它们索引到一个表(实际上是两个表之一)中,该表定义了要访问的段。该定义不仅描述了段在内存中的位置(使用基本和限制),而且还描述了段的类型(代码,数据,堆栈甚至系统)以及可以访问它的程序类型(OS内核,普通程序) ,设备驱动程序等)。

段寄存器

每个16位段寄存器都采用以下格式:

+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
 Desc Index = 13-bit index into a Descriptor Table (described below)
 G/L        = 1-bit flag for which Descriptor Table to Index: Global or Local
 Priv       = 2-bit field defining the Privilege level for access

全球/本地

全局/本地位定义访问是进入描述符的全局表(毫不奇怪地称为全局描述符表或GDT)还是本地描述符表(LDT)。LDT的想法是,每个程序都可以有自己的描述符表-操作系统将定义一组全局段,并且每个程序都将具有自己的本地代码段,数据段和堆栈段。操作系统将管理不同描述符表之间的内存。

描述符表

每个描述符表(全局或本地)是一个由8,192个描述符组成的64K数组:每个8字节记录定义了所描述的细分的多个方面。段寄存器的描述符索引字段可用于8,192个描述符:并非巧合!

描述符

描述符保存以下信息-请注意,描述符的格式随着新处理器的发布而发生了变化,但是每种类型中都保留了相同的信息:

  • 基本
    这定义了内存段的起始地址。

  • 限制
    这定义了内存段的大小。他们必须做出决定:大小是否0x0000等于0,所以无法访问?或最大尺寸?
    相反,他们选择了第三个选项:“限制”字段是细分中的最后一个可寻址位置。这意味着可以定义一个再见细分;或地址大小的最大值。

  • 类型
    段有多种类型:传统的代码段,数据段和堆栈段(请参见下文),但还定义了其他系统段:

    • 本地描述符表段定义了可以访问多少个本地描述符。

    • 任务状态段可用于硬件管理的上下文切换;

    • 受控的“呼叫门”,它可以允许程序调用操作系统-但只能通过经过精心管理的入口点进行调用。

  • 属性
    还维护了细分的某些属性,如:

    • 只读vs读写

    • 该段当前是否存在-允许按需进行内存管理;

    • 哪些级别的代码(操作系统,驱动程序和程序)可以访问此网段。

最后真正的保护!

如果操作系统将描述符表保存在不能由单纯的程序访问的段中,则它可以严格管理定义了哪些段,以及为每个段分配和访问了什么内存。程序可以制造自己喜欢的任何段寄存器值-但是如果它有胆量将其实际加载段寄存器中……CPU硬件将认识到建议的描述符值违反了许多规则中的任何一条,并且而不是完成请求,它将向操作系统引发异常,以允许它处理错误的程序。

此例外通常是#13,即“一般保护例外”,它在Microsoft Windows中广为人知...(有人认为英特尔工程师是迷信的吗?)

失误

可能发生的错误包括:

  • 如果建议的描述符索引大于表的大小;

  • 如果建议的描述符是系统描述符而不是代码,数据或堆栈;

  • 如果提议的描述符比请求程序具有更大的特权;

  • 如果建议的描述符标记为“不可读”(例如代码段),但是尝试将其读取而不是执行;

  • 如果建议的描述符标记为“不存在”。

    请注意,最后一个对于程序可能不是致命的问题:操作系统可以记录该标志,恢复该段,将其标记为现在存在,然后允许错误的指令成功进行。

或者,也许描述符已成功加载到段寄存器中,但是随后对其进行的访问违反了以下规则之一:

  • 段寄存器加载0x0000了GDT的描述符索引。硬件将其保留为NULL:

  • 如果已加载的描述符标记为只读,但尝试对其进行写操作。

  • 访问的任何部分(1、2、4或更多字节)是否超出了段的限制。