title: How do I enter 32-bit protected mode in NASM assembly?
body: <p>I'm learning x86 assembly, and I'm trying to make a toy operating system in NASM, but I don't understand some things.</p>
  
  <p>I made a bootloader that is successfully boots my kernel:</p>
  
  <ol>
  <li>Loads 14 sectors from the diskette which contains the kernel file;</li>
  <li>Search a file in these sectors labeled <code>kernel.feo</code>;</li>
  <li>Loads that file into the memory to the offset <code>0x2000</code>;</li>
  <li>Executes the kernel using a far jump <code>jmp 0x2000:0x0000</code>.</li>
  </ol>
  
  <p>So I have the kernel code located at <code>0x2000:0</code> in the memory. <code>CS</code> might be properly set because the using of a far jump. In this kernel code, I want to enter 32-bit protected mode, but I'm not sure how GDTs are working. When I run the code below on a virtual machine <code>(QEMU)</code>, it is don't do anything.</p>
  
  <p>I want to please you to help me entering 32-bit protected mode!</p>
  
  <blockquote>
    <p>That said, you have the following problems:</p>
    
    <ol>
    <li>You assume the code is loaded at <code>0x7c00:0</code> due to the <code>org 0</code>, but that might not be the case. The only thing guaranteed is the physical address. You should use a far jump to your entry point so that <code>CS</code> is properly set.</li>
    <li>You are for some reason setting <code>DS</code> to <code>0x2000</code> so your code won't find any data at all. You should set <code>DS</code> to match <code>CS</code>, or use a <code>CS</code> override everywhere (not recommended).</li>
    <li>The protected mode code assumes zero-based segment, which in turn means it expects <code>org 0x7c00</code> which of course conflicts with your setup. You should switch to <code>org 0x7c00</code> and segments <code>0</code>.</li>
    <li>The VGA text mode segment is at <code>0xb8000</code> not <code>0xb80000</code> (one less zero).</li>
    <li>You don't have the boot signature bytes <code>0x55 0xaa</code> at the end of the boot sector.</li>
    </ol>
  </blockquote>
  
  <p>I have corrected these things in my code:</p>
  
  <ol>
  <li><code>[org 0x0]</code> is corrected to <code>[org 0x2000]</code> and segments are set to <code>0</code>;</li>
  <li><code>DS</code> is corrected to <code>0</code> instead of <code>0x2000</code>, so now it matches with <code>CS</code>;</li>
  <li>VGA Text Mode segment is corrected to <code>0xb8000</code>;</li>
  </ol>
  
  <p>But the code won't work with these corrections, it should print two strings but it don't do anything!</p>
  
  <p>Note that this kernel code should not end with a boot signature <code>0x55 0xAA</code>, because it isn't a boot sector.</p>
  
  <p>Here is the corrected kernel code (that not working):</p>
  
  <pre><code>[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 $
  </code></pre>