How to write your own Operating System (x86_64): Bootloader, 2nd Stage (long mode)

Πως να γράψετε το δικό σας Λειτουργικό Σύστημα (x86_64): Εκκινητής, 2o Στάδιο (μακρά κατάσταση)

· Coding Προγραμματισμός · assembly assembly os λειτουργικό σύστημα

IntroductionΕισαγωγή

In our previous article, we showed how to implement the first stage of the bootloader. Now, let's proceed to the second stage. First of all, in order for the execution to continue to the second stage, we have to remove the relevant comment from the jmp instruction in stage1/bootstage1.asm:

Σε προηγούμενο άρθρο μας, δείξαμε πως υλοποιούμε το πρώτο στάδιο του εκκινητή. Εδώ, θα εξετάσουμε το δεύτερο στάδιο. Πρώτα από όλα, για να προχωρήσει η εκτέλεση στο δεύτερο στάδιο, πρέπει να αφαιρέσουμε το σχετικό σχόλιο από την εντολή jmp στο stage1/bootstage1.asm:

    ...
    ; Jump to the entry point of stage 2
    jmp Stage2_entrypoint 
    ...

Stage 2 codeΚώδικας του 2ου σταδίου

In the x86_64 computer architecture, long mode is the architecture's intended primary mode of operation, where a 64-bit operating system can access 64-bit instructions and registers. Let's see some things we need in order to enter the long mode:

  1. We check if the long mode is supported.
  2. We activate the A20 gate / address line.
  3. We prepare the memory paging.
  4. We remap the PIC (Programmable Interrupt Controller).
  5. Then, we enter the long mode and pass the control to the Kernel.

Let's examine the code for the second stage:

Στην αρχιτεκτονική υπολογιστών x86_64, η προορισμένη κατάσταση λειτουργίας είναι η λεγόμενη μακρά κατάσταση (long mode) όπου ενα 64-bit λειτουργικό σύστημα έχει πρόσβαση στις 64-bit εντολές και τους 64-bit καταχωρητές. Ας δούμε τι χρειάζεται να κάνουμε ώστε να μπούμε στην λειτουργία μακράς κατάστασης:

  1. Ελέγχουμε αν η λειτουργία μακράς κατάστασης υποστηρίζεται.
  2. Ενεργοποιούμε την πύλη / γραμμή διεύθυνσης A20.
  3. Προετοιμάζομε τη σελιδοποίηση της μνήμης.
  4. Επανακαθορίζουμε τον Προγραμματιζόμενο Ελεγκτή Διακοπών (PIC).
  5. Τέλος, εισερχόμαστε στη λειτουργία μακράς κατάστασης και δίνουμε τον έλεγχο στον Πυρήνα.

Ας δούμε, λοιπόν, τον κώδικα για το δεύτερο στάδιο:

stage2/bootstage2.asm

BITS 16

;---Initialized data------------------------------------------------------------
stage2_message dw 19
db 'Entering Stage 2...'
longmode_supported_message dw 23
db 'Long mode is supported.'
longmode_not_supported_message dw 27
db 'Long mode is not supported.'

;---Code------------------------------------------------------------------------
Stage2_entrypoint:
      ; Print 'Entering Stage 2...' message
      mov si, stage2_message
      call Real_mode_println

      ; Check if long mode is supported
      call Is_longmode_supported
      test eax, eax
      jz .long_mode_not_supported
      mov si, longmode_supported_message
      call Real_mode_println

      ; Enable Gate A20
      call Enable_A20
      ; Prepare paging  
      call Prepare_paging
      ; Remap PIC
      call Remap_PIC
      ; Enter long mode
      call Enter_long_mode

     .long_mode_not_supported:
        mov si, longmode_not_supported_message
        call Real_mode_println
       .halt: hlt ; Infinite loop. 
        jmp .halt ; (It prevents us from going off in memory and executing junk).


; Include
%include "stage2/a20.asm"
%include "stage2/paging.asm"
%include "stage2/pic.asm"
%include "stage2/longmode.asm"

stage2/a20.asm

When IBM PC AT was released in 1984 (designed around the Intel 80286 microprocessor), for the sake of backward compatibility, a quirk in the 8086 microprocessor architecture (a memory wraparound) had to be replicated. You can read more about this issue here. Thus, the A20 line on the address bus was (and still is in many systems) disabled by default. This means that if we want to gain access to the full memory range, we have to enable the A20 line. There are several different ways (that may or may not be supported) and some of them may cause problems on some computers. Therefore, the recommended method is to try all of them, in the order of least risk, until one works:

  1. We test if A20 is already enabled. If it is, we don't need to do anything at all.
  2. We try to enable A20 using the BIOS function (we ignore the returned status).
  3. We test if A20 is enabled (to see if the BIOS function actually worked or not).
  4. We try to enable A20 using the keyboard controller method.
  5. We test if A20 is enabled in a loop with a time-out (as the keyboard controller method may work slowly).
  6. We try to enable A20 using the "Fast A20" method last.
  7. We test if A20 is enabled in a loop with a time-out (as the "Fast A20" method may work slowly).
  8. If none of the above worked, we give up.

Το 1984 κυκλοφόρησε ο IBM PC AT, σχεδιασμένος γύρω από τον μικροεπεξεργαστη 80286 της Intel. Για χάρη της προς τα πίσω συμβατότητας, μια ιδιοτροπία στην αρχιτεκτονική του μικροεπεξεργαστή 8086 (μια κυκλική επικάλυψη στην πρόσβαση της μνήμης) έπρεπε να αναπαραχθεί και σε αυτόν. Μπορείτε να διαβάσετε περισσότερα για αυτό το θέμα εδώ. Η ουσία είναι ότι η γραμμή Α20 στο δίαυλο διευθύνσεων ήταν (και εξακολουθεί να είναι σε πολλά συστήματα) αρχικά απενεργοποιημένη. Αυτό σημαίνει ότι αν θέλουμε να αποκτήσουμε πρόσβαση στο πλήρες εύρος της μνήνης, πρέπει να ενεργοποιήσουμε τη γραμμή Α20. Υπάρχουν αρκετοί διαφορετικοί τρόποι για να το επιτύχουμε αυτό (που μπορει να υποστηρίζονται ή όχι) και κάποιοι από αυτούς μπορεί να δημιουργήσουν προβλήματα σε κάποια συστήματα. Ως εκ τούτου, η συνιστώμενη μέθοδος είναι να δοκιμάσουμε όλους τους τρόπους κατά σειρά αυξανόμενου ρίσκου, μέχρι κάποιος να δουλέψει:

  1. Ελέγχουμε αν η A20 είναι ήδη ενεργοποιημένη. Αν είναι, δε χρειάζεται να κάνουμε κάτι άλλο.
  2. Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τη λειτουργία του BIOS (αγνοούμε την τιμή που μας γυρίζει).
  3. Ελέγχουμε αν η A20 ενεργοποιήθηκε (για να δούμε αν η λειτουργία του BIOS δούλεψε στην πράξη ή όχι).
  4. Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τον ελεκτή του πληκτρολογίου.
  5. Ελέγχουμε αν η A20 ενεργοποιήθηκε σε ένα βρόχο με κάποιο χρονικό τέλος (μιας και η μέθοδος με τον ελεκτή του πληκτρολογίου μπορεί να πάρει κάποιο χρόνο για να δουλέψει).
  6. Δοκιμάζουμε να ενεργοποιήσουμε την Α20, χρησιμοποιώντας τη μέθοδο "Fast A20" τελευταία.
  7. Ελέγχουμε αν η A20 ενεργοποιήθηκε σε ένα βρόχο με κάποιο χρονικό τέλος (μιας και η μέθοδος "Fast A20" μπορεί να πάρει κάποιο χρόνο για να δουλέψει).
  8. Αν δε δούλεψε κανένα από τα παραπάνω, τα παρατάμε.

BITS 16

;---Initialized data------------------------------------------------------------
a20_enabled_message dw 15
db 'A20 is enabled.'
a20_disabled_message dw 16
db 'A20 is disabled.' 
a20_trying_bios dw 34
db 'Trying to enable A20 using BIOS...'
a20_trying_keyb dw 49
db 'Trying to enable A20 using Keyboard Controller...'
a20_trying_io92 dw 40
db 'Trying to enable A20 using IO port 92...'

;---Code------------------------------------------------------------------------
Enable_A20:
    call Check_A20 ; Check if A20 is already enabled.
    test ax, ax
    jnz .end

    ; Try to enable A20 using BIOS.
    mov si, a20_trying_bios
    call Real_mode_println  
    call Enable_A20_using_BIOS 

    call Check_A20 ; Check if A20 is enabled.
    test ax, ax
    jnz .end

    ; Try to enable A20 using eEyboard Controller.
    mov si, a20_trying_keyb 
    call Real_mode_println
    call Enable_A20_using_Keyboard_Controller 

    call Check_A20 ; Check if A20 is enabled.
    test ax, ax
    jnz .end

    ; Try to enable A20 using IO port 92h (Fast A20 method).
    mov si, a20_trying_io92 
    call Real_mode_println
    call Enable_A20_using_Keyboard_Controller

    call Check_A20 ; Check if A20 is enabled.
    test ax, ax
    jnz .end
   .halt: hlt 
    jmp .halt ; Infinite loop.
   .end:
    ret


Check_A20:
;********************************************************************;
; Check the status of the A20 line                                   ;
;********************************************************************;
    call Real_mode_check_A20
    test ax, ax
    jnz .a20_enabled
        mov si, a20_disabled_message
        call Real_mode_println
        ret
   .a20_enabled:
        mov si, a20_enabled_message
        call Real_mode_println  
        ret
                

Real_mode_check_A20:
;**************************************************************************;
; Check the status of the A20 line (in real mode)                          ;
;--------------------------------------------------------------------------;
; Returns: ax = 0 if the a20 line is disabled (memory wraps around)        ;
;          ax = 1 if the a20 line is enabled (memory does not wrap around) ;
;**************************************************************************;
    pushf
    push ds
    push es
    push di
    push si
    cli ; clear interrupts
    
    xor ax, ax ; ax = 0
    mov es, ax ; es = 0
    not ax     ; ax = 0xFFFF
    mov ds, ax ; ds = 0xFFFF
    mov di, 0x0500 ; 0500 and 0510 are chosen since they are guaranteed to be free 
    mov si, 0x0510 ; for use at any point of time after BIOS initialization.

    ; save the original values found at these addresses.
    mov dl, byte [es:di]  
    push dx
    mov dl, byte [ds:si]
    push dx
    
    mov byte [es:di], 0x00 ; [es:di] is 0:0500
    mov byte [ds:si], 0xFF ; [ds:si] is FFFF:0510 
    cmp byte [es:di], 0xFF ; if the A20 line is disabled, [es:di] will contain 0xFF
                           ; (as the write to [ds:si] really occured to 00500).

    mov ax, 0 ; A20 disabled ([es:di] equal to 0xFF).
    je .a20_disabled
    mov ax, 1 ; A20 enabled.
   .a20_disabled:

    ; restore original values
    pop dx  
    mov byte [ds:si], dl
    pop dx
    mov byte [es:di], dl
   
    pop si
    pop di
    pop es
    pop ds
    popf
    sti ; Enable interrupts.
    ret


Enable_A20_using_BIOS:
;*************************************************************;
; Try to enable A20 gate using the BIOS (int 15h, ax = 2401h) ;
;-------------------------------------------------------------;
; Returns: ax = 0 (Failure)                                   ;
;          ax = 1 (Success)                                   ;
;*************************************************************;
    mov ax,2403h  ; Query A20 gate Support (later PS/2s systems)
    int 15h     
    jb .failure   ; INT 15h is not supported                
    cmp ah, 0
    jnz .failure  ; INT 15h is not supported
    mov ax, 2402h ; Get A20 gate Status
    int 15h
    jb .failure   ; Couldn't get status
    cmp ah, 0
    jnz .failure  ; Couldn't get status
    cmp al, 1     
    jz .success   ; A20 is already activated
    mov ax, 2401h ; Enable A20 gate 
    int 15h     
    jb .failure   ; Couldn't enable the A20 gate
    cmp ah, 0 
    jnz .failure  ; Couldn't enable the A20 gate
   .success:
        mov ax, 1
        ret
   .failure:
        mov ax, 0
        ret


Disable_A20_using_BIOS:
;**************************************************************;
; Try to disable A20 gate using the BIOS (int 15h, ax = 2400h) ;
;**************************************************************;
    mov ax, 2400h
    int 15h
    ret


Enable_A20_using_Keyboard_Controller:
;******************************************************************;
; Try to enable A20 line using the Keyboard Controller (chip 8042) ;
;------------------------------------------------------------------;
; Returns: ax = 0 (Failure)                                        ;
;          ax = 1 (Success)                                        ;
;******************************************************************;
    cli           ; Clear interrupts.
    call a20wait
    mov al, 0xAD  ; Disable keyboard.
    out 0x64, al
    call a20wait
    mov al, 0xD0  ; Read from input.
    out 0x64, al
    call a20wait2 
    in al,0x60
    push eax
    call a20wait
    mov al, 0xD1  ; Write to output.
    out 0x64, al
    call a20wait
    pop eax
    or al, 2
    out 0x60, al
    call a20wait
    mov al, 0xAE  ; Enable keyboard.
    out 0x64, al
    call a20wait
    sti           ; Enables interrupts.
    ret

a20wait:
    in      al, 0x64
    test    al, 2
    jnz     a20wait
    ret

a20wait2:
    in      al, 0x64
    test    al, 1
    jz      a20wait2
    ret


;*********************************************************************;
; Enable A20 Line via IO port 92h (Fast A20 method)                   ;
;---------------------------------------------------------------------;
; This method is quite dangerous because it may cause conflicts with  ;
; some hardware devices forcing the system to halt.                   ;
;=====================================================================;
; Bits of port 92h                                                    ;
;---------------------------------------------------------------------;
; Bit 0 - Setting to 1 causes a fast reset                            ;
; Bit 1 - 0: disable A20, 1: enable A20                               ;
; Bit 2 - Manufacturer defined                                        ;      
; Bit 3 - power on password bytes. 0: accessible, 1: inaccessible     ;
; Bits 4-5 - Manufacturer defined                                     ;
; Bits 6-7 - 00: HDD activity LED off, 01 or any value is "on"        ;
;*********************************************************************;
Enable_A20_using_IO_port_92:
    in al, 0x92  ; Read from port 0x92
    test al, 2   ; Check if bit 1 (i.e. the 2nd bit) is set.
    jnz .end     ; If bit 1 (i.e. the 2nd bit) is already set don't do anything.
    or al, 2     ; Activate bit 1 (i.e. the 2nd bit).
    and al, 0xFE ; Make sure bit 0 is 0 (it causes a fast reset).
    out 0x92, al ; Write to port 0x92
   .end:
    ret

stage2/paging.asm

Here is the code that sets the memory paging:

Εδώ είναι ο κώδικας που καθορίζει τη σελιδοποίηση της μνήμης:

BITS 16

;---Define----------------------------------------------------------------------
%define PAGE_PRESENT (1 << 0)
%define PAGE_WRITE   (1 << 1)
%define CODE_SEG      0x0008
%define PAGING_DATA   0x9000

;---Initialized data------------------------------------------------------------

;****************************************************************************************;
; Global Descriptor Table (GDT)                                                          ;
;****************************************************************************************;
; The Global Descriptor Table (GDT) is a data structure used by x86-family processors    ;
; (starting with the 80286) in order to define the characteristics of the various memory ;
; areas (segments) used during program execution, including the base address, the size,  ;
; and access privileges like executability and writability.                              ;
;****************************************************************************************;
GDT:
  .Null:
    dq 0x0000000000000000  ; Null Descriptor (should be present).
  .Code:
    dq 0x00209A0000000000  ; 64-bit code descriptor (exec/read).
    dq 0x0000920000000000  ; 64-bit data descriptor (read/write).
  ALIGN 4
    dw 0                   ; Padding (to make the "address of the GDT" field aligned on a 4-byte boundary).
  .Pointer:
    dw $ - GDT - 1         ; 16-bit Size (Limit) of GDT.
    dd GDT                 ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit)


;---Code------------------------------------------------------------------------
Prepare_paging:
;*******************************************************************************************;
; Prepare paging                                                                            ;
;-------------------------------------------------------------------------------------------;
; ES:EDI Should point to a valid page-aligned 16KiB buffer, for the PML4, PDPT, PD and a PT.;
; SS:ESP Should point to memory that can be used as a small (1 uint32_t) stack.             ;
;*******************************************************************************************;
    mov edi, PAGING_DATA ; Point edi to a free space to create the paging structures.

    ; Zero out the 16KiB buffer. Since we are doing a rep stosd, count should be bytes/4.   
    push di         ; REP STOSD alters DI.
    mov ecx, 0x1000
    xor eax, eax
    cld
    rep stosd
    pop di          ; Get DI back.
 
    ; Build the Page Map Level 4. ES:DI points to the Page Map Level 4 table.
    lea eax, [es:di + 0x1000]         ; EAX = Address of the Page Directory Pointer Table.
    or eax, PAGE_PRESENT | PAGE_WRITE ; OR EAX with the flags (present flag, writable flag).
    mov [es:di], eax                  ; Store the value of EAX as the first PML4E.

     ; Build the Page Directory Pointer Table.
    lea eax, [es:di + 0x2000]         ; Put the address of the Page Directory in to EAX.
    or eax, PAGE_PRESENT | PAGE_WRITE ; OR EAX with the flags (present flag, writable flag).
    mov [es:di + 0x1000], eax         ; Store the value of EAX as the first PDPTE.

     ; Build the Page Directory.
    lea eax, [es:di + 0x3000]          ; Put the address of the Page Table in to EAX.
    or eax, PAGE_PRESENT | PAGE_WRITE  ; OR EAX with the flags (present flag, writable flag).
    mov [es:di + 0x2000], eax          ; Store to value of EAX as the first PDE.

    push di                            ; Save DI for the time being.
    lea di, [di + 0x3000]              ; Point DI to the page table.
    mov eax, PAGE_PRESENT | PAGE_WRITE ; Move the flags into EAX - and point it to 0x0000.

    ; Build the Page Table.
    .LoopPageTable:
        mov [es:di], eax
        add eax, 0x1000
        add di, 8
        cmp eax, 0x200000               ; If we did all 2MiB, end.
        jb .LoopPageTable

    pop di                              ; Restore DI.
    ret

stage2/pic.asm

The Programmable Interrupt Controller (PIC) is one of the most important chips in the x86 architecture. Its function is to manage hardware interrupts and send them to the appropriate system interrupts. This allows the system to respond to devices without the need for polling them all the time. In protected mode and long mode, the Interrupt Requests (IRQs) 0-15 are in conflict with the CPU exceptions which reserve the first 32 positions (0-31 or 0x00-0x1F). Thus, prior to switching to the long mode, we have to change the PIC's offsets (a.k.a. remapping the PIC) so that IRQs use non-reserved vectors:

O Προγραμματιζόμενος Ελεγκτής Διακοπών (Programmable Interrupt Controller, PIC) είναι ένα από τα πιο σημαντικά ολοκληρωμένα κυκλώματα στην αρχιτεκτονικη x86. Η λειτούργια του είναι να διαχειρίζεται τις διακοπές υλικού (hardware interrupts) και να τις στέλνει στις κατάλληλες διακοπές συστήματος. Αυτό επιτρέπει στο σύστημα να ανταποκρίνεται στις συσκευές χωρίς την ανάγκη να τις βολιδοσκοπεί περιοδικά (polling) όλη την ώρα. Στην προστατευμένη κατάσταση λειτουργίας (protected mode) και στην μακρά κατάσταση λειτουργίας (long mode), οι αιτήσεις διακοπών (Interrupt Requests, IRQs) 0-15 συγκρούονται με τις εξαιρέσεις του επεξεργαστή (CPU exceptions) που δεσμεύουν τις πρώτες 32 θέσεις (0-31 ή 0x00-0x1F). Ως εκ τούτου, πριν τη μετάβαση στην λειτουργία μακράς κατάστασης, πρέπει να μετατοπίσουμε τα IRQs σε μη δεσμευμένες θέσεις στον PIC (αυτή η διαδικασία είναι γνωστή και ως "remapping the PIC"):

; PIC (Programmable Interrupt Controller)

BITS 16

;---Constants----------------------------------------------------------------------
PIC1_COMMAND    equ 0x20 ; Command port of 1st PIC
PIC1_DATA       equ 0x21 ; Data port of 1st PIC
PIC2_COMMAND    equ 0xA0 ; Command port of 2nd PIC
PIC2_DATA       equ 0xA1 ; Data port of 2nd PIC
PIC_EOI         equ 0x20 ; EOI (End of interrupt) command (= 0x20)

ICW1_ICW4       equ 0x01 ; Initialization Command Word 4 is needed
ICW1_SINGLE     equ 0x02 ; Single mode (0: Cascade mode)
ICW1_INTERVAL4  equ 0x04 ; Call address interval 4 (0: 8)
ICW1_LEVEL      equ 0x08 ; Level triggered mode (0: Edge mode)
ICW1_INIT       equ 0x10 ; Initialization - required!

ICW4_8086       equ 0x01 ; 8086/88 mode (0: MCS-80/85 mode)
ICW4_AUTO_EOI   equ 0x02 ; Auto End Of Interrupt (0: Normal EOI)
ICW4_BUF_SLAVE  equ 0x08 ; Buffered mode/slave
ICW4_BUF_MASTER equ 0x0C ; Buffered mode/master
ICW4_SFNM       equ 0x10 ; Special Fully Nested Mode


;---Code---------------------------------------------------------------------------
Remap_PIC:
;***************************************************************************;
; In protected / long mode, the IRQs 0-15 conflict with the CPU exceptions  ;
; (which are reserved up until 0x1F). It is thus recommended to change the  ;
; PIC's offsets (remapping the PIC) so that IRQs use non-reserved  vectors. ; 
; A common choice is to move them to the beginning of the available range:  ;
; IRQs 0..15 -> INT 0x20..0x2F (30..47). For that, we need to set the 1st   ;
; PIC's offset to 0x20 (32) and the 2nd's to 0x28 (40).                     ;  
;***************************************************************************;
    push ax

    ; Disable IRQs
    mov al, 0xFF ; Out 0xFF to 0xA1 and 0x21 to mask/disable all IRQs.
    out PIC1_DATA, al
    out PIC2_DATA, al
    nop
    nop

    ; Remap PIC
    mov al, ICW1_INIT | ICW1_ICW4 ; ICW1: Send initialization command (= 0x11) to both PICs 
    out PIC1_COMMAND, al    
    out PIC2_COMMAND, al     
    mov al, 0x20       ; ICW2: Set vector offset of 1st PIC to 0x20 (i.e. IRQ0 => INT 32) 
    out PIC1_DATA, al
    mov al, 0x28       ; ICW2: Set vector offset of 2nd PIC to 0x28 (i.e. IRQ8 => INT 40)    
    out PIC2_DATA, al
    mov al, 4          ; ICW3: tell 1st PIC that there is a 2nd PIC at IRQ2 (= 00000100)
    out PIC1_DATA, al       
    mov al, 2          ; ICW3: tell 2nd PIC its "cascade" identity (= 00000010) 
    out PIC2_DATA, al        
    mov al, ICW4_8086  ; ICW4: Set mode to 8086/88 mode
    out PIC1_DATA, al
    out PIC2_DATA, al

    mov al, 0xFF       ; OCW1: We mask all interrupts (until we set a proper IDT in Kernel)
    out PIC1_DATA, al
    out PIC2_DATA, al

    pop ax
    ret

stage2/longmode.asm

Here is the code for checking if the long mode is supported and the code for entering the long mode:

Εδώ βρίσκεται ο κώδικας που έχει να κάνει με τον έλεγχο υποστήριξης και τη μετάβαση στην λειτουργία μακράς κατάστασης (long mode):

BITS 16

;---Code------------------------------------------------------------------------
Is_longmode_supported:
;********************************************************************;
; Check if Long mode is supported                                    ;
;--------------------------------------------------------------------;
; Returns: eax = 0 if Long mode is NOT supported, else non-zero.     ;
;********************************************************************;
    mov eax, 0x80000000 ; Test if extended processor info in available.  
    cpuid                
    cmp eax, 0x80000001 
    jb .not_supported     

    mov eax, 0x80000001 ; After calling CPUID with EAX = 0x80000001, 
    cpuid               ; all AMD64 compliant processors have the longmode-capable-bit
    test edx, (1 << 29) ; (bit 29) turned on in the EDX (extended feature flags).

    jz .not_supported   ; If it's not set, there is no long mode.
    ret

   .not_supported:
      xor eax, eax
      ret


Enter_long_mode:
;********************************************************************;
; Enter long mode                                                    ;
;********************************************************************;
    mov edi, PAGING_DATA; Point edi at the PAGING_DATA.
    mov eax, 10100000b  ; Set the PAE and PGE bit.
    mov cr4, eax
    mov edx, edi        ; Point CR3 at the PML4.
    mov cr3, edx
    mov ecx, 0xC0000080 ; Read from the EFER MSR. 
    rdmsr    
    or eax, 0x00000100  ; Set the LME bit.
    wrmsr
    mov ebx, cr0        ; Activate long mode
    or ebx, 0x80000001  ; by enabling paging and protection simultaneously.
    mov cr0, ebx                    
    lgdt [GDT.Pointer]  ; Load GDT.Pointer.
    jmp CODE_SEG:Kernel ; Load CS with 64 bit segment and flush the instruction cache.

kernel/kernel.asm

Here is some dummy code that write a message directly into the video RAM, just to test if our bootloader is functional:

Εδώ έχουμε έναν απλό κώδικα, ο οποίος γράφει έναν μήνυμα απευθείας στην περιοχή της μνήμης των γραφικών, απλά για να δούμε αν ο εκκινητή μας είναι λειτουργικός:

BITS 64  ; We have entered the long mode! :)

;---Define----------------------------------------------------------------------
%define DATA_SEG    0x0010
%define VRAM       0xB8000  

;---Code------------------------------------------------------------------------
Kernel:
;********************************************************************;
; Just some dummy code for now                                       ;
;********************************************************************;
    ; Set all segments registers to DATA_SEG
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
 
    ; Set EDI to point to Video RAM (0xB8000)
    mov edi, VRAM    
 
    ; Print "Hello World!"
    mov rax, 0x1F6C1F6C1F651F48    
    mov [edi], rax
    mov rax, 0x1F6F1F571F201F6F
    mov [edi + 8], rax
    mov rax, 0x1F211F641F6C1F72
    mov [edi + 16], rax
 
   .halt: hlt 
    jmp .halt ; Infinite loop.

Running our codeΕκτέλεση του κώδικά μας

We can test our code very easily using an emulator like QEMU:

Μπορούμε να δοκιμάσουμε τον κώδικά μας πολύ εύκολα, χρησιμοποιώντας έναν εξομοιωτή όπως ο QEMU:

$ make
$ qemu-system-x86_64 -drive format=raw,file=os.bin

Running Bootloader

Full codeΠλήρης κώδικας

You can download the full code from here: bootloader code

Μπορείτε να κατεβάσετε τον πλήρη κώδικα απο εδώ: κώδικας εκκινητή

ReferencesΑναφορές

See also...

Δείτε επίσης...