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:
a) Olha o primeiro setor do disco definido no setup
b) Encontrou setor de boot válido?
c) Sim. Lê o setor e carrega o código para o segmento endereço 7C00h na memória. (Valeu pela correção Muzgo :)
d) Não. Parte para o próximo dispositivo da lista de boot e vai para a) até encontrar.
e) Caso não encontre em nenhum dos dispositivos, exibe mensagem de erro padrão da BIOS.
A Lista de software que usei:
1. NASM - Para compilar o código
2. QEMU - Maquina virtual que uso para testar o setor de boot
3. 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