Search code examples
assemblyx86qemuosdevprotected-mode

A weird problem when transitioning to protected mode


I am trying to switch to protected mode after playing around a little bit with real mode, but something weird occurs. In real mode I have created a simple welcome screen asking the user to enter their name. After that, the switch to protected mode should occur, but this is what happens instead:

enter image description here

As you can see I press Enter and the background of a character starts changing, and then QEMU (the emulator I am using) restarts and re-displays the welcome screen.

Here is the code :

section startUp vstart=0x7f00

; clearing the screen 
    mov ah,0x00
    mov al,0x03   
    int 0x10
; disabling the cursor
    mov ah,0x01
    mov ch,0x3f 
    int 0x10
; using Bios to print character
    mov ah,0x0e
    mov al,'C'
    mov cx,0x01
    int 0x10
; printing a string 
    mov ax,msg
    mov bx,0x00
    mov cl,0x0f
    mov dx,0x00
    call printMsg
; print warning 
    mov ax,warning
    mov bx,0x00
    mov cl,0x04
    mov dx,0x3a
    call printMsg
; print message
    mov ax,msg2
    mov bx,0x00
    mov cl,0x0f
    mov dx,0x0140
    call printMsg
; input 
    mov si,keypress
    mov bx,0x00
    call input



; switching to protected mode 
    cli           ; disabling interrupts 
    lgdt [GDT_Descriptor]     
    mov eax,cr0
    or eax,0x01
    mov cr0,eax   ; here we are in 32 bit protected mode

    jmp protectedMode








; printing a string function 
printMsg:
    pusha 

    ; ax ==> offset , bx ==> segment , dl ==> Line a0*lineNumber - 1, cl ==> color 

    mov es,bx
    
    mov bx,0xB000
    mov ds,bx 

    mov si,ax
    
    mov di,0x8000
    add di,dx

    mov byte[es:0x7e00],0x00 
    jmp loopThrough

    
    loopThrough:
        mov al,byte[es:si]
        mov byte[ds:di], al

        add di,0x01
        mov byte[ds:di],cl
        sub di,0x01

        add si,0x01

        mov bl,byte[es:si]
        cmp bl,0x00
            
        je quit

        add di,0x02
        jmp loopThrough
            

    quit :
        popa
        ret



; input function kjjj
input:
    pusha
    ; si ==> offset , bx ==> segment 
    mov di,si 
    mov es,bx 

    jmp loopinterupt


    loopinterupt :
        mov ah,0x00
        int 0x16

        cmp al,0x20

        je quitInput


        mov byte[es:si],al
        add si,0x01
        mov byte[es:si],0x00

        mov ax,di
        mov dx,0x172
        mov cl,0x0f
        call printMsg

        
        jmp loopinterupt

    quitInput :
        popa 
        ret





; Data


; Setting Up the GDT
GDT :

times 8 db 0x00

; base : 0x100000   ; Limit : 0x00700
dw 0x0700  ; limit 1 
dw 0x0000  ; base 1 
db 0x10    ; base 2
db 0x9a    ; access Byte 
db 0xc0    ; limit + flags
db 0x00d    ; base 3


; base : 0x800000  ; limit : 0x00700 
dw 0x0700  ; limit 1 
dw 0x0000  ; base 1 
db 0x80    ; base 2
db 0x96    ; access Byte 
db 0xc0    ; limit + flags
db 0x00    ; base 3

GDT_Descriptor :
dw GDT_Descriptor - GDT - 1
dd GDT 





msg db 'Welcome to the OS fdffdfgd',0x00
msg2 db 'Please write your name : ',0x00
keypress db 'K',0x00
warning db ""


[bits 32]
protectedMode:
    jmp $


times 1024-($-$$) db 0 

So as you saw the code executes just fine until the jmp protectedMode is executed.


Solution

  • Following the Protected Mode guide on OSDev, one can see there are 3 steps involved.

    1. Disabling interrupts

    You are doing this correctly in your code by using the cli instruction.

    1. Enabling the A20 gate

    Due to horrible history, the hardware clears bit 20 of every address when accessing memory. You most likely don't want this, since it means that half of your address space can't be accessed. A full guide on how to disable this behavior is on OSDev, but just for a quick test on QEMU, this should work:

    mov ax, 0x2401
    int 0x15
    
    1. Creating and loading the GDT

    If you want to switch to 32-bit Protected Mode, you most likely want two segment descriptors to start with: code and data. Since you probably want access to the full 4GB address space, you should specify a 0 base and a 4GB limit for both of them. Therefore, the only bit that will differ between them is the code/data bit:

    ; Setting Up the GDT
    GDT :
    
    times 8 db 0x00
    
    ; code segment: base=0; limit=0xFFFFFFFF
    ; present, non-system, executable, non-conforming, read+exec
    dw 0xFFFF  ; limit 1 
    dw 0x0000  ; base 1 
    db 0x00    ; base 2
    db 0x9a    ; access Byte
    db 0xcf    ; limit + flags
    db 0x00    ; base 3
    
    
    ; data segment: base=0; limit=0xFFFFFFFF
    ; present, non-system, data segment, expand-up, read+write
    dw 0xFFFF  ; limit 1 
    dw 0x0000  ; base 1 
    db 0x00    ; base 2
    db 0x92    ; access Byte 
    db 0xcf    ; limit + flags
    db 0x00    ; base 3
    
    GDT_Descriptor :
    dw GDT_Descriptor - GDT - 1
    dd GDT
    

    These segments will allow you to use 32-bit registers like eax to address any memory location up to the 4GB mark.

    Finally, make sure that you are actually using these segments (by loading them into the segment registers), or you can cause pure chaos.


    Full mode-switching code:

    ; enable the A20 gate
    mov ax, 0x2401
    int 0x15
    
    ; disable interrupts
    cli
     ; we do it after the BIOS call
     ; who knows what sort of buggy BIOS may execute STI and not restore our flags properly
    
    ; load the GDT
    lgdt [GDT_Descriptor]
    
    ; enter protected mode (set bit 0 in cr0)
    mov eax, cr0
    or eax, 1
    mov cr0, eax
    
    ; jump to protected mode entry point
    jmp 0x08:protectedMode
     ; Here we use an absolute FAR JUMP, which reloads CS
     ; If we don't reload CS, the CPU will continue executing Protected Mode code using the old CS descriptor => chaos
     ; CS=0x08 because that is the offset of the code descriptor in the GDT
    
    ; ...
    
    ; protected mode entry point
    [bits 32]
    protectedMode:
    
    ; reload the other segment registers with the data descriptor
    mov eax, 0x10
    mov ds, eax
    mov es, eax
    mov fs, eax
    mov gs, eax
    
    ; VERY IMPORTANT: set up the stack
    mov ss, eax
    mov esp, you_decide_the_place