问题 汇编程序使用GDT跳转到保护模式


我目前正在玩x86 Assember,以提高我的低级编程技能。目前,我在32位保护模式下的寻址方案面临一点问题。

情况如下:

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

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

jmp ProtectedMode


[...]

bits 32

ProtectedMode:
    .halt:
        hlt
        jmp .halt

到目前为止这个工作绝对正常。 “jmp ProtectedMode”在没有显式远跳的情况下工作以清除预取队列 - 因为该程序加载了偏移量0(开头的org 0) - 导致代码段指向正确的位置。

我现在的问题是,在“ProtectedMode”标签中我想跳转到另一个加载到0x8000的程序(我用内存转储检查了这个,加载函数确实正常工作,程序正确加载到0x8000) 。

由于CPU现在处于ProtectedMode而不再是RealMode,因此寻址模式不同。 ProtectedMode使用描述符选择器查找基址和描述符表中的限制,以添加给定的偏移量并检索物理地址(据我所知)。因此,在进入ProtectedMode之前必须安装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跳转到ProtectedMode中的物理地址0x8000?

我的第一个想法是选择代码描述符(CODE_DESC),它应该指向0x7e00(当前程序被加载)并使用必要的偏移量来获得0x8000(512字节),从而产生跳转指令:

jmp CODE_DESC:0x200

但这不起作用。

jmp 0x7e0:0x200 

不起作用......

你知道我在这里缺少什么吗?也许我不理解32位ProtectedMode寻址方案和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

8897
2018-02-04 02:34


起源



答案:


代码中存在多个问题。

首先,你的 GDTR.Base 包含的偏移量 GDT 从代码的开头,因为你的代码被编译为从地址0开始(因为 org 0)。基地址必须是物理地址,而不是相对地址。 IOW,如果你保留这个 org 0,你必须添加 CS* 16(= 0x7e00)到 Base

第二,因为同样的原因 org 0,代码中的32位偏移量(之后 bits 32 和 ProtectedMode:)不等于它们对应的物理地址,它们比物理地址小0x7e00。 OTOH,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位模式,因为你正在绊倒相对简单的东西。


12
2018-02-04 10:32



感谢您的解释......这正是我这样做的原因 - 当您第一次通过阅读x86参考文件从头开始这样做时,并不是那么简单!顺便说一下:有没有一本很好的书可以提供哪些建议来处理这些类型的主题? - Sebastian B.
我不懂好书。来自英特尔和AMD的官方文档包含所有信息,它不是一本典型的书籍或教科书,您可以轻松阅读并立即理解所有内容(顺便说一下,英特尔的文档中存在许多拼写错误和偶然的错误)。在线有很多文章和教程。你可以随时试验。或者查看某人的代码并提出问题。看到这些组: alt.os.development, comp.lang.asm.x86。 - Alexey Frunze
谢谢你的建议 !!我来看看吧! - Sebastian B.


答案:


代码中存在多个问题。

首先,你的 GDTR.Base 包含的偏移量 GDT 从代码的开头,因为你的代码被编译为从地址0开始(因为 org 0)。基地址必须是物理地址,而不是相对地址。 IOW,如果你保留这个 org 0,你必须添加 CS* 16(= 0x7e00)到 Base

第二,因为同样的原因 org 0,代码中的32位偏移量(之后 bits 32 和 ProtectedMode:)不等于它们对应的物理地址,它们比物理地址小0x7e00。 OTOH,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位模式,因为你正在绊倒相对简单的东西。


12
2018-02-04 10:32



感谢您的解释......这正是我这样做的原因 - 当您第一次通过阅读x86参考文件从头开始这样做时,并不是那么简单!顺便说一下:有没有一本很好的书可以提供哪些建议来处理这些类型的主题? - Sebastian B.
我不懂好书。来自英特尔和AMD的官方文档包含所有信息,它不是一本典型的书籍或教科书,您可以轻松阅读并立即理解所有内容(顺便说一下,英特尔的文档中存在许多拼写错误和偶然的错误)。在线有很多文章和教程。你可以随时试验。或者查看某人的代码并提出问题。看到这些组: alt.os.development, comp.lang.asm.x86。 - Alexey Frunze
谢谢你的建议 !!我来看看吧! - Sebastian B.


一些事情。首先,您当前的代码在技术上不会进入保护模式。您通过加载进入保护模式 cs 使用GDT的描述符。既然你不能直接设置 cs 注册,最简单的方法是使用远程跳转。用以下内容替换当前跳转:

jmp (CODE_DESC-NULL_DESC):ProtectedMode

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

完成这两项操作后,您可以使用正常的跳转指令跳转到程序。如果您选择保留GDT,请使用完整地址:

jmp 0x8000

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

jmp 0x200

有关GDT的更多信息
有关进入保护模式的更多信息


3
2018-02-04 02:51



谢谢你的回答..好的 - 当使用跳转指令“jmp CODE_DESC:ProtectedMode”时,CPU三重故障并重置(因为这个跳转方向似乎在某处跳转)。 “jmp ProtectedMode”跳转到正确的标签并暂停系统。由于这可能与GDT基础问题相关,因此我将更改GDT并重试。谢谢你的快速回复!! - Sebastian B.
@ughoavgfhw你的意思是 jmp (CODE_DESC-NULL_DESC):ProtectedMode? - Nayuki
@NayukiMinase感谢你抓住这个,我认为他们已经抵消了。 - ughoavgfhw
嘿,好吧 - 你是对的 - 我会尝试这个。 - Sebastian B.
嗯,我把“基本低”改为0x7e00,跳转指令现在是“jmp(CODE_DESC-NULL_DESC):ProtectedMode”..但是这个仍然崩溃......有什么我没有得到关于GDT的东西吗?阅读上面提到的教程并没有真正谈论基础......:| - Sebastian B.