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

32 bit protected mode not working for several assembly files

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

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

我认为这是我的代码的问题,但我也有 运行 这个 similar code,同样的问题是只能在 16 位模式下工作。

我是不是没有正确使用 QEMU,还是我搞砸了其他事情?我的代码是:

boot_sector.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

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

gdt.asm

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

print_string_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

print_string.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

命令: 构建:

nasm -f bin boot_sector.asm -o boot_sector.bin

Qemu:

qemu-system-x86_64 boot_sector.bin --nographic

您没有正确设置实模式堆栈指针和段寄存器。实模式堆栈指针由 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

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


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

在您的代码中,您似乎缺少 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)

此条目只有 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)

当 运行 在 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.

使用命令行:

qemu-system-x86_64 boot_sector.bin -curses

好的,我明白了。在 macOs 上简单地 运行 qemu-system-x86_64 boot_sector.bin 时,它不显示任何内容,即使在 16 位实模式下也不显示。我在网上某处发现添加 -nographic 会起作用,并且它适用于 16 位实模式。在 32 位 PM 中删除 -nographic 标签并添加 -curses。这非常有效。感谢 Michael Petch 也向我展示了我糟糕的 GDT 条目。

像这样将这些东西放在 switch_to_pm.asm 的顶部:

;switch_to_pm.asm

[bits 16]

; Switch to protected mode

switch_to_pm:

mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x3
int 0x10 ; set vga text mode 3
cli ; 1. disable interrupts
lgdt [gdt_descriptor] ; 2. load the GDT descriptor