看不懂使能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
) 将值 0x2c
和 reload_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
标签后指令在内存中的实际线性地址。
我目前正在遵循启用 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
) 将值0x2c
和reload_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
标签后指令在内存中的实际线性地址。