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

时间:2019-02-19 04:10:02

标签: assembly x86 nasm qemu osdev

我正在编写一个简单的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

3 个答案:

答案 0 :(得分:2)

您没有正确设置实模式堆栈指针和段寄存器。实模式堆栈指针由SS:SP组成。您不知道堆栈在哪里,因为您只修改了SP。 Bootloader的开头应类似于:

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如下:

enter image description here

在您的代码中,您似乎缺少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运行时,它应显示为:

enter image description here

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


如果您希望以控制台方式在图形显示之外运行代码,请告诉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

答案 1 :(得分:1)

好,我知道了。当仅在macO上运行qemu-system-x86_64 boot_sector.bin时,它甚至不以16位实模式显示任何内容。我在网上某个地方发现可以添加-nographic,并且可以在16位实模式下使用。在32位PM中,删除-nographic标签并添加-curses。这工作得很好。感谢Michael Petch也向我展示了我不好的GDT条目。

答案 2 :(得分:0)

像这样将这些东西放在 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