title: Switch from protected mode to real mode in a DOS EXE program
body: <p>I learned to switch to protected mode with a simple DOS based bootloader. This loader loads <code>kernel.bin</code> into a buffer and copies the buffer to 100000h (kernel is 8KiB). Control is then transferred to the kernel. When I return from the kernel and attempt to switch to real mode I encounter a problem.</p>
  
  <p>My FASM assembly code (Similar to my previous <a href="https://stackoverflow.com/questions/54679254/switching-to-protected-mode-from-dos-not-using-dpmi">Stackoverflow question</a>) is as follows:</p>
  
  <pre><code>format MZ
  push cs
  pop ds
  mov eax,cs
  shl eax,4
  mov [AdresSegmentuProgramu_32],eax ;Calculating real mode segment
  add eax,gdt_table
  mov [gdtr+2],eax
  mov ax,[AdresSegmentuProgramu_32+2]
  mov [code_realsegment_descriptor+3],ax  
  mov al,[AdresSegmentuProgramu_32+1]       ;Setting 16-bit descriptors to return from protected mode
      mov [code_realsegment_descriptor+2],al
      mov ax,[AdresSegmentuProgramu_32+2]
      mov [data_realsegment_descriptor+3],ax
  mov al,[AdresSegmentuProgramu_32+1]
  mov [data_realsegment_descriptor+2],al
  mov ax,3d00h
  mov dx,NazwaPliku
  int 21h
  mov bx,ax
  mov ax,3f00h
  mov cx,8192
  mov dx,KernelGDOS32
  int 21h
  mov ax,3e00h
  int 21h
  lgdt [gdtr]
  
  mov eax,[AdresSegmentuProgramu_32]
  
  add eax, pmode_entry ;Far jump to reset CS and jump to simple code
  
  mov [AdresSegmentu_PMODE_entry],eax
  
  mov [far_jump],eax   
  mov eax,[AdresSegmentuProgramu_32]   
  add eax,KernelGDOS32   
  mov [AdresSegmentu_KernelGDOS_32],eax
  
  cli                         ; Turn off interrupts
  
  mov eax,cr0
  
  or eax,1    ;Switch to PMODE
  
  mov cr0,eax
  
  ; Move code that was between turning on protectedmode bit and the FAR JMP   
  ; to an earlier point.  
  ; We need to do a FAR JMP but with a 32-bit offset and a 16-bit selector   
  ; rather than a 16-bit offset and 16-bit selector. A 16-bit offset will   
  ; not work for any address greater than 64KiB. Use FWORD modifier to allow   
  ; a 32-bit offset in the indirect address for the JMP FAR.
  ; FWORD = 48 bit address (32+16)
  
  mov edi,[AdresSegmentu_KernelGDOS_32]
  
  jmp far fword [ds:far_jump]
  
  far_jump:   
  dd 0
  dw 08h ; Selector 0x08
  
  gdtr: dw 128   
  dd 0
  
  ldtr:   
  dw 0x3ff   
  dd 0
  
  AdresSegmentuProgramu_32 : dd 0
  AdresSegmentu_PMODE_entry dd 0
  AdresSegmentu_KernelGDOS_32 dd 0
  
  NazwaPliku db 'kernel.bin',0
  use32
  align 4
  gdt_table:
  dq 0   ;NULL Descriptor
  code_descriptor:
  dw 0ffffh
  dw 0                  ;8h
  db 0
  db 09ah
  db 11001111b
  db 0
  data_descriptor: ;I prefer flat mode. There are two descriptors for data and code points to 0.
  dw 0ffffh
  dw 0
  db 0                   ;10h
  db 092h
  db 11001111b
  db 0
  stack_descriptor:
  dw 0ffffh
  dw 0
  db 0                      ;18h
  db 096h
  db 11001111b
  db 0
  code_realsegment_descriptor:
  dw 0ffffh                      ;20h
  dw 0
  db 0
  db 09ah
  db 10001111b
  db 0
  data_realsegment_descriptor:
  dw 0ffffh
  dw 0
  db 0
  db 092h                           ;28h
  db 10001111b
  db 0
  stack_realsegment_descriptor : dq 0
  TIMES 8 dq 0
  KernelGDOS32 : TIMES 8192 db 0 ;Jądro naszego systemu który zostanie załadowany 
  do obszaru pamięci powyżej 1 MB.
  pmode_entry:
  
  ;this section works correctly
  
  mov ebx,100000h
  mov ax, 10h ;Set DS to a 32-bit data selector
  mov ds, ax
  mov ax,18h  ;Set SS to a 32-bit stack selector
  mov ss,ax
  mov esi,100000h
  xor cx,cx
  petla_glowna:
  mov al,[edi]
  mov [esi],al
  inc esi
  inc edi
  cmp cx,8192
  inc cx
  jne petla_glowna
  
  mov esp,200000h
  call dword ebx
  
  mov eax,0
  mov cr3,eax
  
  use16
  
  ;but i want to switch to real mode cpu generates protection fault
  ;I was reading osdev.org instructions and still nothing 
  
  mov ax,0x28
  mov ds,ax
  mov es,ax   
  mov fs,ax
  mov gs,ax
  mov ss,ax
  mov sp,16000
  jmp far 0x20:entry16
  entry16:
  mov eax,cr0
  mov eax,0    ;back to real mode
  mov cr0,eax
  
  jmp dword 0:rmode   ;far jump
  rmode:
  
  mov sp,16000
  mov ax,0
  mov ds,ax
  mov es,ax
  mov fs,ax
  mov es,ax
  mov ss,ax
  
  lidt [ldtr]
  sti
  
  mov ax,4c00h ;Exit process
  int 21h
  </code></pre>