首页 文章

如何在NASM程序集中输入32位保护模式?

提问于
浏览
4

我正在学习x86程序集,我正在尝试在NASM中制作玩具操作系统,但我不了解一些东西 .

我做了一个成功启动内核的bootloader:

  • 从包含内核文件的软盘加载14个扇区;

  • 在标有 kernel.feo 的这些部门中搜索文件;

  • 将该文件加载到内存中以偏移 0x2000 ;

  • 使用远跳 jmp 0x2000:0x0000 执行内核 .

所以我的内核代码位于 0x2000:0 内存中 . CS 可能已正确设置,因为使用远跳 . 在这个内核代码中,我想进入32位保护模式,但我不确定GDT是如何工作的 . 当我在虚拟机 (QEMU) 上运行下面的代码时,它不会做任何事情 .

我想请你帮我进入32位保护模式!

那就是说,你有以下问题:由于组织0,你假设代码加载在0x7c00:0,但情况可能并非如此 . 唯一保证的是物理地址 . 您应该使用远程跳转到您的入口点,以便正确设置CS . 您出于某种原因将DS设置为0x2000,因此您的代码根本找不到任何数据 . 您应该将DS设置为匹配CS,或者在任何地方使用CS覆盖(不推荐) . 受保护的模式代码假设从零开始,这意味着它需要org 0x7c00,这当然与您的设置冲突 . 你应该切换到组织0x7c00和段0. VGA文本模式段是0xb8000而不是0xb80000(少一个零) . 您在引导扇区末尾没有引导签名字节0x55 0xaa .

我在代码中更正了这些内容:

  • [org 0x0] 更正为 [org 0x2000] ,并且段设置为 0 ;

  • DS 更正为 0 而不是 0x2000 ,所以现在它与 CS 匹配;

  • VGA文本模式段已更正为 0xb8000 ;

但代码不适用于这些更正,它应该打印两个字符串但它什么都不做!

请注意,此内核代码不应以引导签名 0x55 0xAA 结尾,因为它不是引导扇区 .

这是更正的内核代码(不起作用):

[bits 16]
[org 0x2000]

    jmp 0:kernel_start

gdt_start:

gdt_null:
    dd 0x0
    dd 0x0

gdt_code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0

gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0

gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

print:
    mov ah, 14
    mov bh, 0
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp print
.done:
    ret

uzenet_real db 'uzenet16', 0
uzenet_prot db 'uzenet32', 0

kernel_start:
    mov ax, 0
    mov ss, ax
    mov sp, 0xFFFC

    mov ax, 0
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov si, uzenet_real
    call print

    cli
    lgdt[gdt_descriptor]
    mov eax, cr0
    or eax, 0x1
    mov cr0, eax
    jmp CODE_SEG:b32

[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print32:
    pusha
    mov edx, VIDEO_MEMORY
.loop:
    mov al, [ebx]
    mov ah, WHITE_ON_BLACK
    cmp al, 0
    je .done
    mov [edx], ax
    add ebx, 1
    add edx, 2
    jmp .loop
.done:
    popa
    ret

b32:
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov ebp, 0x90000
    mov esp, ebp

    mov ebx, uzenet_prot
    call print32

    jmp $

1 回答

  • 6

    编程OS是一项高级任务 . 您至少应该能够使用调试器来查找自己的错误并理解基本知识 . 您可能想重新考虑是否具备此项工作的所有先决条件 .

    也就是说,您有以下问题:

    • 由于 org 0 ,您假设代码在 0x7c00:0 加载,但情况可能并非如此 . 唯一保证的是物理地址 . 您应该使用远程跳转到您的入口点,以便正确设置 CS .

    • 您出于某种原因将 DS 设置为 0x2000 ,因此您的代码根本找不到任何数据 . 您应该将 DS 设置为匹配 CS ,或者在任何地方使用 CS 覆盖(不推荐) .

    • 保护模式代码假定从零开始,这意味着它需要 org 0x7c00 ,这当然与您的设置冲突 . 你应该切换到 org 0x7c00 和段 0 .

    • VGA文本模式段位于 0xb8000 而不是 0xb80000 (少一个零) .

    • 在引导扇区末尾没有引导签名字节 0x55 0xaa .

    固定代码:

    [bits 16]
    [org 0x7c00]
    
        jmp 0:kernel_start
    
    gdt_start:
    
    gdt_null:
        dd 0x0
        dd 0x0
    
    gdt_code:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10011010b
        db 11001111b
        db 0x0
    
    gdt_data:
        dw 0xffff
        dw 0x0
        db 0x0
        db 10010010b
        db 11001111b
        db 0x0
    
    gdt_end:
    
    gdt_descriptor:
        dw gdt_end - gdt_start
        dd gdt_start
    
    CODE_SEG equ gdt_code - gdt_start
    DATA_SEG equ gdt_data - gdt_start
    
    print:
        pusha
        mov ah, 14
        mov bh, 0
    .loop:
        lodsb
        cmp al, 0
        je .done
        int 0x10
        jmp .loop
    .done:
        popa
        ret
    
    uzenet16 db 'uzenet16', 0
    uzenet32 db 'uzenet32', 0
    
    kernel_start:
        mov ax, 0
        mov ss, ax
        mov sp, 0xFFFC
    
        mov ax, 0
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
    
        mov si, uzenet16
        call print
    
        cli
        lgdt[gdt_descriptor]
        mov eax, cr0
        or eax, 0x1
        mov cr0, eax
        jmp CODE_SEG:b32
    
    [bits 32]
    
    VIDEO_MEMORY equ 0xb8000
    WHITE_ON_BLACK equ 0x0f
    
    print32:
        pusha
        mov edx, VIDEO_MEMORY
    .loop:
        mov al, [ebx]
        mov ah, WHITE_ON_BLACK
        cmp al, 0
        je .done
        mov [edx], ax
        add ebx, 1
        add edx, 2
        jmp .loop
    .done:
        popa
        ret
    
    b32:
        mov ax, DATA_SEG
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
    
        mov ebp, 0x2000
        mov esp, ebp
    
        mov ebx, uzenet32
        call print32
    
        jmp $
    
    [SECTION signature start=0x7dfe]
    dw 0AA55h
    

    您的更新问题似乎仍然与代码加载的位置相混淆:您说 offset 0x2000 然后谈论 Executes the kernel using a far jump jmp 0x2000:0x0000 这当然是错误的,因为它在段中有一个零,并且应该是零段远跳: jmp 0:0x2000 . 除此之外,验证您的代码确实已在正确的位置加载到内存中 . 学习使用调试器 .

    这是一个小的引导扇区,它从第二个扇区加载上述代码以寻址 0x2000 . 它工作正常,问题不在于GDT的东西,特别是如果你甚至没有打印出真实的模式信息(你也不清楚) .

    [bits 16]
    [org 0x7c00]
    mov ax, 0201h
    mov cx, 0002h
    mov dh, 0
    mov bx, 0
    mov es, bx
    mov bx, 2000h
    int 13h
    jmp 0:2000h
    
    [SECTION signature start=0x7dfe]
    dw 0AA55h
    

相关问题