【问题标题】:Assembler jump in Protected Mode with GDT汇编程序在 GDT 的保护模式下跳转
【发布时间】:2012-02-26 14:52:18
【问题描述】:

我目前正在玩 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?

我的第一个想法是选择应该指向 0x7e00(当前程序是否已加载)的代码描述符 (CODE_DESC),并使用到达 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

【问题讨论】:

    标签: assembly x86 protected-mode addressing-mode


    【解决方案1】:

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

    jmp (CODE_DESC-NULL_DESC):ProtectedMode
    

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

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

    jmp 0x8000
    

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

    jmp 0x200
    

    More information about the GDT
    More information about entering protected mode

    【讨论】:

    • 感谢您的回答.. 好的 - 当使用跳转指令“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(空描述符中的 2x dd 0)。如果我再次将基数保持在 0 的低位并说 jmp 08h:ProtectedMode (相当于你的方式)并使用“jmp 7ff8”(0x8000 - 08h)跳转到在 0x8000 加载的程序,它应该可以工作......但它没有不:|甚至跳转到“ProtectedMode”也会崩溃。 “jmp ProtectedMode”本身可以工作(但这可能是因为 CS 基数为 0)。你们有什么建议?
    【解决方案2】:

    代码中存在多个问题。

    首先,您的GDTR.Base 包含GDT 与代码开头的偏移量,因为您的代码被编译为从地址0 开始(因为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 位模式,因为您会被相对简单的东西绊倒。

    【讨论】:

    • 感谢您的解释...这正是我这样做的原因 - 当您第一次通过阅读 x86 参考资料从头开始执行此操作时,这并不是那么简单 :)!顺便说一句:你有什么好书可以推荐来处理这些类型的主题吗?
    • 我不知道好书。英特尔和 AMD 的官方文档包含所有信息,它不是典型的书籍或教科书,您可以轻松阅读并立即理解所有内容(顺便说一句,英特尔的文档中有很多错别字和偶尔的错误)。网上有很多文章和教程。你可以随时进行实验。或者查看某人的代码并提出问题。查看这些组:alt.os.developmentcomp.lang.asm.x86
    • 感谢您的建议!我去看看!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多