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



我正在学习x86汇编,我正在尝试用NASM制作玩具操作系统,但我不明白一些事情。

我做了一个引导加载程序,可以成功引导我的内核:

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

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

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

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

  1. 您假设由于org 0,代码以0x7c00:0加载,但事实可能并非如此。唯一可以保证的是物理地址。您应该使用远距离跳转到入口点,以便正确设置CS
  2. 出于某种原因,您将DS设置为 0x2000,因此您的代码根本找不到任何数据。您应该设置DS以匹配CS,或者在任何地方使用CS覆盖(不推荐(。
  3. 保护模式代码假定从零开始的段,这反过来意味着它期望org 0x7c00这当然与您的设置冲突。您应该切换到org 0x7c00和分段0
  4. VGA 文本模式段位于0xb8000而不是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 $

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

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

  1. 您假设由于org 0,代码以0x7c00:0加载,但事实可能并非如此。唯一可以保证的是物理地址。您应该使用远距离跳转到入口点,以便正确设置CS
  2. 出于某种原因,您将DS设置为 0x2000,因此您的代码根本找不到任何数据。您应该设置DS以匹配CS,或者在所有位置使用CS覆盖(不推荐(。
  3. 保护模式代码假定从零开始的段,这反过来意味着它期望org 0x7c00当然与您的设置冲突。您应该切换到org 0x7c00和细分0
  4. VGA 文本模式段位于0xb8000而不是0xb80000(少一个零(。
  5. 引导
  6. 扇区末尾没有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

相关内容

  • 没有找到相关文章

最新更新