看不懂使能GDT分段,即更新CS寄存器

Cannot understand enabling GDT segmentation, namely updating CS register

我目前正在遵循启用 GDT 分段的指南。我正在使用 GNU 汇编程序,并使用 Bochs 进行仿真。

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

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    [=11=]x2c, $reload_cs

reload_cs:
    mov [=11=]x34, %ax
    mov %ax, %ds
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs
    mov %ax, %ss

loop:
    jmp loop

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

您似乎对 GDT 的工作原理有很多误解。

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

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

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

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

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

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

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