Programação em Assembly
Programação do computador
Para escrevermos um programa começamos com a nossa linguagem natural para descrever o nosso objetivo seguido da sua tradução para linguagem de programação. Estes dois passos são obtidos pelos programadores. Assim que temos o nosso programa escrito, o compilador traduz para linguagem assembly e, por último, o assemblador traduz tudo para código máquina.
Mas como funciona a linguagem assembly? Começamos por ter uma instrução por linha, cada instrução tem uma formatação rígida, alguns comentários escritos logo durante o processo de transcrição para linguagem assembly e instruções que refletem diretamente os recursos do processador.
Se tivermos por exemplo
conta: ADD R1, R2 ; soma ao saldo
Sabemos que conta representa a nossa etiqueta, ADD a mnemónica, R1 e R2 são os operandos, e soma ao saldo é o nosso comentário em relação ao que é que a nossa linha de comando faz.
Comentários
Cada comentário no nosso programa deve ser iniciado pelo carácter ";" eo comentário que se segue até ao final da linha. Praticamente todas as linhas de assembly devem ter um comentário a indicar o que está a ser feito, se não, o código torna-se praticamente impossível de ser entendido, visto que é uma linguagem de baixo nível.
Como podemos ver no exemplo, sem comentários a explicar-nos o que cada linha de código faz, não sabemos qual é o objetivo do programa nem o que está a ser feito.
Registos do processador
Os registos do processador representam os recursos mais importantes que uma instrução pode manipular; são de memória interna, de acesso muito mais rápido que a memória externa e com instruções que manipulam alguns registos diretamente.
Se estivermos a avaliar em PEPE, temos dois tipos de registos distintos, cada qual com 16 bits (4 dígitos hexadecimais):
- PC (Program Counter);
- 16 registos (R0 a R15), alguns "especiais"
Bits de estado (flags)
Dentro do Registo de estado (RE), podemos ter flags que nos fornecem indicações sobre o resultado da operação anterior, contudo é importante notar que nem todas as instruções os alteram. Estas flags podem influenciar o resultado da operação seguinte.
Podemos ter 4 tipos de flags diferentes:
- (Z) Zero: fica a 1 se o resultado de uma operação for zero;
- (C) Carry, transporte: fica a 1 se o resultado de uma operação tiver transporte;
- (V) Overflow, excesso: fica a 1 se o resultado de uma operação não couber na palavra do processador;
- (N) Negativo: fica a 1 se o resultado de uma operação for negativo
Classe de instruções
Podemos classificar cada instrução dentro de 5 classes diferentes: instruções aritméticas, instruções lógicas, instruções de deslocamento, instruções de transferência de dados e instruções de controlo de fluxo.
As instruções aritméticas são todas as instruções que lidam com números em complemento para 2, nomeadamente ADD, SUB, CMP, MUL e DIV.
As instruções lógicas representam as instruções que lidam com sequências de bits, como é o caso do AND, OR e SET.
As instruções de deslocamento lidam com o deslocamento dos bits de um registo, ou seja, instruções como SHR e ROL.
As instruções de transferência de dados são as instruções que nos permitem transferir dados entre dois registos ou entre um registo e a memória, como fazemos com o MOV e SWAP.
Por último, as instruções de controlo de fluxo são as que controlam a sequência de execução de instruções, podendo tomar decisões, como JMP, JZ, JNZ, CALL e RET. Nestas operações, os valores dos registos são considerados endereços sem sinal, ou seja, qualquer valor de 0000H a FFFFH.
Para além disso, podemos referir-mo-nos a instruções com as lógicas e de deslocamento como instruções de bit. Nestas instruções, os registos são apenas um conjunto de bits individuais.
Ao representarmos números em assembly temos que ter em atenção que nas instruções aritméticas, os valores estão em complemento para 2, ou seja representam qualquer valor entre 8000H e 7FFFH.
Instruções aritméticas, lógicas e de deslocamento
Exemplo de instruções que implementam operações aritméticas:
Podemos também ter operações de deslocamento, como já foi referido acima. Estas instruções apenas multiplicam (SHL) ou dividem (SHR) por , seja n o número dado na instrução. Estas operações efetuam-se da seguinte forma:
SHL a, n
SHR a, n
Também podemos usar as instruções ROL ou ROR quando fazemos uma multiplicação ou divisão mas o primeiro algarismo (bit mais significativo) passa a ser o último (ROL) na multiplicação, ou o último algarismo (bit menos significativo) passa a ser o primeiro (ROR) na divisão.
ROL a, n
ROR a, n
Instruções de transferência de dados
Transferências de dados (16 bits)
Acesso à memória em 8 bits
Swap
Instruções de controlo de fluxo
Quando estamos a usar uma linguagem de alto nível, a execução das nossas instruções é feita de forma sequencial, exceto se temos uma decisão (quando temos um if ou switch) ou se temos uma interação, quer seja incondicional, como é o caso do for, ou condicional, while. Este controlo de fluxo é obtido através de bits de estado que indicam o resultado da instrução anterior, ou de instruções específicas, quer sejam de saltos (in)condicionais, de chamada de rotina ou até mesmo de retorno de rotina.
Mas o que são instruções de salto? Instruções de salto, tal como o nome indica, são instruções que nos deixam "saltar" de uma posição para outra, ou seja, na prática o que estamos a fazer é alterar o PC em vez de o deixar incrementar normalmente. Estes saltos podem ser:
- Incondicionais
- Condicionais
- Absolutos
- Relativos
Diretivas
Quando nos referimos a aspetos importantes de assembly é necessário falar sobre as diretivas que são uma espécie de pseudo-instruções pois servem apenas para o assembler e não para o microprocessador. Por outras palavras, as diretivas não geram código executável.
EQU
A primeira diretiva a ser discutida é EQU. Esta diretiva, que não gera código, serve somente para definir o valor de constantes simbólicas. Ou seja, esta diretiva permite-nos atribuir o valor que nos pretendemos às constantes que vamos usando ao longo do nosso programa.
NOME EQU constante-literal
Exemplo:
DUZIA EQU 12
PLACE
A diretiva PLACE ajuda o assembler a indicar o endereço a partir do qual as instruções ou variáveis seguintes devem ficar localizadas. Assim, até aparecer um PLACE, considera-se que há um PLACE 0 implícito, desde o inicio do programa.
PLACE endereço
tip
Após o reset, o PEPE inicializa o PC com o valor 0000H, por isso, tem de haver um PLACE 0000H algures no nosso programa, não sendo obrigatório estar logo no início do ficheiro.
WORD
Ao usarmos WORD estamos a reservar um espaço, define, para uma variável de 16 bits. A mesma diretiva pode definir várias variáveis de uma word consecutivas, contudo, cada variável gasta 2 bytes.
ETIQUETA: WORD constante{,constante}
Exemplo:
VAR1: WORD 1
Ficamos com uma variável inicializada a 1 que fica localizada no endereço atribuído pelo assemblador a VAR1. WORD é diferente de EQU.
TABLE
Agora que já vimos o que é que a diretiva WORD faz, temos que saber o que é que a diretiva TABLE faz.
Ao usarmos TABLE estamos a definir, reservar espaço, para uma tabela com várias variáveis de 16 bits. É importante manter em conta que apenas reserva espaço, não a inicializa.
ETIQUETA: TABLE constante
Exemplo:
T1: TABLE 1OH
Ficamos com um espaço para 16 words reservado, ou seja temos 32 bytes reservados. A primeira word fica reservada no endereço atribuído a T1, a segunda em T1+2, etc.
Esta diretiva é boa para reservar uma área que depois se pode ir escrevendo ao longo do programa, porém, para definir tabelas de constantes é preferível definir os vários elementos da tabela com a diretiva WORD.
BYTE
A última diretiva é BYTE, que define uma variável de 8 bits, ou seja, um byte. Para valores negativos, deve-se usar variáveis WORD e não BYTE pois estes precisam sempre dos bits todos num processador.
A mesma diretiva permite definir várias variáveis de um byte consecutivas.
ETIQUETA: BYTE constante{,constante}
Exemplo:
S1: BYTE 'a', "ola", 12H
Modos de endereçamento
warning
Para ver exemplos de programas em Assembly, é recomendada a leitura dos slides das teóricas.