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 010 Stack solo lectura
TYPE 100 Codigo solo ejecucion
TYPE 101 Codigo ejecucion / lectura
TYPE 110 Codigo solo ejecucion conformado
TYPE 111 Codigo ejecucion / lectura conformado
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:
El siguiente programa "switch_to_pm.asm" realiza la conmutación a modo protegido.
http://wiki.osdev.org/JohnBurger:Demo
http://wiki.osdev.org/Main_Page
http://wiki.osdev.org/Tutorials
32-bits | 16-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:
Una vez obtenida la maquina virtual la ejecutamos desde VirtualBox y obtenemos el resultado que se muestra en la figura.
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.
Bibliografia
http://wiki.osdev.org/JohnBurger:Demo
http://wiki.osdev.org/Main_Page
http://wiki.osdev.org/Tutorials