我目前正在遵循启用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标签。
我的例程版本(& 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
Ps:我不确定为什么ljmp $0x2c, reload_cs
不起作用 - 在reload_cs
前加$
编译,但标签通常根据我的经验不需要这种语法......
答案 0 :(得分:7)
您似乎对GDT如何运作存在一些误解。
LGDT指令不会使用选择器加载GDTR。它的操作数是存储器中的一个位置,它包含一个包含16位限制和32位线性基址的结构。它在进入保护模式之前通常以实模式执行。
GDT仅适用于保护模式。要使用它,必须通过将CR0中的PE位置1来从实模式切换到保护模式。
GDT是一个包含许多8字节长段描述符的内存表。 GDT在存储器中的位置和限制由LGDT指令加载到GDTR中的基数和限制确定,如上所述。每个描述符包含各种类型和权限位,对于基本描述符类型,还包含段的基数的线性地址以及段的限制。
保护模式寻址通过获取相关段寄存器中包含的选择器值并将其作为索引用于GDT或LDT来工作。索引段描述符提供被寻址的段的基地址。将该基数添加到相关偏移量以确定所引用的线性地址。您的远程跳转指令(ljmp $0x2c, $reload_cs
)分别将值0x2c
和reload_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的受保护模式代码段,告诉汇编程序放置将所有内容放入.text
部分,然后告诉链接器将.text
部分放在您将其加载到内存中的实际线性地址处。那样0 + reload_cs
将等于reload_cs
标签后面指令的内存中的实际线性地址。