MC404
Programação estruturada em linguagem de montagem

Prof. Célio Guimarães
Atualizado em 20 Set 2004

Este documento cobre diversos assuntos relacionados à programação em linguagem de montagem:

1. Programação estruturada em linguagem de montagem

Programação estruturada é importante qualquer que seja a linguagem e talvez com maior razão em linguagens de montagem. A maioria das técnicas relacionadas usadas ao programar em alto nível se aplica também para linguagens de montagem:
  1. Todo programa não trivial deve ser estruturado em subrotinas ou funções.
  2. Subrotinas devem ter uma cabeçalho explicando seu objetivo, quais parâmetros de entrada e quais parâmetros de saída.
  3. Subrotinas podem ser colocadas após o programa principal e na ordem em que são invocadas mas isto é um gosto pessoal. Alguns programadores em C preferem colocá-las na ordem inversa com o programa principal (main()) no final.
  4. Em assembler comentários de linha são altamente recomendados, preferivelmente alinhados à direita de comandos: um comentário de linha não deve explicar o que uma instrução/diretiva faz (está no manual!), mas o seu papel no algoritmo sendo desenvolvido.
  5. Parâmetros do programa que podem mudar conforme o teste devem ser definidos via constantes no inicio do programa, de forma análoga à utilização da diretiva #define em C.
  6. Parâmetros de versão/configuração devem preferencialmente ser definidos através das macro_diretivas %ifdef, %if, %else e %endif.
  7. Contrôle de fluxo: este é o item menos trivial: em linguagens de alto nivel temos os comandos while,   if-then-else,   case,  break, etc; em assembler só temos saltos diretos e saltos condicionais. Por isso cuidados extras são necessários:
(*)Exercício: escreva um trecho de programa contendo apenas uma instrução lógica e saltos condicinais apropriados, para verificar se um inteiro com sinal em ax é positivo, negativo, par ou ímpar, exibindo no vídeo uma mensagem distinta para cada uma das quatro possibilidades. Desenhe os arcos onde há saltos e verifique se o número de cruzamentos de arcos é mínimo.

2. Utilização das funções de E/S do BIOS

Ao dar partida um PC executa um programa denominado BIOS contido na memória de programa não volátil (flash) do PC. Este programa oferece um conjunto de funções de E/S para o teclado, vídeo, disquete, disco rígido, etc, necessárias ao processo de partida (bootstrap) antes da carga do Sistema Operacional (feita a partir de um dos discos, podendo inclusive ser o DOS ou outro qualquer a partir de um disquete). Essas funções estão disponíveis a programas em assembler e são de nível mais baixo (isto é, mais perto do hardware), do que as funções do DOS: por exemplo, existem funções para saber se uma tecla foi levantada ou abaixada, para posicionar o cursor na tela, para definir as cores de foreground e background, tamanho do cursor, etc. Elas são usadas pelo programa inicial de boot que reside no 1º setor do disco de boot (e que por sua vez carrega o Sistema Operacional a partir de um ou mais arquivos no disco). Um resumo das funções do BIOS pode ser visto neste link. Vamos mencionar apenas as seguintes funções:

  • escrita de caracter com seu atributo (cor): int 10h, ah=09
    (esta função não avança o cursor, veja funções a seguir)
  • obter as coordenadas (*) da posição corrente do cursor: int 10h, ah=03
  • posicionar o cursor numa nova posição: int 10h, ah=02
  • escrita de uma cadeia de caracteres: int 10h, ah=13h
    (end da cadeia passado em bp, comprimento em cx e coordenadas no video em dl,dh (coluna e linha)).
  • definição e rolagem para cima (scroll up) de uma janela definida pelas suas coordenadas: int 10h, ah=06
    (permite limpar uma janela arbitrária do vídeo, ou preenchê-la com uma cor qualquer)
  • leitura de um ou mais setores de disquete ou de disco rígido: int 13h, ah=02 (*)As coordenadas de um caracter numa janela são medidas a partir da origem (0,0) localizada no canto superior esquerdo da janela, com o eixo x indo para a direita e o eixo y para baixo. Uma janela com 25 linhas x 80 colunas teria na diagonal saindo da origem os pontos extremos (0,0), (79, 24).

    Um programa exemplo bem documentado que ilustra a criação de uma janela e o seu preenchimento com duas mensagens usando funções do BIOS encontra-se neste link. Este programa exibe um recurso interessante só disponível em assembler e no venerável FORTRAN que é o de uma rotina com múltiplos pontos de entrada.
    A utilização das funções descritas de vídeo e teclado é também exemplificada neste programa biosfun.asm que lê 15 mensagens do teclado, ecoando no vídeo cada mensagem com uma cor diferente.

    3. Utilização do debug do DOS para ler escrever setores de um disquete

    O debug do DOS permite ler e escrever setores de um disquete ou o conteúdo de um arquivo de forma bastante simples:
    após invocar o debug (prompt -):

    1. leitura de um ou mais setores
      -L end-mem   drive_no   sector_no   nsectors  
      -L 0  0  0  1           -- lê a partir do endereço 0, 1º floppy, setor 0, 1 setor
      
    2. escrita de um ou mais setores: só muda a letra do comando, w:
      -w 0  0  0  1
      
    3. leitura de um arquivo. Exemplo: ler o arquivo boot.bin a partir do endereço de memória 0:
      -n boot.bin	   		   -- abre o arquivo boot.bin para leitura
      -L 0				   -- lê o arquivo boot.bin a partir da posição de memória 0
      
    Esses comandos serão usados na geração de um disquete de boot, veja a seguir.

    4. Programando o setor de boot de um disquete para o PC

    Quando um PC dá partida o BIOS carrega o 1º setor (512 bytes) do 1º disco habilitado para carregar o Sistema Operacional (e que pode ser o disquete, e que suporemos que é o caso no que se segue). Este setor (localizado no cilindro 0, face 0, setor 1) é lido a partir da posição de memória 7C00h e contém o programa inicial de boot do sistema. O BIOS valida o programa de boot verificando se os dois últimos bytes do setor carregado possuem os valores 55h e AAh. Nesse caso ele salta para a posição de carga, 7C00h, caso contrário emite uma mensagem de erro, tipicamente, "Invalid system disk" e aborta o processo. O programa do setor de boot contém uma área de dados com 60 bytes localizada a partir do 3º byte do setor, com informações sobre o sistema de arquivos do disquete (usualmente FAT12). Os dois 1ºs bytes contêm uma instrução de salto para além dessa área de dados onde começa propriamente o código de boot que consiste em ler do disquete os arquivos IO.SYS e MSDOS.SYS que são o núcleo do sistema operacional DOS.

    O conteúdo do setor de boot para o nosso teste é irrelevante, apenas temos que garantir a existência dos bytes 55h e AAh nas duas últimas posições. Por exemplo, a área de dados não é necessária, mas se ela não existir o disquete não mais será reconhecido como um disquete DOS pelos sistemas operacionais da MS. Vamos então colocar um código arbitrário no setor de boot, que é justamente o programa biosfun.asm citado anteriormente e que exercita o vídeo e teclado usando funções do BIOS. A fim de evitar que a máquina fique travada após a emissão da última mensagem, (o usuário deverá então digitar <enter>) o programa desvia para a posição de memória FFFF0h do BIOS, que é onde processadores da família Intel X86 buscam a 1ª instrução após receber o sinal de reset (emitido quando ligamos a máquina). Se então retirarmos o disquete do drive o PC dará partida normalmente a partir do disco rígido.

    Em resumo: qualquer programa em linguagem de máquina (binário) colocado no 1º setor de um disquete cujos últimos dois bytes contêm os valores 55h e AAh será executado pelo BIOS na partida de um PC habilitado a dar "boot" pelo disquete. Neste instante o PC estará sob seu contrôle! Para gerar esse programa é suficiente:

    1. Codificar o programa, digamos, boot.asm, no NASM com a diretiva org 07C00h;
    2. Gerar um arquivo binário com o comando:
      nasmw boot.asm -o boot.bin
      
    3. Gravar boot.bin no disquete via debug:
      debug
      -n boot.bin
      -L 0
      -w 0 0 0 1
      (você pode verificar nesse ponto se a gravação foi correta com os comandos
      -f 0 200 ff, -L 0 0 0 1 e -d0 -d -d -d , que exibirão os 4 x 128 bytes gravados)
      -q
      
    Agora dê partida no PC com o disquete recém escrito.

    5. Descrição do programa biosfun.asm

    A 1ª diretiva permite definir a variável BOOT. Nesse caso será gerada a versão para gravação no setor de boot de um disquete, senão será gerado um arquivo .COM usual. No 1º caso gera-se o código com o salto inicial, a tabela de dados de um disquete DOS com FAT12 e a inicialização dos registradores de segmento DS, SS e ES com zero. A partir do rótulo init o código é idêntico para as duas versões.
    Em seguida a rotina initvideo inicializa uma janela do video com 25 linhas e 80 colunas (numeradas a partir de 0) e exibe a mensagem inicial que solicita a digitação pelo teclado de 15 mensagens. O laço principal do programa (começando no rótulo l1) exibe sucessivamente a mensagem "Digite mais uma mensagem" (chamando a rotina printmsg), lê uma mensagem pelo teclado até ser digitado CR (rotina readline, que também ecoa a mensagem no vídeo), e muda a cor de foreground do video. O laço termina quando atinge a cor 0 (preta, não exibida pois é igual à de background) e a mensagem final é exibida. No caso da versão de boot é lido novamente do teclado um ou mais caracteres terminados por CR, a fim de dar tempo de exibir no video a ultima mensagem, antes de reiniciar o PC através de um salto longo para a posição(CS:IP) = ffff:0 (ou equivalentemente, f000:fff0 ) que dá partida ao PC imediatamente após o reset. As rotinas seguintes dão suporte às rotinas printmsg e readline usando funções do BIOS e são auto-explicativas com auxílio do resumo citado de funções do bios. No caso do setor de boot, as diretivas após a definição das mensagens verificam se o código + dados não excedem 512 bytes, zeram os bytes seguintes (diretiva times) e inicializam os dois últimos bytes com 55h e AAh.