在保护模式下使用GDT进行汇编跳转

12

我目前正在尝试使用x86汇编语言来提高我低级编程技能。 目前,我在32位保护模式下的寻址方案中遇到了一些小问题。

情况如下:

我有一个程序加载在0x7e0处,它将CPU切换到保护模式并跳转到代码中相应的标签:

[...]
code to switch CPU in Protected Mode
[...]

jmp ProtectedMode


[...]

bits 32

ProtectedMode:
    .halt:
        hlt
        jmp .halt

到目前为止这个程序完美运行,"jmp ProtectedMode"可以正常工作而不需要显式的far jump指令去清除预取队列 – 因为这个程序是从offset 0(在开始时的org 0)处加载,所以代码端口自然指向正确的位置。

现在我的问题是,在“ProtectedMode”标签中,我想要跳转到另一个程序,该程序已经被加载至0x8000(通过内存转储检查过了,加载函数也顺利地将程序正确地加载到了0x8000)。

由于CPU现在处于保护模式而不是实模式,地址寻址机制不同了。保护模式使用描述符选择器在一个描述符表中查找基址和限制,并将给定的偏移量加上,以获取物理地址(根据我的理解)。因此,在进入保护模式之前安装GDT是必要的。

我的GDT看起来像下面这样:

%ifndef __GDT_INC_INCLUDED__
%define __GDT_INC_INCLUDED__

;*********************************
;* Global Descriptor Table (GDT) *
;*********************************
NULL_DESC:
    dd 0            ; null descriptor
    dd 0

CODE_DESC:
    dw 0xFFFF       ; limit low
    dw 0            ; base low
    db 0            ; base middle
    db 10011010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

DATA_DESC:
    dw 0xFFFF       ; data descriptor
    dw 0            ; limit low
    db 0            ; base low
    db 10010010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

gdtr:
    Limit dw 24         ; length of GDT
    Base dd NULL_DESC   ; base of GDT

%endif ;__GDT_INC_INCLUDED__

并通过 GDT 寄存器加载

lgdt [gdtr]

我到目前为止还不理解的是,在保护模式下,如何使用GDT跳转到物理地址0x8000?

我的第一反应是选择代码描述符(CODE_DESC),它应该指向0x7e00(当前程序加载的位置),并使用必要的偏移量到达0x8000(512字节),从而得出跳转指令:

jmp CODE_DESC:0x200

但是这并不起作用。

jmp 0x7e0:0x200 

这也不起作用......

你有什么想法,我在这里缺少了什么吗?也许我没有理解32位保护模式寻址方案和GDT使用中的一些基本要素。

[编辑]完整代码:

bits 16
org 0                       ; loaded with offset 0000 (phys addr: 0x7e00)

jmp Start

Start:
    xor ax, ax
    mov ax, cs
    mov ds, ax              ; update data segment

    cli                     ; clear interrupts

    lgdt [gdtr]             ; load GDT from GDTR (see gdt_32.inc)

    call OpenA20Gate        ; open the A20 gate 

    call EnablePMode        ; jumps to ProtectedMode

;******************
;* Opens A20 Gate *
;******************
OpenA20Gate:
    in al, 0x93         ; switch A20 gate via fast A20 port 92

    or al, 2            ; set A20 Gate bit 1
    and al, ~1          ; clear INIT_NOW bit
    out 0x92, al

    ret

;**************************
;* Enables Protected Mode *
;**************************
EnablePMode:
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp ProtectedMode ; this works (jumps to label and halts)
    ;jmp (CODE_DESC-NULL_DESC):ProtectedMode ; => does not work
    ;jmp 08h:ProtectedMode , => does not work

;***************
;* data fields *
;*  &includes  *
;***************
%include "gdt_32.inc"

;******************
;* Protected Mode *
;******************
bits 32

ProtectedMode:
    ;here I want to jump to physical addr 0x8000 (elf64 asm program)

    .halt:
        hlt
        jmp .halt
2个回答

14

代码中存在多个问题。

首先,你的GDTR.Base包含了GDT相对于代码起始位置的偏移量,因为你的代码被编译成从地址0开始(由于org 0),所以基地址必须是物理地址,而不是相对地址。也就是说,如果你保持org 0,你必须将CS*16 (=0x7e00)加到Base上。

其次,由于同样的org 0,你代码中的32位偏移(在bits 32ProtectedMode:之后)并不等于它们对应的物理地址,它们比实际地址小0x7e00。另一方面,你在GDT中定义的段从物理地址0开始(因为GDT条目的基地址部分为0),而不是0x7e00。这意味着当你尝试使用这些段与你的代码/数据时,你会将地址误差定为0x7e00。如果你想保持org 0,那么GDT中的基地址必须设置为0x7e00。

或者你可以将org 0改为org 0x7e00,然后GDT中的基地址应该为0。这样你就不需要将GDTR.Base调整0x7e00,而是直接设置为0即可。

以下是修改后的代码:

bits 16
org 0x7e00                  ; loaded at phys addr 0x7e00
                            ; control must be transferred with jmp 0:0x7e00

    xor ax, ax
    mov ds, ax              ; update data segment

    cli                     ; clear interrupts

    lgdt [gdtr]             ; load GDT from GDTR (see gdt_32.inc)

    call OpenA20Gate        ; open the A20 gate 

    call EnablePMode        ; jumps to ProtectedMode

;******************
;* Opens A20 Gate *
;******************
OpenA20Gate:
    in al, 0x93         ; switch A20 gate via fast A20 port 92

    or al, 2            ; set A20 Gate bit 1
    and al, ~1          ; clear INIT_NOW bit
    out 0x92, al

    ret

;**************************
;* Enables Protected Mode *
;**************************
EnablePMode:
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp (CODE_DESC - NULL_DESC) : ProtectedMode

;***************
;* data fields *
;*  &includes  *
;***************
;%include "gdt_32.inc"
;*********************************
;* Global Descriptor Table (GDT) *
;*********************************
NULL_DESC:
    dd 0            ; null descriptor
    dd 0

CODE_DESC:
    dw 0xFFFF       ; limit low
    dw 0            ; base low
    db 0            ; base middle
    db 10011010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

DATA_DESC:
    dw 0xFFFF       ; limit low
    dw 0            ; base low
    db 0            ; base middle
    db 10010010b    ; access
    db 11001111b    ; granularity
    db 0            ; base high

gdtr:
    Limit dw gdtr - NULL_DESC - 1 ; length of GDT
    Base dd NULL_DESC   ; base of GDT

;******************
;* Protected Mode *
;******************
bits 32

ProtectedMode:
    mov     ax, DATA_DESC - NULL_DESC
    mov     ds, ax ; update data segment

    .halt:
        hlt
        jmp .halt

请注意,一个段的限制等于段的大小减1。

还有一些要点... 用有效的选择符或0来加载所有段寄存器。同时,设置栈。如果你的栈里面有垃圾数据(或来自实模式的旧值),当你开始使用中断/异常时,会出现更多的崩溃。

最后,我不知道elf64是什么,但是你需要为其他模块处理org问题,并确保所有生成的地址都对应于装载地址。如果你打算启用64位模式,那么就有大量的工作要做。我建议不要急于转入64位模式,因为你可能会被相对简单的问题绊住。


谢谢您的解释。这正是我这么做的原因——当您第一次仅通过阅读x86参考手册从头开始时,这并不那么简单 :)!另外:有没有什么好的书籍可以推荐,专门涉及这些类型的主题? - Sebastian B.
1
我不知道好书。英特尔和AMD官方文档包含所有信息,但那不是你容易阅读并立即理解一切的典型书籍或教科书(顺便说一下,英特尔的文档中有很多错别字和偶尔的错误)。有许多在线文章和教程。你也可以随时进行实验。或查看别人的代码并提问。请参见这些组:alt.os.developmentcomp.lang.asm.x86 - Alexey Frunze
谢谢你的建议!!我会查看一下! - Sebastian B.

3

有几个问题。首先,你当前的代码并没有技术上进入保护模式。要通过从GDT加载一个描述符来将系统置于保护模式中。由于无法直接设置cs寄存器,最简单的方法是使用far jump进行替换。请将你当前的jump语句替换为:

jmp (CODE_DESC-NULL_DESC):ProtectedMode

其次,你的代码段基址是0而不是0x7e00。如果你看一下标记为“base”的四个字节,它们都是0。 你有两个选择。要么更改你的GDT以使基址为0x7e00,要么添加指令以更改所有受保护模式代码的加载地址为基址0。

完成这些步骤后,你可以使用普通跳转指令跳转到你的程序。如果你选择保留你的GDT,那么你需要使用完整的地址:

jmp 0x8000

如果您选择更改代码段的基址,那么您需要使用相对于该基址的地址。

jmp 0x200

了解更多关于全局描述符表(GDT)的信息
了解更多关于进入保护模式的信息


感谢您的回答。好的,当使用跳转指令“jmp CODE_DESC:ProtectedMode”时,CPU会三重故障并重置(因为这个跳转方向似乎跳到了某个地方)。而“jmp ProtectedMode”则跳转到正确的标签并停止系统。由于这可能与GDT基址问题有关,我将更改GDT并再次尝试。感谢您的快速响应! - Sebastian B.
@ughoavgfhw 你的意思是 jmp (CODE_DESC-NULL_DESC):ProtectedMode 吗? - Nayuki
@NayukiMinase 谢谢你指出这个问题,我以为它们已经是偏移量了。 - ughoavgfhw
嗯,我把“base low”改成了0x7e00,跳转指令现在是“jmp (CODE_DESC-NULL_DESC):ProtectedMode”。。但这个还是崩溃了...我有没有理解GDT的东西?阅读上面提到的教程并没有真正讲述基础知识... :| - Sebastian B.
看当前的实现,我会认为 (CODE_DESC-NULL_DESC) 会给出一个段选择器为0。因为 CODE_DESC 必须指向08h(在 null 描述符中是2x dd 0)。如果我再次将基地址低位设置为0,并说 jmp 08h:ProtectedMode(与您的方式等效),并使用 "jmp 7ff8"(0x8000 - 08h)跳转到加载在0x8000处的程序,它应该可以工作...但它没有:| 即使跳转到 "ProtectedMode" 也会崩溃。 "jmp ProtectedMode" 本身是有效的(但这可能是因为 CS 基地址为0)。你们有什么建议? - Sebastian B.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接