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

Axl*_*ion 6 x86 assembly nasm gdt

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

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

  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匹配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 $
Run Code Online (Sandbox Code Playgroud)

Jes*_*ter 7

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

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

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

你的更新问题似乎仍然被困在代码加载的位置:你说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
Run Code Online (Sandbox Code Playgroud)