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

时间:2015-02-21 11:51:39

标签: assembly x86 nasm gdt

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

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

  1. 从包含内核文件的软盘加载14个扇区;
  2. 搜索标有kernel.feo;
  3. 的这些部门中的文件
  4. 将该文件加载到内存中以偏移0x2000;
  5. 使用远程跳转jmp 0x2000:0x0000执行内核。
  6. 所以我的内核代码位于内存中的0x2000:0CS可能已正确设置,因为使用了远程跳转。在这个内核代码中,我想进入32位保护模式,但我不确定GDT是如何工作的。当我在虚拟机(QEMU)上运行以下代码时,它不会做任何事情。

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

      

    那就是说,你有以下问题:

         
        
    1. 您认为由于0x7c00:0,代码已在org 0加载,但情况可能并非如此。唯一保证的是物理地址。您应该使用远程跳转到您的入口点,以便正确设置CS
    2.   
    3. 您出于某种原因将DS设置为0x2000,因此您的代码根本无法找到任何数据。您应该将DS设置为与CS匹配,或者在任何地方使用CS覆盖(不推荐)。
    4.   
    5. 受保护的模式代码假设从零开始,这意味着它需要org 0x7c00,这当然与您的设置冲突。您应切换为org 0x7c00和细分0
    6.   
    7. VGA文字模式片段位于0xb8000而不是0xb80000(少了一个零)。
    8.   
    9. 您在启动扇区末尾没有启动签名字节0x55 0xaa
    10.   

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

    1. [org 0x0]已更正为[org 0x2000],且细分受众群设置为0;
    2. DS已更正为0而非0x2000,因此现在它与CS匹配;
    3. VGA文字模式片段已更正为0xb8000;
    4. 但代码不能使用这些更正,它应该打印两个字符串,但它什么都不做!

      请注意,此内核代码不应以引导签名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 个答案:

答案 0 :(得分: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
  6. 固定代码:

    [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