Assembly 带GDT的汇编程序在保护模式下跳转

Assembly 带GDT的汇编程序在保护模式下跳转,assembly,x86,protected-mode,addressing-mode,Assembly,X86,Protected Mode,Addressing Mode,为了提高我的低级编程技能,我目前正在使用x86 Assember。目前,我在32位保护模式下的寻址方案中遇到了一个小问题 情况如下: %ifndef __GDT_INC_INCLUDED__ %define __GDT_INC_INCLUDED__ ;********************************* ;* Global Descriptor Table (GDT) * ;********************************* NULL_DESC: dd 0

为了提高我的低级编程技能,我目前正在使用x86 Assember。目前,我在32位保护模式下的寻址方案中遇到了一个小问题

情况如下:

%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__
我在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

有几件事。首先,您当前的代码在技术上没有进入保护模式。通过从GDT加载带有描述符的
cs
,可以进入保护模式。由于无法直接设置
cs
寄存器,因此最简单的方法是使用跳远。将当前跳转替换为:

jmp (CODE_DESC-NULL_DESC):ProtectedMode
其次,代码段的基数是0,而不是0x7e00。如果您查看标有单词“base”的四个字节,它们都是0。你有两个选择。将GDT更改为以0x7e00为基数,或添加指令以更改以0为基数的所有受保护模式代码的加载地址

一旦您完成了这两件事,您就可以使用正常的跳转指令跳转到您的程序。如果您选择保持GDT的原样,您将使用完整地址:

jmp 0x8000
如果选择更改代码段的基址,则需要使用与之相关的地址

jmp 0x200


代码中存在多个问题

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

其次,由于相同的
org0
,代码中的32位偏移量(在
位32
保护模式:
之后)不等于它们对应的物理地址,它们比物理地址小0x7e00。OTOH,GDT中定义的段从物理地址0开始(因为GDT条目的基本部分是0),而不是从0x7e00开始。这意味着当您尝试将这些段与代码/数据一起使用时,您将丢失0x7e00的地址。如果要保留组织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位模式,因为你会被相对简单的东西绊倒。

谢谢你的回答。。OK-当使用跳转指令“jmp CODE_DESC:ProtectedMode”时,CPU会出现三重故障并复位(因为这个跳转方向似乎在某个地方跳转)。“jmp ProtectedMode”跳转到正确的标签并停止系统。因为这可能与GDT基本问题相关,所以我将更改GDT并重试。谢谢你的快速回复@ughoavgfhw你的意思是
jmp(code_DESC-NULL_DESC):ProtectedMode
?@NayukiMinase感谢你捕捉到了这一点,我假设它们已经是偏移量了。嗯,我将“base low”改为0x7e00,跳转指令现在是“jmp(code_DESC-NULL_DESC):ProtectedMode”。。但这一个仍然崩溃。。。关于GDT的东西有什么我不知道的吗?阅读上面提到的教程并不是真正讨论基础…:|看看当前的实现,我假设(CODE_DESC-NULL_DESC)将给出一个0的段选择器。因为CODE_DESC必须指向08h(null描述符中的2x dd 0)。如果我再次将基数设为0,然后说jmp 08h:ProtectedMode(这相当于您的方式),然后跳转到在0x8000处加载的程序,并使用“jmp 7ff8”(0x8000-08h),则