【问题标题】:Cannot understand enabling GDT segmentation, namely updating CS register无法理解启用 GDT 分段,即更新 CS 寄存器
【发布时间】:2016-02-28 15:03:33
【问题描述】:

我目前正在遵循启用 GDT 分段的指南。我正在使用 GNU Assembler 和 Bochs 进行仿真。

我知道我需要使用 GDT 描述符加载 GDT 寄存器。我已经这样做了,接下来的步骤是现在将所有具有偏移量的段寄存器加载到代码/数据段描述符的各个位置,相对于 GDT。这样做的代码如下:

reloadSegments:
   ; Reload CS register containing code selector:
   JMP   0x08:reload_CS ; 0x08 points at the new code selector
.reload_CS:
   ; Reload data segment registers:
   MOV   AX, 0x10 ; 0x10 points at the new data selector
   MOV   DS, AX
   MOV   ES, AX
   MOV   FS, AX
   MOV   GS, AX
   MOV   SS, AX
   RET

然而,我无法理解如何隐式加载带有偏移量的 CS 寄存器,而不会出现明显不可避免的结果,即跳转到 CS:IP 对指向的任何内存位置——即,如果代码段描述符是位于 GDT_start+0x10,我尝试将 0x10 加载到 CS 寄存器,虚拟机跳转到 0x10:IP,我从未进入 .reload_CS 标签。

我的例程版本(at&t 语法):

_start:
    // Disable interrupts
    cli
    // Load GDT register with location of GDT
    lgdt    0x3c

    // Load location of Code segment descriptor into cs
    ljmp    $0x2c, $reload_cs

reload_cs:
    mov $0x34, %ax
    mov %ax, %ds
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs
    mov %ax, %ss

loop:
    jmp loop

P.s:我不确定为什么 ljmp $0x2c, reload_cs 不起作用 - 将 reload_cs$ 加前缀可以编译,但根据我的经验,标签通常不需要这种语法...

【问题讨论】:

  • 这是一个很大的跳跃,不确定哪一部分让您感到困惑?它将跳转到给定地址,同时重新加载CS。设置GDT 是您的工作,以便您跳转到正确的位置。如果段基数是0 并且您处于org 0 模式,那将是正确的。 PS:lgdt 0x3c非常可疑。
  • 好吧,我读到 cs 必须包含相对于 GDT 的代码段的偏移量,但 cs 也构成了指向下一条指令的指针,即 cs:ip - 因此我最终跳转到0x2c:reload_cs,这不是reload_cs 的位置。到目前为止我能理解的唯一解决方案是在 GDT 中设计代码段的位置以保持cs 不变,这很荒谬。我觉得我缺少一些关于汇编程序的基本知识。
  • cs:ip 在保护模式下使用 GDT 条目中的基地址。您只需确保在指令中添加了偏移量的基地址会将您带到正确的位置。实现这一目标的最简单方法是在实模式下使用org 0,并将描述符条目中的基地址设为零。
  • 对不起,我还是没听懂。你的意思是,在我尝试 far jmp 隐式更新 cs 以包含 GDT 偏移量之前,我需要启用保护模式?关于将代码段作为 GDT 表的第一个偏移量的建议,我不能这样做,因为根据规范要求第一个条目用零填充,据我所知,cs 不能有一个零偏移。我上传了一些图片来说明我的情况:imgur.com/a/K25OQ
  • 编辑:你的意思是我应该从一开始就在 0x0:0x(something) 加载整个程序,并将reload_cs 之后的代码位置向下移动足够的字节以偏移cs 的变化?

标签: assembly x86 gnu-assembler osdev


【解决方案1】:

您似乎对 GDT 的工作方式有许多误解。

  • LGDT 指令不会使用选择器加载 GDTR。它的操作数是内存中的一个位置,它包含一个包含 16 位限制和 32 位线性基地址的结构。它通常在进入保护模式之前以实模式执行。

  • GDT 仅在保护模式下工作。要使用它,您必须通过设置 CR0 中的 PE 位从实模式切换到保护模式。

  • GDT 是内存中的一个表,包含许多 8 字节长的段描述符。 GDT 在内存中的位置和限制由如上所述的 LGDT 指令加载到 GDTR 的基数和限制确定。每个描述符都包含各种类型和权限位,对于基本描述符类型,还包含段基址的线性地址以及段的限制。

  • 保护模式寻址通过获取相关段寄存器中包含的选择器值并将其用作 GDT 或 LDT 的索引来工作。索引段描述符提供了被寻址的段的基地址。该基数被添加到相关偏移量以确定被引用的线性地址。您的远跳转指令 (ljmp $0x2c, $reload_cs) 将值 0x2creload_cs 分别加载到 CS 和 EIP 中。下一条要执行的指令由0x2c 引用的段描述符的基数和reload_cs 的值相加来确定。

  • 段选择器0x2c 不是 GDT 的索引,它是 LDT 的索引。选择器的最低有效三位是特殊的。位 0 和 1 是请求的特权级别,此处应为 0。第 2 位是表格指示符,如果它为 0,则选择器引用 GDT,如果为 1,则它使用 LDT。其余位 3-15 提供 GDT/LDT 的索引。

  • 符号reload_cs 的值由汇编器和/或链接器确定。你需要确保它的值是正确的。当您将其用作受保护模式代码段的偏移量时,这意味着该段的偏移量必须是reload_cs: 之后的指令实际位于内存中的位置。汇编器和链接器不知道您将代码加载到内存的位置,也不知道您是如何设置代码段的。

由于您使用的是 GNU 汇编器并且可能是 GNU 链接器,因此确保 reload_cs 具有正确值的最简单方法是使用基数为 0 的保护模式代码段,告诉汇编器将所有内容放入 @ 987654330@ 部分,然后告诉链接器将.text 部分定位在您将其加载到内存中的实际线性地址处。这样0 + reload_cs 将等于reload_cs 标签后面的指令在内存中的实际线性地址。

【讨论】:

    猜你喜欢
    • 2014-02-04
    • 2013-01-17
    • 2016-03-19
    • 2017-12-11
    • 1970-01-01
    • 2019-12-10
    • 1970-01-01
    • 2021-06-22
    • 2021-01-21
    相关资源
    最近更新 更多