Max*_*nov 0 x86 assembly gnu-assembler osdev
我目前正在遵循启用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
Run Code Online (Sandbox Code Playgroud)
但是,我无法理解如何可以隐式加载具有偏移量的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
Run Code Online (Sandbox Code Playgroud)
Ps:我不确定为什么ljmp $0x2c, reload_cs不起作用 - 前缀reload_cs为$编译但标签通常不需要我的经验中的这种语法...
您似乎对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标签后面的指令的存储器中的实际线性地址.