8

我正在学习 x86 汇编,我正在尝试在 NASM 中制作一个玩具操作系统,但我不明白一些东西。

我制作了一个成功启动内核的引导加载程序:

  1. 从包含内核文件的软盘加载 14 个扇区;
  2. kernel.feo在这些标记为;的扇区中搜索文件
  3. 将该文件加载到内存中的偏移量0x2000
  4. 使用远跳转执行内核jmp 0x2000:0x0000

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

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

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

  1. 您假设代码是0x7c00:0由于 加载的org 0,但情况可能并非如此。唯一保证的是物理地址。您应该使用远跳转到您的入口点,以便CS正确设置。
  2. 您出于某种原因设置DS为,0x2000因此您的代码根本找不到任何数据。您应该设置DS为 match CS,或在任何地方使用CS覆盖(不推荐)。
  3. 保护模式代码假定从零开始的段,这反过来意味着它期望org 0x7c00这当然与您的设置冲突。您应该切换到org 0x7c00和 段0
  4. VGA 文本模式段为0xb8000not 0xb80000(减一)。
  5. 0x55 0xaa引导扇区末尾没有引导签名字节。

我在我的代码中更正了这些事情:

  1. [org 0x0]被更正为[org 0x2000],段被设置为0
  2. DS被更正为,0而不是0x2000,所以现在它匹配CS;
  3. 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 $
4

1 回答 1

10

对操作系统进行编程是一项高级任务。您至少应该能够使用调试器来发现自己的错误并理解基本的东西。您可能需要重新考虑您是否具备这项工作的所有先决条件。

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

  1. 您假设代码是0x7c00:0由于 加载的org 0,但情况可能并非如此。唯一保证的是物理地址。您应该使用远跳转到您的入口点,以便CS正确设置。
  2. 您出于某种原因设置DS为,0x2000因此您的代码根本找不到任何数据。您应该设置DS为 match CS,或在任何地方使用CS覆盖(不推荐)。
  3. 保护模式代码假定从零开始的段,这反过来意味着它期望org 0x7c00这当然与您的设置冲突。您应该切换到org 0x7c00和 段0
  4. VGA 文本模式段为0xb8000not 0xb80000(减一)。
  5. 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. 除此之外,请验证您的代码是否确实在正确的位置加载到内存中。学习使用调试器。

这是一个小的引导扇区,它将上述代码从第二个扇区加载到 address 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
于 2015-02-21T12:44:46.457 回答