有关 x86 系统中段选择器的详细信息

Details about segment selectors in x86 system

提问人:choiyhking 提问时间:9/20/2023 最后编辑:choiyhking 更新时间:9/20/2023 访问量:141

问:

我正在研究x86系统的保护环。

访问数据细分受众群的示例

在这张图片中,有段选择器。 我的问题是......

  1. 段选择器在 RAM 中?
  2. 谁创建区段选择器?自然而然?
  3. 一个区段可以有多个区段选择器吗?
  4. 顺便说一句,段选择器和段寄存器的关系?
  5. 这种结构(即分页分割、段选择器等)在现代系统上使用?

谢谢。

x86 分页 CPU 寄存器

评论

2赞 Nate Eldredge 9/20/2023
为了走捷径,#5 的答案是否定的。
0赞 choiyhking 9/20/2023
感谢您的回复。那么,现代系统只使用分页来管理内存呢?
0赞 DarkAtom 9/20/2023
@NateEldredge我觉得这太简单了。它们被使用,但它们的使用方式使它们的效果无关紧要。如果没有分段结构,则无法在 x86 上实现权限分离。分页仅强制执行权限级别,但某些东西(GDT)必须定义它们。例如,如果用户代码可以执行,则分页本身是无用的。mov cr3
1赞 Peter Cordes 9/20/2023
@DarkAtom:如果不设置 GDT,您甚至根本无法切换到保护模式或长模式,因为您无法跳转到 32 位或 64 位代码段。所以是的,我想可以公平地说你没有权限分离/内存保护,但那是因为你会被困在实模式中,根本无法启用分页!(或者只需更改控制寄存器位即可使您进入 16 位保护模式,而段基数与实模式保持不变?如果没有 GDT,您将无法处理中断,因为这涉及加载 IDT 中指定的新 CS:[er]IP。
1赞 DarkAtom 9/20/2023
@PeterCordes 虽然我确定根据手册这是未定义的行为,但您可能可以切换到保护模式甚至长期模式。我认为您可以设置(甚至可能 和 )并使用实模式描述符缓存。不过,您可能无法退出 16 位模式。编辑:没有看到你的编辑,所以我写了这个:PCR0.PECR0.PGCR4.PAEEFER.LME

答:

3赞 DarkAtom 9/20/2023 #1

我将尝试用尽可能简单的术语来解释这一点。

什么是细分?

分段是将内存划分为多个段的想法。这些区段具有基址限制(或大小)。

传统上,在实模式(DOS时代)中,段定义如下:

base_address = segment_register * 16
limit = 64 KiB

但是,在保护模式下,段不再像这样定义。相反,有一个表格,其中包含有关每个细分的详细信息。作为奖励,它不仅描述了基址和限制,还描述了使用它所需的权限(这就是为什么它被称为保护模式)以及它是 16 位还是 32 位段。


全局描述符表 (GDT)

GDT 是由操作系统在内存中设置的表(即数组),其中包含描述符。它们有 2 种类型:段描述符(定义段及其所有属性)和系统描述符(定义内存中的各种操作系统结构,有些很重要,有些不那么重要)。

操作系统使用特权 lgdt 指令告知 CPU 此表的位置。但是,如果在用户模式代码中尝试它,它将出错。

区段描述符

区段描述符定义区段。请参阅下文,了解如何实际加载和使用一个。区段描述符具有格式。

段选择器

段选择器只是 GDT 的索引。它们具有特定格式,其中最后 3 位用于其他目的(位 2 是表指示器,而位 1-0 是请求的权限级别)。只有第 15-3 位描述实际索引。例如,选择器指示 GDT 中的索引 4(从 0 开始)。0x20

段寄存器

段寄存器(、、、和)设计用于在使用时存储这些选择器。但是,当它们不使用时,它们可以存储在内存或任何其他地方。CSDSESFSGSSS

NULL 选择器充当 C 的 NULL 指针。您可以自由地将其加载到段寄存器中,但任何使用它的尝试都会出错。

例如:内存访问(例如将相对于 所描述的段基)(或者您可以显式指定段寄存器,例如 )。[ebx]DS[es:ebx]

在实模式下,这样的指令会访问内存地址。但是,在保护模式下,基址取自 GDT(在 表示的索引处),并将偏移量添加到其中。好的部分是,如果 指示的描述符是特权的,但进行内存访问的代码不是,它将出现常规保护故障。ds * 16 + ebxDSebxDS


区段描述符是如何创建的?

如前所述,段描述符是 GDT 的一部分,由操作系统创建。段选择器(通常)由操作系统提供给应用程序。如果需要,应用程序可以自由设置自己的选择器,前提是相应的描述符存在并且可以访问(在现代操作系统中几乎从未出现过这种情况)。

您可以有多个段选择器吗?

一个人可以而且总是有多个选择器。这是因为必须引用代码段描述符,而其他所有内容都引用数据段描述符。CS

每次访问内存访问 GDT 是不是很慢?

是的。如果是这种情况,则每次有人访问内存时,CPU 都必须访问正确的 GDT 条目,以便验证权限级别并获取段基数和计算限制。

幸运的是,每当您加载段寄存器时,CPU 都会缓存这些条目(到所谓的描述符缓存中)。这就是为什么加载段寄存器是一项相当昂贵的操作。然后,这些缓存将保证保持不变,直到您再次重新加载相应的段寄存器。这包括在不恢复描述符缓存的情况下切换出保护模式(这就是进入虚幻模式的方式)。


现代 32 位系统上的分段

无法在 x86 上禁用分段,但可以...井。。。绕过。通过将所有描述符设置为具有基本 0 和 4GiB 限制,您基本上绕过了分段的所有优点和缺点。

所有现代系统都使用分页来实现内存保护。但是,仍然需要分段,因为它强制执行权限级别。GDT 中的每个描述符都有一个 DPL(描述符权限级别)字段。这就是强制执行所谓的特权环的原因。如果用户模式应用程序有权访问环 0 选择器,则它可以执行特权指令并绕过所有保护机制,包括分页。

除此之外,还有一些系统结构(在GDT中使用系统描述符定义),例如任务状态段(TSS),它们对操作系统至关重要。例如,TSS 可确保当发生从环 3 到环 0 的任何转换时,堆栈 () 将切换到已知的内核堆栈,因为用户模式堆栈不可信。SS:ESP

分段仍用于 和 寄存器。在 Windows 中,引用指向线程信息块的描述符,而 NULL。FSGSFSGS

64 位系统上的分段

在 64 位模式下,分段几乎被禁用。唯一真正重要的选择器是 ,它根据 DPL 字段设置权限级别。和 寄存器未使用。 并设置为 0(在 64 位模式下,NULL 选择器不执行任何操作,使用时不会出错),同时仍然指向描述符(加载 NULL 错误),尽管我不确定为什么它不能为 0。CSDSESSSDSESSS

但是,描述符不能设置基数或限制*。从本质上讲,CPU 创建的环境必须以 32 位模式手动创建。内存保护是通过分页实现的,这在 64 位模式下是必需的。

有一种方法可以设置 和 选择器的基础(请参阅 WRFSBASE 和 WRGSBASE 说明)。这完全不会影响 GDT 条目(它们仍然只有 32 位基数的空间),而是直接修改描述符缓存(寄存器本身通常设置为 0)。Windows 64 位使用而不是指向 TIB。FSGSGSFS


* 某些 CPU 支持在 256TiB 地址空间的最后 4GiB 中设置段限制。据我了解,这样做是为了在硬件虚拟化(VT-x 和 AMD-V)出现之前促进软件虚拟化。

评论

0赞 Peter Cordes 9/20/2023
段“描述符缓存”不是像 L1d 或 TLB 那样的普通缓存;当他们阅读 GDT 时,它是在架构上定义的,并保证他们不会被随机驱逐,因此它们可能与 GDT 的内容不同步。甚至切换回实模式,分段基数/限制将保持其内部状态。
1赞 Peter Cordes 9/20/2023
32 位 x86 CPU 如何以复位矢量0xFFFFFFF0启动,即使它以 16 位实模式启动?我解释了一种 IMO 更有意义的思考方式:内部状态,您可以通过在实模式或受保护模式下写入段寄存器来编写(根据模式具有不同的语义)。
0赞 DarkAtom 9/20/2023
@PeterCordes 从来不意味着描述符缓存就像普通缓存一样。我现在将编辑它。
1赞 Peter Cordes 9/20/2023
我只是略读了你的答案;我不认为你真的暗示了什么,但是每次访问内存访问GDT不是很慢吗? 确实暗示了这一点。我只记得在“描述符缓存”中找到“缓存”这个词对我理解事情是如何工作的相反的,因为它暗示了很多关于段基数/限制和其他内部状态的不正确的事情。它基本上是额外的内部寄存器,根本不是缓存。(但这是英特尔做出的设计决定;它可能只是一个可以在任何时候重新访问GDT的缓存。
1赞 DarkAtom 9/20/2023
@PeterCordes 我觉得它很奇怪的原因是因为允许 Ring 0 加载空选择器并使用它。事实上,在发生权限转移的每次中断中,CPU 都会自动归零(在堆栈中保存后)。内核基本上总是有一个归零的 SS,因为它没有理由费心去改变它。唯一的限制是 RPL 必须与 CPL 匹配。 加载 null 选择器仅在环 3 处出错。SSSS