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

情况如下:

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

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

jmp ProtectedMode


[...]

bits 32

ProtectedMode:
    .halt:
        hlt
        jmp .halt

到目前为止,这绝对可以正常工作。 “jmp ProtectedMode”无需明显的远跳即可清除预取队列,因为此程序加载了偏移量0(开头为组织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

最佳答案

代码中存在多个问题。

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

其次,由于使用相同的org 0,因此代码中的32位偏移量(在bits 32ProtectedMode:之后)不等于它们所对应的物理地址,它们比物理地址小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位模式,因为您会遇到一些相对简单的问题。

关于assembly - 带有GDT的保护模式下的汇编器跳转,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/9137947/

10-11 15:13