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

domingo, 20 de abril de 2014

Como cargar a memoria más sectores del disco de inicio

Como cargar a memoria más sectores del disco de inicio

En la página anterior escribimos un pequeño programa en asembler para escribir en pantalla "Hola Mundo" hacien uso de la Interrupción 10H, ahora vamos a utilizar la interrupción 13H para cargar sectores del disco a RAM, a esta subrutina la vamos a llamar "carga_sector.asm".

;carga DH sectores a ES:BX desde el disco DL
carga_sector:
push dx          ; Guarda DX en el stack para usarla posteriormente
mov ah, 0x02 ; Funcion del BIOS para leer sectores
mov al, dh      ; Leer DH sectores
mov ch, 0x00 ; Seleccionar cilindro 0
mov dh, 0x00 ; Seleccionar cabeza 0
mov cl, 0x02  ; Comenzar lectura desde el sector 2 
int 0x13         ; Interrupcion del BIOS 
jc disk_error   ; Si hay error de lectua el carry vale 1 y salta
pop dx           ; Restaura DX desde el  stack
cmp dh, al      ; si AL (sectores leidos) != DH (sectores solicitados)
jne disk_error ; Muesta mensaje de error
ret
disk_error:
mov si, DISK_ERROR_MSG
call print_string
jmp $
; Variables
DISK_ERROR_MSG db " Error de Lectura !" , 0

Para verificar que hemos leido dos sectores inicializamos el sector dos con la palabra 0xabcd y el sector 3 con la palabra 0x1234, para verificar la carga leemos una palabra del sector 2 y una del sector 3 en memoria y la mostramos en pantalla. Para esto debemos escribir una rutina que convierta numeros hexadecimales a caracteres ASCII y los imprima en pantalla.
Esta rutina la llamamos "print_hex.asm".

;convertimos a DX en su representacion hexadecimal en caracteres ASCIII
print_hex:
              mov   al, dh
call hex_to_char
              lea   bx, [STRING]
              mov   [bx], ax      ;Colocamos el caracter convertido en el string
              mov   al, dl
call hex_to_char
              lea   bx, [STRING+2]
              mov   [bx], ax      ;Colocamos la parte menos significativa a continuacion
mov si, HEX_OUT
call print_string   ; Para mostrar en pantalla usamos la rutina print_string
ret


TABLE:
            db "0123456789ABCDEF", 0

hex_to_char:
            lea   bx, [TABLE]
            mov   ah, al        ;hacemos al igual ah para tener el nibble bajo en al
            shr   ah, 4         ;y el nibble alto en ah
            and   al, 0x0F      ;
            xlat                ;buscamos en TABLA la representacion ASCII de al
            xchg  ah, al        ;intercambiamos al, con ah
            xlat                ;lbuscamos en TABLA la representacion ASCII de al
            ret

HEX_OUT: db 'Sector leido 0x'
STRING:  db '0000',0x0A,0x0D,0        

Ahora ya tenenos todos los elemento para armar el programa que cargue los dos sectores adicionales del disco en memoria y muestre en pantalla los valores de los sectores cargados.
Llamamos a este programa "boot.asm"

[BITS 16]
[org 0x7C00]
start:
mov [BOOT_DRIVE], dl ; El BIOS guarda el disco de inicio en DL.
                                                    ; Luego lo usaremos para cargar los otros dos sectores.
mov bp, 0x8000                 ; Seteamos el stack a este lugar seguro
mov sp, bp ; way, at 0x8000
mov bx, 0x9000                 ; Cargamos 5 sectores a 0x0000(ES):0x9000(BX)
mov dh, 5                           ; desde el disco de inicio.
mov dl, [ BOOT_DRIVE ]
call carga_sector
mov dx, [0x9000 ]              ; Mostramos en pantalla la primera palabra cargada desde el sector 2
call print_hex                      ; Esperamos que sea 0xabcd
mov dx, [0x9000 + 512]    ; Mostramos en pantalla la segunda palabra cargada
call print_hex                      ; Esperamos que sea 0x1234
jmp $
%include "./print_string.asm"
%include "./print_hex.asm" 
%include "./carga_sector.asm"

; Global variables

BOOT_DRIVE: db 0
; Relleno del sector 0
times 510 -( $ - $$ ) db 0
dw 0xaa55
times 256 dw 0xabcd
times 256 dw 0x1234

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

nasm boot.asm -f bin -o boot.bin

Obtenemos un archivo boot.bin que ocupa exactamente 512 x 3 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 os.vdi en la carpeta "Virtualbox VMs"



Ahora debemos escribir el archivo "boot.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.





    

lunes, 14 de abril de 2014

Como crear un disco booteable en VirtualBox

Como crear un disco booteable en VirtualBox

Una computadora cuando arranca carga el sector 0 del primer disco booteable.
Un disco para que sea booteable,  el sector (0) de booteo debe comenzar con codigo (asembler x86) y termina con 0XAA55.



En un disco virtual de VirtualBox en el byte 0x158 como se ve en la figura, se encuentra el puntero al primer sector del disco.


A continuación vamos a escribir un pequeño programa en assembler la computadora carga el sector 0 en la dirección 0x7C00 y transfiere el control a esta dirección, el programa escribe en la pantalla "Hola Mundo!" llamando a la interrupción del BIOS 10H, ya que a esta altura no existe ningún SO cargado.


[BITS 16]

[org 0x7C00]
 start: 
     mov si,MSG    
call print_string    
jmp $ 

print_string:           ; Espera un mensaje terminado en null en si    
mov al,[si]    
or al,al    
jz  .end 
     inc si    
call print_char 
     jmp print_string
 .end:    
retn

print_char: 
    mov ah,0x0E         ; Especifica que caracter queremos escribir en pantalla 
    mov bl,0x07           ; Especifica el color del texto    
mov bh,0x00          ; Numero de pagina. 
    int 0x10                   ; Interrupcion del  BIOS
     retn
;data    

MSG db 'Hola Mundo!',0x0A,0
TIMES 510 - ($ - $$) db 0

DW 0xAA55




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


nasm boot.asm -f bin -o boot.bin



Obtenemos un archivo boot.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 os.vdi en la carpeta "Virtualbox VMs"



Ahora debemos escribir el archivo "boot.bin" en el sector 0 del disco de Virtualbox para facilitar esta tarea se ha escrito un script en python.




#!/usr/bin/python

from struct import *

import sys, getopt ,os

l = long
def main(argv):
inputfile = ''
   outputfile = ''
try:
   opts, args = getopt.getopt(argv,"hi:o:",["ifile=","ofile="])
    except getopt.GetoptError:
     print 'vdiboot.py -i <inputfile> -o <outputfile>'
     sys.exit(2)
  for opt, arg in opts:
if opt == '-h':
print 'vdiboot.py -i <inputfile> -o <outputfile>'
   sys.exit()
     elif opt in ("-i", "--ifile"):
          inputfile = arg
     elif opt in ("-o", "--ofile"):
          outputfile = arg
leng = os.path.getsize(inputfile)
f = open(outputfile,'rb+')
g = open(inputfile,'rb')
f.seek(0x158)
byte = f.read(4)
l = unpack("<l", byte)
f.seek(l[0])
sector0 = g.read(leng)
f.wr ite(sector0)
print 'escribiendo sector ....'
f.close
g.close
if __name__ == "__main__":
        main(sys.argv[1:])

La secuencia de comandos para realizar toda la tarea es:

nasm boot.asm -f bin -o boot.bin
./vdiboot.py -i boot.bin -o boot.vdi
cp ./boot.vdi /home/usuario/VirtualBox\ VMs/boot/

Lanzamos en VirtualBox el sistema boot.vdi y podemos observar en la pantalla "Hola Mundo!


Bibligrafia


http://en.wikibooks.org/wiki/X86_Assembly/Bootloaders
http://www.codeproject.com/KB/tips/boot-loader.aspx?fid=1541607&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=1#_Toc231383187