32 位保护模式不适用于多个汇编文件

Saw*_*bst 0 x86 assembly qemu nasm osdev

我正在编写一个简单的 NASM 汇编引导扇区。该代码应在 16 位实模式下将文本打印到屏幕上,然后切换到 32 位保护模式并将文本打印到屏幕上。

我使用 QEMU 作为我的 CPU 模拟器,它按照应有的方式从 16 位模式打印文本。但是,在 32 位模式下应该打印的文本不会打印。

我认为这是我的代码的问题,但我也运行了类似的代码,同样的问题是仅在 16 位模式下工作。

是我没有正确使用 QEMU,还是我弄乱了其他东西?我的代码是:

引导扇区.asm

; Boot sector that enters 32 bit protected mode
[org 0x7c00]

mov bp, 0x9000          ; Set stack
mov sp, bp

mov bx, MSG_REAL_MODE
call print_string

call switch_to_pm       ; We will never return to here

jmp $

%include "print_string.asm"
%include "gdt.asm"
%include "print_string_pm.asm"
%include "switch_to_pm.asm"

[bits 32]
;Where we arrive after switching to PM
BEGIN_PM:
    mov ebx, MSG_PROTECTED_MODE
    call print_string_pm        ; 32 bit routine to print string

    jmp $                       ; Hang


; Global variables
MSG_REAL_MODE: db "Started in 16-bit real mode.", 0
MSG_PROTECTED_MODE: db "Successfully landed in 32-bit protected mode.", 0

; Boot sector padding
times 510-($-$$) db 0
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)

switch_to_pm.asm

[bits 16]
; Switch to protected mode
switch_to_pm:

    mov bx, MSG_SWITCHING       ; Log
    call print_string

    cli                         ; Clear interrupts

    lgdt [gdt_descriptor]       ; Load GDT

    mov eax, cr0                ; Set the first bit of cr0 to move to protected mode, cr0 can't be set directly
    or eax, 0x1                 ; Set first bit only
    mov cr0, eax

    jmp CODE_SEG:init_pm        ; Make far jump to to 32 bit code. Forces CPU to clear cache

[bits 32]
; Initialize registers and the stack once in PM
init_pm:

    mov ax, DATA_SEG            ; Now in PM, our old segments are meaningless
    mov ds, ax                  ; so we point our segment registers to the data selector defined GDT
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000            ; Move stack
    mov esp, ebp

    call BEGIN_PM               ; Call 32 bit PM code


; Global variables
MSG_SWITCHING: db "Switching to 32-bit protected mode...", 0
Run Code Online (Sandbox Code Playgroud)

汇编语言

gdt_start:

gdt_null:           ; The mandatory null descriptor
    dd 0x0          ; dd = define double word (4 bytes)
    dd 0x0

gdt_code:           ; Code segment descriptor
    dw 0xffff       ; Limit (bites 0-15)
    dw 0x0          ; Base (bits 0-15)
    db 0x0          ; Base (bits 16-23)
    db 10011010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, limit (bits 16-19)

gdt_data:
    dw 0xffff       ; Limit (bites 0-15)
    dw 0x0          ; Base (bits 0-15)
    db 0x0          ; Base (bits 16-23)
    db 10010010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, limit (bits 16-19)
    db 0x0

gdt_end:            ; necessary so assembler can calculate gdt size below

gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; GDT size

    dd gdt_start                ; Start adress of GDT

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
Run Code Online (Sandbox Code Playgroud)

打印字符串_pm.asm

[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
    pusha
    mov edx, VIDEO_MEMORY

print_str_pm_loop:
    mov al, [ebx]
    mov ah, WHITE_ON_BLACK

    cmp al, 0
    je print_str_pm_return

    mov [edx], ax

    add ebx, 1
    add edx, 2

    jmp print_str_pm_loop

print_str_pm_return:
    popa
    ret
Run Code Online (Sandbox Code Playgroud)

打印字符串.asm

print_string:
    pusha
    mov ah, 0x0e

_print_str_loop:
    mov al, [bx]
    cmp al, 0
    je _print_str_return
    int 0x10
    inc bx
    jmp _print_str_loop

_print_str_return:
    popa
    ret
Run Code Online (Sandbox Code Playgroud)

命令: 构建:

nasm -f bin boot_sector.asm -o boot_sector.bin
Run Code Online (Sandbox Code Playgroud)

奎姆:

qemu-system-x86_64 boot_sector.bin --nographic
Run Code Online (Sandbox Code Playgroud)

Mic*_*tch 5

您没有正确设置实模式堆栈指针和段寄存器。实模式堆栈指针由 SS:SP 组成。您不知道堆栈在内存中的位置,因为您只修改了 SP。引导加载程序的开头应该类似于:

xor ax, ax              ; Set ES=DS=0 since an ORG of 0x7c00 is used
mov es, ax              ;     0x0000<<4+0x7c00 = physical address 0x07c00
mov ds, ax

mov bp, 0x9000
mov ss, ax              ; Set stack to 0x0000:0x9000
mov sp, bp
Run Code Online (Sandbox Code Playgroud)

您的代码不依赖于BP,因此不必进行设置,尽管这样做不会造成任何损害。


进入保护模式的主要问题是 GDT 中的错误。每个描述符条目为 8 个字节,每个描述符的布局如下:

在此输入图像描述

在您的代码中,您似乎在 32 位代码描述符中丢失了一个字节:

gdt_code:           ; Code segment descriptor
    dw 0xffff       ; Limit (bites 0-15)
    dw 0x0          ; Base (bits 0-15)
    db 0x0          ; Base (bits 16-23)
    db 10011010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, limit (bits 16-19)
Run Code Online (Sandbox Code Playgroud)

该条目只有 7 个字节长。看来您缺少最后一个字节,该字节应该为 0 来完成 32 位基地址。它应该是:

gdt_code:           ; Code segment descriptor
    dw 0xffff       ; Limit (bites 0-15)
    dw 0x0          ; Base (bits 0-15)
    db 0x0          ; Base (bits 16-23)
    db 10011010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, limit (bits 16-19)
    db 0x0          ; Base (bits 24-31)
Run Code Online (Sandbox Code Playgroud)

当使用以下命令在 QEMU 中运行时,qemu-system-x86_64 boot_sector.bin它应该显示为:

在此输入图像描述

我用红色突出显示了在保护模式下打印的文本。


如果您希望在控制台模式下的图形显示之外运行代码,请告诉 QEMU 使用curses。

    -curses
       Normally, if QEMU is compiled with graphical window support, it
       displays output such as guest graphics, guest console, and the QEMU
       monitor in a window. With this option, QEMU can display the VGA
       output when in text mode using a curses/ncurses interface. Nothing
       is displayed in graphical mode.
Run Code Online (Sandbox Code Playgroud)

使用命令行:

qemu-system-x86_64 boot_sector.bin -curses
Run Code Online (Sandbox Code Playgroud)