November 02, 2007

Bootstrap Simples


Introdução

Esse fim de semana andei estudando um pouco de assembly. Decidi fazer um bootstrap para ver se me animava um pouco, e acabou me voltando a vontade de realmente brincar de criar o meu sistema operacional. Como tudo precisa de um começo, decidi que o bootstrap seria um belo lugar para começar.
Vejamos então como é que fazemos para dar um boot na maquina e o que é que a(o) BIOS (Basic Input Output System) espera para iniciar o sistema operacional.
Quando o computador inicia e o POST termina, a BIOS faz uma chamada para ler o primeiro setor do disco que está definido como primário no setup. Funciona assim:

  1. Olha o primeiro setor do disco definido no setup
  2. Encontrou setor de boot válido?
  3. Sim. Lê o setor e carrega o código para o segmento endereço 7C00h na memória. (Valeu pela correção Muzgo :)
  4. Não. Parte para o próximo dispositivo da lista de boot e vai para a) até encontrar.
  5. Caso não encontre em nenhum dos dispositivos, exibe mensagem de erro padrão da BIOS.

A Lista de software que usei:

  • NASM - Para compilar o código
  • QEMU - Maquina virtual que uso para testar o setor de boot
  • dd - Utilizado para escrever os dados no disco

O setor de boot

Quando definimos o disco no qual desejamos dar o boot, a BIOS chama uma interrupção (19h se nao me engano), para ler o primeiro setor desse disco. O primeiro setor é o que se encontra na posição CHS (Cylinder Head Sector) 0:0:1. Cada setor no disco tem 512 bytes, então como a BIOS lê o primeiro setor do disco, nosso ‘programa’ precisa ter 512 bytes ou menos. Meu objetivo aqui não é de falar muito sobre HCS, maiores informações você pode encontrar aqui.
O que identifica se temos um setor de boot válido ou não?
O setor de boot tem uma ‘assinatura’, os últimos dois bytes do setor devem ser ‘0xAA55’. Quando essa assinatura é encontrada, os 512 bytes são carregados para a memória na posição 7C00h e o programa é executado.

O código

Como já diria o conde Drácula em ‘Castlevania: Symphony of the Night’: “Enough talk!”.
Vamos dar uma olhada então em um código bem simples, que imprime uma string na tela.

; Boot.asm
 
ORG 7C00h                   ; Posição onde estaremos quando o código for
 
                            ; carregado para a memória
 
mymsg: db 'Olá Setor de BOOT',10,0
 
xor ax,ax                   ; Limpando ax
 
mov si,mymsg                ; Nossa mensagem em SI
 
putstr:
        lodsb               ; Coloca o byte apontado em SI em AL, e incrementa
                            ; o contador
 
        or al,al            ; Verificamos se encontramos o byte '0' da string
        jz hang;            ; Se sim, paramos de imprimir
        mov ah,0x0E         ; Função para escrever
 
        mov bx,0x0007       ; Define a página e a cor onde escrevemos
        int 0x10            ; Interrupção de video
        jmp putstr          ; Imprimir proximo caracter
 
hang:
        jmp hang            ; Após impressão, entramos em loop infinito
 
times 512-($-$$)-2 DB 0     ; Preenchemos o resto da memória com '0's
 
                            ; até 510 bytes
DW 0xAA55                   ; Assinatura do setor de boot

Acredito que o código esteja auto explicativo. Você deve precisar manjar um pouco de assembly, então se não entendeu o código, procure estudar um pouquinho de assembly.
Vamos agora compilar o nosso código, criar um disco para o QEMU, e escrever o nosso setor de boot no disco.

$ nasm boot.asm -f bin -o boot.bin  
$ qemu-img create /tmp/boot.img -f qcow 1M  
$ dd if=boot.bin of=/tmp/boot.img

Com isso feito, basta agora executar o QEMU e dizer que desejamos usar o arquivo /tmp/boot.img como nosso disco.

$ qemu /tmp/boot.img -m 16

O resultado é a nossa frase impressa logo após a BIOS fazer o POST.

Colocando código no Disco

Bom, agora que já sabemos dar o boot, está na hora de colocarmos código no disco e executar esse código. Esse é um processo um pouco mais complicado, mas vamos que vamos!
A idéia agora é fazer com que o nosso programa de boot chame um binario que esteja gravado no disco e o execute. Então precisaremos escrever dois programas distintos: Um para ser o nosso setor de boot, e um que será chamado por ele. “Show me the code”

; Boot2.asm
 
ORG 7C00h
 
mov si,msg
 
prntMsg:                    ; Imprime a mensagem em SI
  lodsb
  mov ah,0x0E
 
  mov bx,0x0007
  int 10h
  or al,al
 
  jnz prntMsg
 
mov [drv],dl                ; DL contém o identificador da unidade em que 
                            ; o setor de boot foi encontrado
 
; Inicializar o disco. Aqui colocamos a cabeça do disco no inicio dele
 
.diskSetup
  mov ax,0        ; Função para resetar o disco rígido
  mov dl,[drv]    ; O drive que vamos resetar
 
  int 13h         ; Chama a interrupção de disco
  jc .diskSetup   ; Se der erro, tentamos de novo
 
; Após resetar o disco, colocaremos a cabeça no setor que o programa se 
 
; encontra e carregamos o programa na memória
 
.diskRead
  mov ah,02h    ; Função para ler o disco
  mov al,3      ; Ler 3 setores (512 * 3 bytes)
 
  mov ch,0      ; Apontar para o cilindro 0
  mov cl,0x02   ; Ler a partir do setor 2 (2, 3 e 4)
 
  mov dh,0      ; Cabeça 0
  mov dl,[drv]  ; Disco de onde queremos ler os dados
 
; Os dados que lemos com essa função são armazenados em ES:BX
; No nosso caso aqui entao teremos 1000h:0
 
  mov bx,0x1000
  mov es,bx
 
  mov bx,0
 
  int 13h       ; Interrupção do disco
  jc .diskRead  ; Em caso de erro, tenta de novo
 
jmp 1000h:0;    ; Aqui nós pulamos para o nosso código que acaba de ser
                ; carregado na memória
 
hang:
  jmp hang      ; Loop infinito
 
drv db 0
msg db 'Chamando programa do HD',13,10,0
 
times 512-($-$$)-2 db 0   ; Completa o espaço do setor que sobra com 0s
 
DW 0xAA55                 ; Assinatura do setor de boot nos ultimos dois bytes

Esse código então, como da para perceber, lê o código do disco, coloca o código na posição de memória 1000h:0 e pula para lá para começar a execução. Bom, precisamos agora então do programa que desejamos ler do disco e executar. Esse programa, assim como o primeiro exemplo vai simplesmente imprimir uma mensagem na tela.

;  Programa.asm
 
  mov ax, 1000h           ; Atualizar os registros de segmentos
 
  mov ds, ax
  mov es, ax
  mov si, msg             ; Mensagem em SI
 
putstr:
  lodsb
  or al,al
  jz hang
 
  mov ah,0x0E
  mov bx,0x0007
  int 0x10
 
  jmp putstr
 
hang:
  jmp hang
 
msg     db 'Bla bla bla!',13,10,0

Nesse programa nós atualizamos os registros de segmento para o mesmo endereço onde carregamos o programa do nosso HD, e executamos a rotina basica de imprimir a mensagem na tela, e entrar em loop infinito.
Para executar esse exemplo, os paso são parecidos. Vejamos:

$ nasm boot2.asm -f bin -o boot2.bin  
$ nasm programa.asm -f bin -o programa.bin  
$ qemu-img create ./boot2.img -f qcow 1M  
$ dd if=boot2.bin of=./boot2.img  
$ dd if=programa.bin of=./boot2.img bs=512 seek=1  
$ qemu ./boot.img -m 16

O comando ‘$ dd if=programa.bin of=./boot2.img bs=512 seek=1’ coloca o nosso código no segundo setor do disco, pois conforme definimos em nosso código, é la que nosso bootstrap está esperando encontra-lo.

Conclusão

Bom pessoal, é isso ai. Como o tpitulo disse, é um bootstrap simples, apenas para dar um gostinho de como funciona, e trazer mais animo para maiores pesquisas.
Espero que tenham gostado e que possa ser útil para alguém.

Até o próxmo :D