Oficina

8. O loader Numaboa

Sab

26

Mai

2007


16:20

  • Imprimir
(6 votos, média 5.00 de 5) 


Maluco beleza MÓDULO 8 do SO Numaboa

Um loader com apenas um loop infinito foi um bom começo mas, para dizer a verdade, quando o testei pela primeira vez fiquei desconfiada. Será que o setor de boot estava realmente funcionando como pretendido? Numa situação como esta, um dos testes que podem ser efetuados é adicionar algum código que dê saída de um caracter ou de uma string para a tela: se aparecer na tela - funcionou, se não aparecer na tela - deu caca.

Entra aí uma novidade: é preciso saber como dar saída para a tela e a única opção que temos é usar uma das rotinas da BIOS fazendo um chamado de interrupção.

As interrupções da BIOS

As interrupções da BIOS são uma mão na roda. Oferecem uma série de rotinas básicas (daí o nome de Basic Input/Output Services) que podem ser acessadas via interrupções de hardware. Da extensa lista de interrupções disponíveis, a que nos interessa no momento é a INT 10.

A INT 10 é conhecida como Serviços da BIOS para Vídeo (Video BIOS Services). A INT 10 aciona uma porção de rotinas diferentes (mais de 25) dependendo do valor colocado na porção alta do registrador EAX (nos sistemas de 32 bits - AX nos sistemas de 16 bits). INT 10 com AH=00, por exemplo, configura o modo de vídeo; INT 10 com AH=06, rola a página para cima; INT 10 com AH=0C, escreve um pixel gráfico nas coordenadas, etc.

Para uma lista completa das interrupções da BIOS e seus respectivos serviços ou rotinas procure literatura técnica especializada (veja nas considerações finais deste texto). Uma descrição detalhada não faz parte deste tutorial.

A INT 10 com AH=0E

A rotina que nos interessa é a INT 10 com AH=0E, que escreve texto em modo teletipo (modo teletipo é quando o cursor avança uma posição depois de escrever um caracter). Os valores, todos em notação hexadecimal, que podem ser enviados para a BIOS através dos registradores são:

	AH = 0E

	AL = ASCII do caracter que deve ser escrito na porção baixa do
		registrador EAX ou AX (Low = baixa)

	BH = número da página de vídeo (modos texto) na porção alta do
		registrador EBX ou BX (High = alta)

	BL = cor de frente e de fundo dos pixels (modos gráficos) na
		porção baixa do registrador EBX ou BX

	- não há valor de retorno

	- o cursor avança depois da escrita

	- caracteres BEL (07), BS (08), LF (0A), and CR (0D) são
		tratados como códigos de controle

Uma letra na tela

Vamos completar o código do boot loader de loop infinito, escrito no módulo anterior, para colocar uma letra na tela. A letra escolhida será a letra A, cujo ASCII em notação decimal é 65.

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa mov ah,0x0E ; Indica a rotina de teletipo da BIOS mov al,65 ; O valor ASCII do caracter a ser escrito mov bh,0x00 ; Número da página de vídeo mov bl,0x07 ; Atributos do texto (Controla a cor de frente ; e a cor de fundo). 07 é texto branco em fundo ; preto int 0x10 ; Chamar a interrupção de vídeo da BIOS pendura: jmp pendura ; O loop infinito ; Para terminar times 510-($-$$) db 0 ; Preenche o resto do setor com zeros db 0x55,0xAA ; Põe a assinatura do boot loader no final

Entre o marcador do início do programa e o loop infinito estão algumas instruções MOV para MOVer valores para determinadas porções dos registradores AX e BX. Não se esqueça de que estamos trabalhando com 16 bits. Neste caso, os registradores aceitam apenas 16 bits e recebem o nome de AX, BX, CX, etc. Caso estivéssemos trabalhando com 32 bits, os registradores aceitariam 32 bits e receberiam o nome de EAX, EBX, ECX, etc. Na porção baixa de BX (bl) inserimos o valor 07, que determina texto branco em fundo preto. Você pode tentar outros valores para obter outras combinações de cores.

Conferindo o loader Numaboa

Escreva o código do exemplo e grave-o como loader2.asm (ou outro nome qualquer). Compile-o com o NASM usando nasm.exe -f bin loader2.asm -o loader2.bin ou com o NASM para Windows usando nasmw.exe -f bin loader2.asm -o loader2.bin. Abra o loader2.bin num editor hexadecimal e verifique:

0000 0000    B4 0E B0 41 B7 00 B3 07    CD 10 EB FE 00 00 00 00
0000 0010    00 00 00 00 ...
...
0000 01F0                        ...    00 00 00 00 00 00 55 AA

Se o código compilado estiver ok, transfira-o para o setor de boot de um disquete (você pode formatar o disquete usado anteriormente para reutilizá-lo) e teste-o (como explicado no módulo anterior).


Identificando o sistema operacional Numaboa

Escrever apenas uma letra é um negócio marreta. No mínimo, precisamos limpar a tela e identificar nosso sistema operacional. Para isto, bastam alguns ajustes.

Limpando a tela

Como já sabemos que a INT 10 possui os serviços de vídeo, precisamos apenas identificar a rotina para limpar a tela: pode ser a 06 (Scroll window up) que rola a tela para cima ou a 07 (Scroll window down) que rola a tela para baixo. Fica a seu critério qual das duas usar pois o efeito será o mesmo:

	AH = 06 - ROLAGEM PARA CIMA

	AL = número de linhas de rolagem. As linhas anteriores serão
		limpas. Se 0 ou maior que o tamanho da tela, a tela toda
		será limpa.
	BH = atributo usado para limpar a(s) linha(s).
	CH = linha do canto superior esquerdo da janela de rolagem.
	CL = coluna do canto superior esquerdo da janela de rolagem.
	DH = linha do canto inferior direito da janela de rolagem.
	DL = coluna do canto inferior direito da janela de rolagem.
	- não tem valor de retorno

	AH = 07 - ROLAGEM PARA BAIXO
	- usa os mesmos registradores com os mesmos valores.

Eu optei pelo serviço 07 da INT 10.

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa ; Limpar a tela mov ah,0x07 ; Indica a rotina de rolagem de tela da BIOS mov al,0x00 ; Rolar TODAS as linhas mov bh,0x07 ; Texto branco em fundo preto mov ch,0x00 ; Linha do canto superior esquerdo mov cl,0x00 ; Coluna do canto superior esquerdo mov dh,800 ; Linha do canto inferior direito (maior que a tela) mov dl,1200 ; Coluna do canto inferior direito (maior que a tela) int 0x10 ; Chamar a INT 10 mov ah,0x0E ; Indica a rotina de teletipo da BIOS ...

Para dar as coordenadas do canto superior esquerdo da tela também poderíamos usar mov cx, 0x0000 (observe o hexadecimal 0x0000 com quatro zeros! os quatro zeros são necessários para que os 16 bits sejam preenchidos). Se indicarmos um número de linha e coluna maior que a tela para o canto inferior direito, temos a certeza de que toda a tela será limpa.

Posicionando o cursor

Limpar a tela não significa que o cursor seja deslocado na posição 0,0 - o início da tela. É preciso usar mais um serviço da INT 10 que seta a posição do cursor: INT 10 serviço 02.

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa ; Limpar a tela mov ah,0x07 ; Indica a rotina de rolagem de tela da BIOS mov al,0x00 ; Rolar TODAS as linhas mov bh,0x07 ; Texto branco em fundo preto mov ch,0x00 ; Linha do canto superior esquerdo mov cl,0x00 ; Coluna do canto superior esquerdo mov dh,800 ; Linha do canto inferior direito (maior que a tela) mov dl,1200 ; Coluna do canto inferior direito (maior que a tela) int 0x10 ; Chamar a INT 10 ; Posicionar o cursor mov ah,0x02 ; Indica a rotina de posicionamento do cursor mov bh,0x00 ; Número da página de vídeo mov dx,0x0000 ; Linha e coluna da nova posição do cursor (0,0) int 0x10 ; Chamar a INT 10 mov ah,0x0E ; Indica a rotina de teletipo da BIOS ...

Declarando uma string

Uma sequência de caracteres, ou string, pode ser armazenada numa variável. Sabemos que variáveis nada mais são do que endereços de memória onde ficam armazenados seus valores. Se quisermos declarar a variável StringSO, que irá conter o valor da nossa string "Sistema Operacional NumaBoa", será preciso declará-la e atribuir-lhe um valor. Além dos caracteres, queremos que o cursor seja posicionado na próxima linha. Para isto, adicionamos os caracteres 13 (return=retorno de carro) e 10 (line feed = mudança de linha). Como se trata de uma string, o caracter terminador precisa ser zero, ou seja:

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa ; Limpar a tela mov ah,0x07 ; Indica a rotina de rolagem de tela da BIOS mov al,0x00 ; Rolar TODAS as linhas mov bh,0x07 ; Texto branco em fundo preto mov ch,0x00 ; Linha do canto superior esquerdo mov cl,0x00 ; Coluna do canto superior esquerdo mov dh,800 ; Linha do canto inferior direito (maior que a tela) mov dl,1200 ; Coluna do canto inferior direito (maior que a tela) int 0x10 ; Chamar a INT 10 ; Posicionar o cursor mov ah,0x02 ; Indica a rotina de posicionamento do cursor mov bh,0x00 ; Número da página de vídeo mov dx,0x0000 ; Linha e coluna da nova posição do cursor (0,0) int 0x10 ; Chamar a INT 10 mov ah,0x0E ; Indica a rotina de teletipo da BIOS ... ; Dados StringSO db 'Sistema Operacional NumaBoa',13,10,0 ; Para terminar ...

Indicando a posição da string na memória

Só para relembrar: sistemas de 16 bits são muito limitados para indicar endereços de memória. Com 16 bits, o maior número que podemos obter é 65535 (que é o mesmo que 1111 1111 1111 1111 em binário). O valor 65535 corresponde a 64 Kb, o que seria uma quantidade de memória insignificante e limitaria muito os aplicativos e sistemas operacionais. Foi daí que o pessoal resolveu "lotear" a memória em segmentos e, logicamente, em segmentos de 64 Kb. O endereçamento passou a ser composto por duas referências: o segmento e o deslocamento dentro do segmento. Assim, o endereço 0000:0005 se refere ao deslocamento 5 dentro do segmento 0.

A área de memória, onde são armazenados os dados (nossas variáveis), é chamado de segmento de dados. Um dos registradores da CPU fica encarregado de indicar a posição deste segmento: é o DS (Data Segment). O endereçamento é feito com DS:Deslocamento, como explicado acima. O registrador que guarda o deslocamento é o SI (Segment Index ou Índice de Segmento).

Como nosso programa é muito pequeno e deixamos o NASM alocar espaço de memória para o programa e para os dados, acaba ficando tudo "empacotado" no mesmo segmento, o segmento 0 (zero). Neste caso, fica fácil indicar a posição da nossa string: DS = 0 e SI = StringSO. Quando passamos o nome da variável para SI, na verdade passamos o deslocamento correspondente à StringSO. Completando nosso código teremos:

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa ; Limpar a tela mov ah,0x07 ; Indica a rotina de rolagem de tela da BIOS mov al,0x00 ; Rolar TODAS as linhas mov bh,0x07 ; Texto branco em fundo preto mov ch,0x00 ; Linha do canto superior esquerdo mov cl,0x00 ; Coluna do canto superior esquerdo mov dh,800 ; Linha do canto inferior direito (maior que a tela) mov dl,1200 ; Coluna do canto inferior direito (maior que a tela) int 0x10 ; Chamar a INT 10 ; Posicionar o cursor mov ah,0x02 ; Indica a rotina de posicionamento do cursor mov bh,0x00 ; Número da página de vídeo mov dx,0x0000 ; Linha e coluna da nova posição do cursor (0,0) int 0x10 ; Chamar a INT 10 mov ax,0x0000 ; Como não é possível carregar diretamente DS mov ds,ax ; passamos o valor de ax para ds mov si,StringSO ; Ponteiro para o deslocamento da string call PoeString ; Chamar a rotina de impressão mov ah,0x0E ; Indica a rotina de teletipo da BIOS ... ; Dados StringSO db 'Sistema Operacional NumaBoa',13,10,0 ; Para terminar ...

Novamente tome cuidado ao mover um valor de 16 bits para ax (mov ax,0x0000). Para não bagunçar nosso código, criamos a sub-rotina PoeString. Esta sub-rotina lerá uma a um os caracteres de StringSO, jogando-os na tela, até encontrar o caracter 0 (zero), marcador do final da string.


Escrevendo a string na tela

A sub-rotina ou procedimento PoeString vai precisar de um loop que, a cada passada, imprima o caracter encontrado na tela. Este loop termina quando o caracter 0 (zero) for encontrado. Nossa rotina de teletipo ficará dentro desta função:

[BITS 16] ; Código de 16 bits [ORG 0x7C00] ; Origem do código em 7C00 principal: ; Marcador do início do programa ; Limpar a tela mov ah,0x07 ; Indica a rotina de rolagem de tela da BIOS mov al,0x00 ; Rolar TODAS as linhas mov bh,0x07 ; Texto branco em fundo preto mov ch,0x00 ; Linha do canto superior esquerdo mov cl,0x00 ; Coluna do canto superior esquerdo mov dh,800 ; Linha do canto inferior direito (maior que a tela) mov dl,1200 ; Coluna do canto inferior direito (maior que a tela) int 0x10 ; Chamar a INT 10 ; Posicionar o cursor mov ah,0x02 ; Indica a rotina de posicionamento do cursor mov bh,0x00 ; Número da página de vídeo mov dx,0x0000 ; Linha e coluna da nova posição do cursor (0,0) int 0x10 ; Chamar a INT 10 mov ax,0x0000 ; Como não é possível carregar diretamente DS mov ds,ax ; passamos o valor de ax para ds mov si,StringSO ; Ponteiro para o deslocamento da string call PoeString ; Chamar a rotina de impressão pendura: jmp pendura ; Loop infinito ; Procedimentos PoeString: ; Marcador do início da sub-rotina mov ah,0x0E ; Indica a rotina de teletipo da BIOS mov bh,0x00 ; Número da página de vídeo mov bl,0x07 ; Texto branco em fundo preto .proxCar ; Marcador interno para início do loop lodsb ; Copia o byte em DS:SI para AL e incrementa SI or al,al ; Verifica se al = 0 jz .volta ; Se al=0, string terminou e salta para .volta int 0x10 ; Se não, chama INT 10 para por caracter na tela jmp .proxCar ; Vai para o início do loop .volta ret ; Retorna à rotina principal ; Dados StringSO db 'Sistema Operacional NumaBoa',13,10,0 ; Para terminar times 510-($-$$) db 0 ; Preenche o resto do setor com zeros db 0x55,0xAA ; Põe a assinatura do boot loader no final

O opcode LODSB carrega um byte existente em [DS:SI] em AL. Depois disto incrementa ou decrementa SI (dependendo da flag de direção: incrementa se a flag for 0, decrementa se for 1). Este código operacional é uma mão na roda para transferir sequências de bytes.

Bem, o código acima realiza o que projetamos. Salve-o como loader2a.asm e compile-o para loader2a.bin com o NASM.

Verificando o boot loader Numaboa

Só para matar a curiosidade, vamos dar uma olhada no código compilado. Use o editor hexadecimal da sua preferência e verifique os 512 bytes do arquivo loader2a.bin, que deve mostrar o seguinte:

0000    B4 07 B0 20 B7 07 B9 00    00 B6 20 B2 B0 CD 10 B4  ´.° ·.¹..¶ ²°Í.´
0010    02 B7 00 BA 00 00 CD 10    B8 00 00 8E D8 BE 35 7C  .·.º..Í.¸..ŽØ¾5|
0020    E8 02 00 EB FE B4 0E B7    00 B3 07 AC 08 C0 74 04  è..ëþ´.·³.¬.Àt.
0030    CD 10 EB F7 C3 53 69 73    74 65 6D 61 20 4F 70 65  Í.ë÷ÃSistema Ope
0040    72 63 61 69 6F 6E 61 6C    20 3E 75 6D 61 42 6F 61  racional NumaBoa
0050    0D 0A 00 00 00 00 00 00    ...
...
01F0                        ...    00 00 00 00 00 00 55 AA

Testando o bootloader Numaboa

Prepare o disquete de teste da mesma forma como foi explicado no módulo anterior. Agora, é só cruzar os dedos e torcer para que tudo dê certo (pra mim deu smile ).

Dê o boot com o disquete no drive e deixe o Sistema Operacional Numaboa assumir o comando e ficar "travado" no loop infinito...


Considerações finais

O Sistema Operacional NumaBoa é composto apenas por um boot loader de 16 bits em modo real. Este trabalho todo para ficar transferindo o boot loader na unha usando o debug.exe e testando o loader com reinício de máquina tem sua razão de ser: se, futuramente, quisermos acessar a máquina em modo real, está meio caminho andado.

Quero destacar o site do engenheiro Roger Morgan, que disponibiliza um verdadeiro "mapa da mina" de interrupções e outras informações preciosas sobre hardware. Se tiver interesse, dê uma olhada em Interrupt Services DOS, BIOS, EMS and Mouse. Além disto, não posso deixar de citar o livro Linguagem Assembly para IBM PC, de Peter Norton, pela Editora Campus em 1988 - livrinho antigo para 16 bits, mas que nunca sai de moda wink

william hill отзывыгипермаркет посудылобановский александр фотоотзывы topodinпоследние новости мира на этот час отзыв mmgp mfxbroker