lunes, 21 de abril de 2014

Paso a modo protegido x86

Paso a modo protegido x86

En los blog anteriores inicializamos una máquina en modo real, cargamos sectores del disco en memoria y ahora vamos a conmutar a modo protegido. Antes de hacer esto debemos aprender a escribir en la pantalla sin utilizar la funciones del BIOS e inicializar la Tabla de Descriptores Globales.
Para imprimir en pantalla guardamos los caracteres a imprimir en la memoria de video en la dirección 0xb8000, como queremos escribir en el segundo renglón le sumamos 160. El archivo se llamara "print_string_pm.asm"

_______________________________________________________________

VIDEO_MEMORY equ 0xb8000 + 160
WHITE_ON_BLACK equ 0x0f
; Imprime una cadena de caracteres terminadas en null apuntada por  EBX
print_string_pm:
           pusha
           mov edx, VIDEO_MEMORY    ;Se inicializa EDX a la segunda
                                    ;linea de la memoria de video.
print_string_pm_loop:
           mov al, [ebx]            ;El caracter apuntado por  EBX 
                                    ;se mueve a  AL
           mov ah, WHITE_ON_BLACK   ;Carga AH con el atributo de
                                    ;video
           cmp al, 0                ;si (al = 0) fin de la cadena
           je print_string_pm_done  ;si no salta a done
           mov [edx] , ax           ;Almacena el caracter en la
                                    ;memoria de video
           add ebx, 1               ;Incremento EBX al proximo
                                    ;caracter.
           add edx, 2               ;Apunto al proximo caracter
                                    ;en la memoria de video.
           jmp print_string_pm_loop ;loop a proximo caracter.
print_string_pm_done:
           popa
           ret  


___________________________________________________________________

El próximo paso es construir la GDT (Global descriptor Table). La tabla de descriptores globales se compone de registros que residen en la memoria como el que muestra la figura:




TYPE -A   A=0 Segmento no accedido
TYPE -A   A=0 Segmento  accedido

TYPE 000  Datos solo lectura
TYPE 001  Datos  lectura/escritura

TYPE 010  Stack  solo lectura
TYPE 011  Stack lectura/escritura

TYPE 100  Codigo solo ejecucion
TYPE 101  Codigo ejecucion /  lectura

TYPE 110  Codigo solo ejecucion conformado
TYPE 111  Codigo ejecucion /  lectura conformado

Cuando la CPU quiere acceder a memoria el segmento de código (CS) se compone de un índice un indicador de tabla y el nivel de privilegio que posee para acceder a memoria. 



A continuación inicializamos la memoria con tres descriptores, el primero nulo, uno con un segmento de código y otro con un segmento de datos mediante la lista de código en "gdt.asm" .


; GDT
gdt_start:
        gdt_null:          ; El descriptor nulo
        dd 0x0             ; 'dd' significa doble word (4 bytes)
        dd 0x0

gdt_code:                  ; El  segmento descriptor de codigo
                           ; base=0x0, limit=0xfffff ,
                           ; 1st flags: (present)1 (privilege)00 
                           ;(descriptor type)1 -> 1001b
                           ; tipo flags: (code)1 (conforming)0 
                           ;(readable)1 (accessed)0 -> 1010b
                           ; 2nd flags: (granularity)1 (32 -bit )1
                           ; (64 -bit seg)0 (AVL)0 -> 1100b
        dw 0xffff          ; Limite (bits 0 -15)
        dw 0x0             ; Base (bits 0 -15)
        db 0x0             ; Base (bits 16 -23)
        db 10011010b       ; 1001(P DPL S) 1010(type codigo no 
                           ;accedido)
        db 11001111b       ; 1100 ( G D/B 0 AVL)  ,1111  Limite
                           ; (bits 16 -19)
        db 0x0             ; Base (bits 24 -31)

gdt_data:                  ; El  segmento descriptor de datos
                           ; Igual que el segmento de código 
                           ;ecepto por los flags.
                           ; type flags: (code)0 (expand down)0 
                           ;(writable)1 (accessed)0 -> 0010b
        dw 0xffff          ; Limite (bits 0 -15)
        dw 0x0             ; Base (bits 0 -15)
        db 0x0             ; Base (bits 16 -23)
        db 10010010b       ;  1001(P DPL S) 0010(type codigo no 
                           ;accedido)
        db 11001111b       ; 1100 ( G D/B 0 AVL)  ,1111  Limite 
                           ;(bits 16 -19)
        db 0x0             ; Base (bits 24 -31)
gdt_end:                   ; El motivo para colocar un rotulo al 
                           ;final de la tabla GDT es que el     
                           ; compilador pueda calcular la longitud
                           ; de la tabla  gdt_end - gdt_start - 1
                             
; GDT descriptor
gdt_descriptor:
        dw gdt_end - gdt_start - 1 ; El tamaño de la tabla gdt es 
                           ;uno menos del calculado
        dd gdt_start       ; Dirección de comienzo del  GDT

; A continuacion se definen dos constantes utiles para el offset  que  los descriptores GDT
; deben contener cuando se entra en modo protegido. Por ejemplo ,
; cuando se setea DS = 0x10 en PM, El  CPU sabe que significa usar el segmento 
; descripto en el  offset 0x10 (i.e. 16 bytes) de la tabla GDT, la cual en nuestro caso 
; el segmento de datos (DATA segment)  (0x0 -> NULL; 0x08 -> CODE; 0x10 -> DATA)
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start


; GDT
gdt_start:
        gdt_null:          ; El descriptor nulo
        dd 0x0             ; 'dd' significa doble word (4 bytes)
        dd 0x0

gdt_code:                  ; El  segmento descriptor de codigo
                           ; base=0x0, limit=0xfffff ,
                           ; 1st flags: (present)1 (privilege)00
                           ; (descriptor type)1 -> 1001b
                           ; tipo flags: (code)1 (conforming)0 
                           ;(readable)1 (accessed)0 -> 1010b
                           ; 2nd flags: (granularity)1 (32 -bit )1 
                           ; (64 -bit seg)0 (AVL)0 -> 1100b
        dw 0xffff          ; Limite (bits 0 -15)
        dw 0x0             ; Base (bits 0 -15)
        db 0x0             ; Base (bits 16 -23)
        db 10011010b       ; 1001(P DPL S) 1010(type codigo no accedido)
        db 11001111b       ; 1100 ( G D/B 0 AVL),1111  Limite (bits 16 -19)
        db 0x0             ; Base (bits 24 -31)

gdt_data:                  ; El  segmento descriptor de datos
                           ; Igual que el segmento de código 
                           ; ecepto por los flags.
                           ; type flags: (code)0 (expand down)0 
                           ;(writable)1 (accessed)0 -> 0010b
        dw 0xffff          ; Limite (bits 0 -15)
        dw 0x0             ; Base (bits 0 -15)
        db 0x0             ; Base (bits 16 -23)
        db 10010010b       ;  1001(P DPL S) 0010(type codigo 
                           ; no accedido)
        db 11001111b       ; 1100 ( G D/B 0 AVL)  ,1111 
                           ; Limite (bits 16 -19)
        db 0x0             ; Base (bits 24 -31)
gdt_end:                   ; El motivo para colocar un rotulo al final 
                           ; de la tabla GDT es que el     
                           ; compilador pueda calcular la longitud de 
                           ; la tabla  gdt_end - gdt_start - 1
                             
; GDT descriptor
gdt_descriptor:
        dw gdt_end - gdt_start - 1 ; El tamaño de la tabla gdt es uno menos del calculado
        dd gdt_start                        ; Dirección de comienzo del  GDT

; A continuacion se definen dos constantes utiles para el offset  que  los descriptores GDT
; deben contener cuando se entra en modo protegido. Por ejemplo ,
; cuando se setea DS = 0x10 en PM, El  CPU sabe que significa usar el segmento 
; descripto en el  offset 0x10 (i.e. 16 bytes) de la tabla GDT, la cual en nuestro caso 
; el segmento de datos (DATA segment)  (0x0 -> NULL; 0x08 -> CODE; 0x10 -> DATA)
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start


Formato del GDTR:
32-bits16-bits
Dirección de la base de la tabla GDT
gdt_start
Limite de la tabla GDT
gdt_end - gdt_start - 1

El siguiente programa "switch_to_pm.asm" realiza la conmutación a modo protegido.


[ bits 16]
; Conmutamos a modo protegido
switch_to_pm:
         cli                     ;Apagamos las interrupciones hasta que hayamos  
                                 ; conmutado a modo protegido
         lgdt [ gdt_descriptor ] ; Cargamos la dirección y tamaño de la tabla GDT
         mov eax, cr0            ; Ponemos el bit 0 en uno del reg cr0 para pasar a modo protegido
         or eax, 0x1
         mov cr0, eax
         jmp CODE_SEG : init_pm  ; Hacemos un salto largo al nuevo segmento  de 32 bits
                                            ; El CPU fuerza a limpiar el cache 

[ bits 32]
; Inicializamos los registros de segmento y el stack.
init_pm :
         mov ax, DATA_SEG 
         mov ds, ax 
         mov ss, ax 
         mov es, ax
         mov fs, ax
         mov gs, ax
         mov ebp, 0x90000        ; Inicializamos el stack
         mov esp, ebp 
         call BEGIN_PM 

A continuación vemos el programa completo al cual llamamos "pm.asm".

; El sector de booteo que entra em modo protegido de 32 bits.
[org 0x7c00 ]
        mov bp, 0x9000 ; Set el stack.
        mov sp, bp
        mov si, MSG_REAL_MODE
        call print_string
        call switch_to_pm ; Note that we never return from here.
        jmp $
%include "print_string.asm"
%include "gdt.asm"
%include "print_string_pm.asm"
%include "switch_to_pm.asm"

; Aqui llegamos despues de haber conmutado a modo protegido .
BEGIN_PM:
        mov ebx, MSG_PROT_MODE
        call print_string_pm ; Usamos  nuestra rutina para imprimir en PM.
        jmp $ ; Hang.
; Variables Globales
MSG_REAL_MODE db " Comienza en  16 - bit Modo Real " , 0
MSG_PROT_MODE db " Paso exitosamente a 32 - bit Modo protegido ", 0
; Rellenamos el Bootsector 
times 510 -( $ - $$ ) db 0
dw 0xaa55


A este programa lo compilamos con el nasm mediante la sentencia:

nasm pm.asm -f bin -o pm.bin

Obtenemos un archivo pm.bin que ocupa exactamente 512  bytes.
Para crear el disco de Virtual Box arrancamos el programa y creamos una nueva VM ( ej. boot) en Type elegimos "other" y en versión elegimos "other/unknow".Elegimos el disco de longitud fija. Buscamos el archivo boot.vdi en la carpeta "Virtualbox VMs"




Ahora debemos escribir el archivo "pm.bin" en el sector 0 del disco de Virtualbox para realizar esto utilizamos un script en python.

Una vez obtenida la maquina virtual la ejecutamos desde VirtualBox y obtenemos el resultado que se muestra en la figura.


Bibliografia


http://wiki.osdev.org/JohnBurger:Demo

http://wiki.osdev.org/Main_Page

http://wiki.osdev.org/Tutorials

No hay comentarios:

Publicar un comentario