Prévia do material em texto
Unidade IV Acessando Unidades Funcionais Básicas em Assembly Linguagem de Montagem Diretor Executivo DAVID LIRA STEPHEN BARROS Gerente Editorial ALESSANDRA VANESSA FERREIRA DOS SANTOS Projeto Gráfico TIAGO DA ROCHA Autoria GISELLE AZEVEDO PINTO AUTORIA Giselle Azevedo Pinto Olá. Sou formada em Sistemas de Informação e pós-graduada em Cibersegurança e em Produção e Sistemas, mestra em Engenharia de Produção, com experiência técnico-profissional de mais de 20 anos na área de Tecnologia da Informação. Sou apaixonada pelo que faço e adoro transmitir minha experiência de vida àqueles que estão iniciando em suas profissões. Por isso, fui convidada pela Editora Telesapiens a integrar seu elenco de autores independentes. Estou muito feliz em poder ajudar você nesta fase de muito estudo e trabalho. Conte comigo! ICONOGRÁFICOS Olá. Esses ícones irão aparecer em sua trilha de aprendizagem toda vez que: OBJETIVO: para o início do desenvolvimento de uma nova competência; DEFINIÇÃO: houver necessidade de apresentar um novo conceito; NOTA: quando necessárias observações ou complementações para o seu conhecimento; IMPORTANTE: as observações escritas tiveram que ser priorizadas para você; EXPLICANDO MELHOR: algo precisa ser melhor explicado ou detalhado; VOCÊ SABIA? curiosidades e indagações lúdicas sobre o tema em estudo, se forem necessárias; SAIBA MAIS: textos, referências bibliográficas e links para aprofundamento do seu conhecimento; REFLITA: se houver a necessidade de chamar a atenção sobre algo a ser refletido ou discutido; ACESSE: se for preciso acessar um ou mais sites para fazer download, assistir vídeos, ler textos, ouvir podcast; RESUMINDO: quando for preciso fazer um resumo acumulativo das últimas abordagens; ATIVIDADES: quando alguma atividade de autoaprendizagem for aplicada; TESTANDO: quando uma competência for concluída e questões forem explicadas; SUMÁRIO Manipulando registradores em linguagem Assembly ................ 10 Registradores Gerais ..................................................................................................................... 13 Registradores de ponteiro ........................................................................................................ 16 Acesso às portas lógicas em Assembly ............................................. 21 O que é uma porta? .......................................................................................................................25 Acessando uma porta em Assembly ................................................................................27 Utilização da memória em Assembly .................................................36 Organização da memória e segmentos .........................................................................37 Endereçamento ............................................................................................................................... 39 Aplicação de macro em linguagem Assembly ...............................48 Definindo uma macro .................................................................................................................. 49 Sintaxe de uma macro .................................................................................................................52 Biblioteca de macros ....................................................................................................................55 7 UNIDADE 04 Linguagem de Montagem 8 INTRODUÇÃO Você sabia que a área de linguagem de montagem é uma das mais demandadas na indústria e será a responsável pela geração de muitos empregos nos próximos 10 anos? Isso mesmo. A linguagem de montagem está presente em todas as máquinas de uma empresa, indústria ou fábrica, independentemente do tipo de fabricante, pois se trata de uma linguagem universal para o ambiente tecnológico. Nesta unidade, iremos estudar sobre: o funcionamento dos registradores em programas em Assembly; a aplicação de comandos para acesso a portas lógicas utilizando a linguagem de montagem; a manipulação de dados em memória por meio da linguagem de montagem Assembly, identificando o propósito dos recursos; e, para finalizar, implementaremos uma macro em linguagem de montagem Assembly. Entendeu? Ao longo desta unidade letiva, você vai mergulhar neste universo! Linguagem de Montagem 9 OBJETIVOS Olá. Seja muito bem-vindo à Unidade 4. Nosso objetivo é auxiliar você no desenvolvimento das seguintes competências profissionais até o término desta etapa de estudos: 1. Entender o funcionamento dos registradores em programas em Assembly. 2. Aplicar os comandos de acesso a portas lógicas utilizando a linguagem de montagem. 3. Manipular dados em memória por meio da linguagem de montagem Assembly e identificar o propósito dos recursos. 4. Implementar uma macro em linguagem de montagem Assembly. Linguagem de Montagem 10 Manipulando registradores em linguagem Assembly OBJETIVO: Ao término deste capítulo, você será capaz de entender o funcionamento dos registradores em programas em Assembly. E então? Motivado para desenvolver essa competência? Então, vamos lá. Avante! Quando usamos computadores, não podemos deixar de falar no componente que é responsável pelo processamento dos dados. Conhecido como CPU ou unidade central de processamento, é composto por unidade de controle e também pela unidade de lógica e aritmética. A função da CPU é fazer a leitura e escrita de dados nas células de memória, regular o tráfego de dados existente nas células de memória e registradores especiais. Além disso, também tem a função de decodificar e executar instruções relacionadas a um programa. Figura 1 – CPU Fonte: Pixabay O processador possui células de memória que são usadas frequentemente, sendo assim, são partes da CPU. Essas células são chamadas de registradores. Um processador do tipo desktop, por exemplo, possui uma média de 14 registradores. Linguagem de Montagem 11 A seguir temos a listagem dos 14 registradores: • AX, que é um registrador acumulador. • BX, que é um registrador base. • CX, que é um registrador contador. • DX, que é um registrador de dados. • DS, que é um registrador de segmento de dados. • ES, que é um registrador de segmento extra. • SS, que é um registrador de segmento de pilha. • CS, que é um registrador de segmento de código. • BP, que é um registrador apontador da base. • SI, que é um registrador de índice-fonte. • DI, que é um registrador de índice destino. • SP, que é um registrador apontador de pilha. • IP, que é um registrador apontador da próxima instrução. • F, que é um registrador de flag. Com o avanço da tecnologia os PCs também têm sofrido evolução. Veremos que podemos manipular registradores de 16 ou 32 bits. A CPU pode realizar operações relacionadas ao cálculo simbólico e ao cálculo numérico. Essas operações podem ser operações elementares, como adição, subtração com dois números, multiplicação e divisão de dados numéricos inteiros, manuseando bits de registradores e comparando as informações e os conteúdos referentes a dois registradores. Em linguagem de montagem Assembly, utilizamos dois tipos de registradores: • Registradores gerais. • Registradores de ponteiros e índices. Linguagem de Montagem 12 IMPORTANTE: Os registradores têm o objetivo de armazenar valores. Assim como em programação de alto nível, usamos variáveis (x e y, por exemplo), em linguagem de montagem, utilizamos registradores. Os processadores possuem em seus chips uma área física reservada para armazenamento de dados, a qual chamamos de registradores. Esse nome é devido ao fato deles registrarem ou salvarem informação por tempo indeterminado. Os registradores podem manipular dados de diversas formas e tamanhos, pois podem possuir o mesmo tamanho da palavra de um processador, ou seja, se temosum processador de 32 bits, temos registradores que possuem o mesmo tamanho. Na tabela que se segue, podemos ver os registradores da Intel x86, que, mesmo sendo classificados como registradores gerais, há uma convenção em relação a eles que deve ser seguida. Quadro 1 – Os oito registradores de uso geral da Intel x86 Registrador Significado Uso sugerido EAX Accumulator Usado em operações aritméticas EBX Base Ponteiro para dados ECX Counter Contador em repetições EDX Data Usado em operações de E/S ESI Source Index Ponteiro para uma string de origem EDI Destination Index Ponteiro para uma string de destino ESP Stack Pointer Ponteiro para o topo da pilha EBP Base Pointer Ponteiro para a base do stack frame Fonte: Mente binária (s.d., online). Linguagem de Montagem 13 Registradores gerais IMPORTANTE: Sobre os registradores classificados como gerais, que são os registradores do primeiro grupo, temos os registradores AX, BX, CX e DX. O primeiro do grupo de registradores gerais é o registrador AX, que podemos classificar como registrador acumulador. Usamos o AX para coletar os resultados referentes às comutações. O registrador AX recebe muitas instruções e, mesmo que cada registrador consiga executar muitas tarefas, instruções como a de multiplicação, por exemplo, possuem destino implícito para AX. O segundo do grupo de registradores gerais é o registrador BX, que podemos classificar como registrador de base. Em muitas aplicações, podemos utilizar o registrador BX da mesma forma que o registrador AX. Porém, o registrador BX tem um poder que o registrador AX não possui. Podemos colocar um endereço de memória no registrador BX e executar um comando de instrução operando o que vem do endereço de memória que está em BX. Ou seja, o registrador BX possui um ponteiro para a memória, e o registrador AX não tem esse recurso. Para exemplificar, vamos comparar duas instruções. Quadro 2 – Instrução MOV INSTRUÇÃO OBJETIVO MOV AX,BX Copiar para AX o conteúdo de BX. BX contém o operando-fonte. MOV AX,(BX) Copiar para AX o conteúdo da palavra de memória cujo endereço está contido em BX. BX aponta para o operando-fonte. Fonte: Elaborado pela autora (2022). Linguagem de Montagem 14 REFLITA: No quadro precedente, podemos observar que o comando MOV possui uma fonte e um destino, e o destino está sendo indicado antes da fonte. Ou seja, no momento de escrever o comando MOV, colocamos em seguida o destino e depois a fonte. Por exemplo, em “MOV AX, BX”, podemos ler “mova para AX o que está em BX”. O registrador CX é o que podemos classificar como registrador contador. O registrador CX realiza diversas tarefas, mas sua função principal é contar os laços de repetição. O registrador CX é automaticamente decrementado na instrução LOOP. E, na linguagem de montagem, os laços costumam ser finalizados quando CX tem valor zero. O quarto registrador geral é o registrador DX, que podemos classificar como registrador de dados. O registrador DX pode ser usado com o registrador AX para instruções referentes ao comprimento de palavra dupla, ou seja, 32 bits. O registrador DX possui 16 bits de ordem alta e o registrador AX possui 16 bits de ordem baixa. IMPORTANTE: Os registradores gerais podem ser referenciados e usados como um registrador de 16 bits ou como 2 registradores de 8 bits. O processador 8088, por exemplo, possui oito registradores de 8 bits, que são usados em instruções do tipo byte e do tipo caracteres. IMPORTANTE: Somente os registradores gerais podem ser divididos em metades de 8 bits. Em algumas instruções, usamos o registrador por completo, ou seja, inteiro, como no caso do AX, mas temos outras instruções que usam apenas uma parte (metade) de um registrador, como AL ou AH. Linguagem de Montagem 15 Durante a programação em Assembly, quando são realizadas as operações de soma ou subtração, usamos registradores de 16 bits, porém, normalmente, são usados registradores e 8 bits. Ou seja, se não houver contas de soma, ou subtração ou multiplicação ou outra conta aritmética, será usado o registrador de 8 bits. Quando colocamos um valor em AX, por exemplo, alteramos as partes AH e AL de AX, dependendo do tamanho dos dados. Em geral, instruções que efetuam aritmética usam os registradores completos de 16 bits, mas as que lidam com caracteres em geral usam os de 8 bits. Todavia, é importante entender que AL e AH são apenas nomes para ambas as metades de AX. Quando AX é carregado com um novo valor, tanto AL quanto AH são mudados, respectivamente, para as metades inferior e superior do número de 16 bits colocado em AX. (TANENBAUM; AUSTIN, 2013, p. 546) Para exemplificar como o registrador AX, AH e AL funcionam e interagem, vamos considerar a instrução: MOV AX,258 Que irá carregar AX com o valor 258 (decimal). Em seguida, AH (de bytes) contém o valor 1, e AL contém o número 2. Figura 2 – Representação binária do decimal 258 256 (decimal) AH AL 2 (decimal) 00000001 00000010 Fonte: Elaborada pela autora (2022). Na notação apresentada, foram acionados os bits ativando o segundo bit em AL (que é referente ao número 2 em decimal) e ativando o primeiro bit em AH (que é referente ao número 256 em decimal). Os bits Linguagem de Montagem 16 ativados geram o número 258 em notação decimal, conforme a tabela binária. Na figura a seguir, podemos ver os registradores do modelo 8088 e observar os grupos de registradores. Figura 3 – Registradores do 8088 Fonte: Tanenbaum e Austin (2013, p. 545). Registradores de ponteiro Em relação ao segundo grupo de registradores, temos os registradores de ponteiros e índices. Em relação a esse grupo, o registrador mais importante do segundo grupo de registradores é o ponteiro de pilha, para o qual utilizamos a nomenclatura SP. O conceito de pilha é bastante usado e aplicado em diversas linguagens de programação. Alguns jogos, como a “Torre de Hanói”, foram desenvolvidos com base nesse conceito. A pilha armazena valores na memória para serem acessados em outro momento, como procedimentos que foram executados pela máquina ou alguma informação de controle interno do sistema. Linguagem de Montagem 17 Pilhas são importantes na maioria das linguagens de programação. A pilha é um segmento de memória que contém certas informações de contexto sobre o programa em execução. Em geral, quando um procedimento é chamado, parte da pilha é reservada para conter as variáveis locais do procedimento, o endereço para onde retornar quando o procedimento estiver concluído e outras informações de controle. A porção da pilha relativa a um procedimento é denominada seu quadro de pilha. (TANENBAUM; AUSTIN, 2013, p. 547) REFLITA: Quando temos um procedimento acionado que chama outro, o sistema coloca um quadro de pilha adicional (que é alocado) que fica geralmente logo abaixo do quadro atual. As pilhas geralmente crescem para baixo, ou seja, de endereços altos para endereços baixos. Contudo, o endereço numérico de valor mais baixo que é ocupado na pilha é denominado o topo da pilha. Podemos utilizar as pilhas para conter as variáveis locais e também para conter os resultados que são temporários. O processador 8088 possui uma instrução (PUSH) que posiciona uma palavra no formato de 16 bits no topo da pilha. A instrução POP pode retirar uma palavra de 16 bits do topo da pilha. Ou seja, o registrador SP pode apontar para o topo da pilha e pode ser modificado pelas instruções PUSH, CALL e POP, sendo incrementado por POP, decrementado por PUSH, e decrementado por CALL. O segundo registrador desse grupo é BP, que é o registrador de base. Ele possui um endereço na pilha e, se o registro SP aponta para o topo, o registrador BP tem o poder de apontar para qualquer local que esteja dentro da pilha. Um uso comum para o registrador BP é apontar para o início da pilha do procedimento atual de formaque fique fácil achar as variáveis do procedimento. Dessa maneira, o registrador BP aponta para a parte de baixo da pilha atual (o endereço numérico maior) e o registrador SP aponta para o topo da pilha (o endereço numérico menor). Ou seja, a pilha atual tem os limites sinalizados por BP e SP. Linguagem de Montagem 18 IMPORTANTE: Nesse grupo de registradores de ponteiros, temos também o registrador SI, que é identificado como o índice de origem, e o registrador DI, que é identificado como o índice de destino. Os registradores SI e DI são usados em combinação com o registrador “BP para endereçar dados na pilha, ou com BX para calcular os endereços de localização de dados de memória” (TANENBAUM; AUSTIN, 2013, p. 547). Vamos falar agora sobre os quatro registradores no grupo de registradores de segmentos. Vale recordar que, quando falamos em pilha, tanto os dados quanto os códigos de instrução ficam na memória principal, porém, em partes diferentes. IMPORTANTE: Os registradores de segmentos comandam as partes diferentes da memória, as quais classificamos como segmentos. Nos registradores de segmentos, temos: • O registrador CS, que é responsável pelos segmentos de código. • O registrador DS, que é responsável pelos segmentos de dados. • O registrador SS, que é responsável pelos segmentos de pilha. • O registrador ES, que é responsável pelos segmentos extras. Durante o procedimento de execução de um programa, os registradores possuem os valores ajustados durante todo o tempo ou a maior parte do tempo. Ou seja, os valores nos registradores DS, CS, ES e SS estão sempre sendo alterados. Linguagem de Montagem 19 IMPORTANTE: O segmento de dados (DS) e o segmento de pilha (SS), que são os registradores de segmentos, usam a mesma parte da memória. Para manipular os dados em programação Assembly em uma pilha, o primeiro dado a entrar é o último a sair, por exemplo: PUSH AX PUSH BX PUSH CX Em Assembly, quando queremos retornar os valores da pilha referente a cada registrador, executamos o seguinte programa: POP CX POP BX POP AX Linguagem de Montagem 20 RESUMINDO: E então? Gostou do que lhe mostramos? Aprendeu mesmo tudinho? Agora, só para termos certeza de que você realmente entendeu o tema de estudo deste capítulo, vamos resumir tudo o que vimos. Você deve ter aprendido que os registradores desempenham uma parte muito importante na programação e, principalmente, na programação de baixo nível, que é a linguagem de montagem. Temos os registradores gerais e os registradores de ponteiros e índice. Os registradores gerais são os acumuladores, contadores, de dados e de base. O registrador acumulador AX, por exemplo, utiliza a parte alta (AH) ou a parte baixa (AL), dependendo do tipo de instrução. Os registradores de ponteiro utilizam registradores específicos para manipular pilhas, por exemplo. Como registradores de ponteiro, temos registradores responsáveis pelo segmento de código, outros pelo de dados. Temos o SS, que é responsável pelo segmento de pilha, e outro pelos segmentos extras. Todos eles sempre sofrem diversas alterações durante o uso da máquina. Linguagem de Montagem 21 Acesso às portas lógicas em Assembly OBJETIVO: Ao término deste capítulo, você será capaz de aplicar os comandos de acesso a portas lógicas utilizando a linguagem de montagem. E então? Motivado para desenvolver essa competência? Então, vamos lá. Avante! Quando falamos em acessar um meio digital ou um computador digital, referimo-nos aos meios de acesso ao equipamento, mediante programação, para envio ou recebimento de dados. Para acessar digitalmente um computador, usamos portas e memória. Uma porta lógica pode ser implementada por instruções AND ou IF, por exemplo, as quais podem gerar um resultado TRUE ou FALSE. As portas podem controlar as informações que são recebidas e enviadas. A memória armazena o bit de dados, podendo ser 0 ou 1, que são os possíveis estados em que o dispositivo pode estar, sendo eles: habilitado ou desabilitado. Com esses dois componentes (porta e memória), quando ligados a outros do mesmo tipo, ou seja, a outras portas e memórias, podemos ter um computador. A base fundamental de um computador são: portas, para a comunicação com outras portas e envio de dados; e memória, para armazenar as portas que devem ser habilitadas e as portas que devem ser desabilitadas. Os elementos básicos de um computador digital, como sabemos, precisam realizar funções de armazenamento, movimentação, processamento e controle. Somente dois tipos fundamentais de componentes são necessários: portas e células de memória. Uma porta é um dispositivo que implementa uma função booleana ou lógica simples, como IF A AND B ARE TRUE THEN C IS TRUE (porta AND). Esses dispositivos são chamados de portas porque controlam o fluxo de dados de modo semelhante às portas de canal. A célula de memória é um dispositivo que pode armazenar um bit de dados; ou seja, o dispositivo pode estar em um de dois estados estáveis de cada vez. Interconectando grandes quantidades desses dispositivos Linguagem de Montagem 22 fundamentais, podemos construir um computador. (STALLINGS, 2010, p. 22) Figura 4 – Porta paralela Fonte: SCHMIDT (2003, p. 4). Em relação a impressoras, por exemplo, alguns modelos utilizam porta paralela, que, por usa vez, utilizam os registradores AX e DX, em que: AH = 01H DX = Porta Em relação aos registradores de retorno, temos: AH = Status da impressora Em que a porta do registrador DX pode ser os valores: LPT1=0, LPT2=1 e assim por diante. Em relação à porta paralela, o estado da impressora é codificado bit a bit, como nos dados seguintes. Linguagem de Montagem 23 Quadro 3 – Porta paralela valor do BIT 1 ou 0 SIGNIFICADO 0 1 Time-put 1 - 2 - 3 1 Informa o erro de entrada e saída 4 1 Impressora selecionada 5 1 Informa o fi m de papel 6 1 Informa o reconhecimento de comunicação 7 1 Informa que a impressora está pronta para o uso *No quadro, os bits 1 e 2 bits não são relevantes. Fonte: Elaborado pela autora (2022). Podemos usar as portas para fazer comunicação com diversos dispositivos externos, usando comando de saída OUT para enviar informações para uma porta específica e também o comando de entrada IN para receber dados ou informação de uma porta específica. Para exemplificar, temos um exemplo de comando de saída: OUT DX,AX Em que usamos o DX com o valor da porta para ser usada e para se comunicar com AX, que possui a informação que será enviada. Outro exemplo é o comando de entrada: IN AX,DX Em que usamos AX como registrador de informação para armazenar o DX que possui o endereço da porta com a informação que vai chegar. Linguagem de Montagem 24 O comando IN pode ler byte ou word de uma porta de E/S, e o objetivo é efetuar a transferência de dados (podendo ser byte ou word) de uma porta de entrada para o destino, que é o acumulador AL ou AX. A porta IN pode ser identificada com um valor de 8 bits, e esse valor pode permitir o acesso a portas de endereços que variam de 0 a 255. Usando o registrador DX, se este for de 16 bits, podemos especificar o endereço de portas, que podem variar de 0 a 65535. O formato do comando é: IN acumulador, porta ou IN acumulador, DX Para exemplificar, usaremos: IN AL, 38H IN AX,DX No código apresentado, a primeira linha de comando transfere para AL o byte na porta de entrada 38H e, na segunda linha de comandos, ocorre a transferência para AX, da word na porta de entrada que foi especificada pelo registrador DX. O comando OUT, por sua vez, pode escrever byte ou word em uma porta de E/S (entrada/saída) e tem o objetivo de efetuar a transferência de um byte ou uma word, presente no acumulador AL ou AX, respectivamente, para uma porta de saída. A porta OUT pode ser identificada com um valor de 8 bits que permite o acesso a portasem que os endereços podem variar 0 a 255, ou com o uso de DX que, como é de 16 bits, a qual permite especificar os endereços de portas que pode variar de 0 até 65535. O formato da porta é: OUT porta, acumulador ou OUT DX, acumulador Para exemplificar, temos o comando: Linguagem de Montagem 25 OUT 61H,AL ; Transfere para a porta de saída 61H, o byte em AL. OUT DX,AL ; Transfere para a porta de saída especificada em DX, o byte gravado em AL. Em que, com a execução da primeira linha, ocorre a transferência para a porta de saída 61H, o byte em AL. Na segunda linha de comando, ocorre a transferência para a porta de saída especificada em DX, o byte gravado em AL. O que é uma porta? Em linguagem de programação de baixo nível, as portas (ou gates) são importantes, por poderem ser montadas com transistores ou outros componentes analógicos. Porém, podem ser modeladas como um dispositivo digital com bastante precisão. Como vimos, as portas podem ter uma ou várias entradas digitais, que são representadas em formatos de 0 ou 1. A porta pode calcular a saída usando instruções bem simples que geram um resultado TRUE ou FALSE, como o AND ou OR. Cada porta pode ser composta por alguns transistores. Quando combinamos algumas portas, podemos formar uma memória no tamanho de 1 bit, para armazenar os valores 0 ou 1. Essas memórias de 1 bit, por sua vez, podem ser combinadas com outras memórias, formando grupos de 16 bits, 32 bits ou 64 bits, formando, assim, os registradores (como os registradores que estudamos AX e BX). Quando combinamos as portas, podemos formar um computador, ou seja, um equipamento com um mecanismo de computação. os objetos interessantes são chamados de portas (ou gates). Embora montadas a partir de componentes analógicos, como transistores, podem ser modeladas com precisão como dispositivos digitais. Cada porta tem uma ou mais entradas digitais (sinais representando 0 ou 1) e calcula como saída alguma função simples dessas entradas, como AND (E) ou OR (OU). Cada porta é composta de no máximo alguns transistores. Um pequeno número de portas podem ser combinadas para formar uma memória de 1 bit, que consegue armazenar um 0 ou um 1. As Linguagem de Montagem 26 memórias de 1 bit podem ser combinadas em grupos de (por exemplo) 16, 32 ou 64 para formar registradores. Cada registrador pode manter um único número binário até algum máximo. As portas também podem ser combinadas para formar o próprio mecanismo de computação principal. (TANENBAUM; AUSTIN, 2013, p. 4) Para Stallings (2010, p. 609), “porta lógica é um circuito eletrônico que produz um sinal de saída que é uma operação booleana simples de seus sinais de entrada”. VOCÊ SABIA? As portas são a base dos computadores digitais, pois formam a base do hardware, e todos os computadores são construídos a partir dessa base. Quando usamos os processadores AVR, dizemos que a porta é o caminho entre a CPU (unidade central de processamento) e os componentes externos de hardware e software. A CPU pode se comunicar com os componentes, obtendo ou lendo deles ou escrevendo neles. Nesse processo, a porta mais utilizada é o registrador de flag, pois os resultados das operações executadas são escritos e também onde as condições ou decisões são feitas. VOCÊ SABIA? Podemos contabilizar 64 portas diferentes em processadores AVR, por exemplo. Dependendo “do espaço de armazenamento e outros hardwares internos, as portas podem estar disponíveis e acessíveis ou não. A lista de portas que podem ser usadas está listada nos data sheets do processador” (SCHMIDT, 2003, p. 16). Nos processadores AVR, por exemplo, as portas possuem endereço fixo, onde a CPU se comunica, e o endereço não varia de uma AVR para outra, ou seja, é independente do tipo de AVR. Isto é, se uma porta C possui um endereço 0x17 (o 0x indica que é uma notação hexadecimal, Linguagem de Montagem 27 então 0x17 está no formato hexadecimal). Esse endereço sempre será usado pela porta C, e não por outra porta como A ou B. Os endereços podem ter uma nomenclatura ou “apelidos” para facilitar a chamada pelo sistema. Esses nomes são declarados no arquivo de cabeçalho (os includes), nos quais cada tipo de processador possui a lista de nomes que é definida pelo fabricante. Nos arquivos do tipo include, temos uma linha que define o endereço da porta B, como: .EQU PORTB, 0x18 Precisamos lembrar somente do nome da porta B, não necessariamente de sua localização de E/S do chip. O arquivo de include 8515def.inc pode ser usado na programação Assembly: .INCLUDE “C:\Somewhere\8515def.inc” Dessa forma, todos os registradores de 8515 podem ser definidos e acessíveis. Acessando uma porta em Assembly Em processadores AVR, assim como em outros processadores, as portas geralmente são organizadas em 8 bits, mas podemos manipular 8 bits individuais que não interferem uns com os outros. Caso esses bits possuam significado, podemos associar no arquivo include, permitindo a manipulação desse bit. Devido à convenção dos nomes, podemos também lembrar a posição desses bits. Os nomes são inseridos nos data sheets (cabeçalhos) e estão incluídos nos arquivos include. Eles são fornecidos nas tabelas das portas. Linguagem de Montagem 28 Figura 5 – Tabela de portas do AVR Componente Nome da porta Registrador da Porta Acumulador SREG Registrador de Status Pilha (stack) SPL/SPH Stackpointer (apontador de pilha) SRAM Externa/ Interrupção Externa MCUCR Registrador de Controle Geral MCU Interrupção Externa GIMSK Registrador de Interrupção Mascarado GIFR Registrador de Flag de Interrupção Interrupção de Timer TIMSK Registrador de Interrupção de Timer Mascarada TIFR Registrador de Interrupção de Flag de Timer Timer 0 TCCR0 Registrador controle de de Timer/Contador 0 TCNT0 Timer/Contador 0 Timer 1 TCCR1A Registrador controle de Timer/Contador 1 A TCCR1B Registrador controle de Timer/Contador 1 B TCNT1 Timer/Contador 1 OCR1A Registrador Comparador de Saída 1 A OCR1B Registrador Comparador de Saída 1 B ICR1L/H Registrador de Captura de Entrada Linguagem de Montagem 29 Timer Watchdog WDTCR Registrador de Controle do Timer Watchdog EEPROM EEAR Registrador de endereço da EEPROM EEDR Registrador de dados da EEPROM EECR Registrador de controle da EEPROM SPI SPCR Registrador de controle de periféricos Seriais SPSR Registrador de status de periféricos Seriais SPDR Registrador de dados de periféricos Seriais UART UDR Registrador de dados da UART USR Registrador de status da UART UCR Registrador de controle da UART UBRR Registrador de velocidade (baud rate) da UART Comparador analógico ACSR Registrador de status e controle do comparador analógico Portas de E/S PORTx Registrador de porta de saída DDRx Registrador de direção da porta PINx Registrador de porta de entrada Fonte: Schmidt (2003, p. 17). Para exemplificar, o registrador de controle geral MCU, também chamado MCUCR, é um número de bits de controle de forma individual que controla características gerais do chip. Linguagem de Montagem 30 .DEF MeuRegistrador = R16 LDI MeuRegistrador, 0b00100000 OUT MCUCR, MeuRegistrador SLEEP O comando anterior coloca o processador para “dormir”. No código apresentado, a instrução OUT envia o conteúdo do registrador, um bit Sleep-Enable chamado SE, para a porta MCUCR e seta o processador AVR, para entrar em repouso, caso tenha uma instrução SLEEP em execução. Já que todos os bits do MCUCR foram setados na instrução acima, e o bit SM (Sleep Mode) teve o valor zero, ocorreu um modo que chamamos meio-repouso, ou seja, não houve execução de outro comando, porém o chip ainda reage a instruções timers e outras interrupções de hardware. No entanto, esses eventos externos podem interromper “o sono” do processador (CPU) caso eles achem necessário enviar uma notificação.Figura 6 – Porta paralela Fonte: Schmidt (2003, p. 4). Na figura anterior, temos uma porta paralela que pode ser acessada por comandos de linguagem Assembly. Para ler o conteúdo de uma porta, podemos utilizar o comando IN. .DEF MeuRegistrador = R16 IN MeuRegistrador, MCUCR Linguagem de Montagem 31 A sequência precedente consegue ler os dados da porta MCUCR feita para o registrador. Como geralmente muitas portas são usadas em parte ou às vezes não utilizadas, os dados lidos são zeros. Na figura adiante, temos os principais comandos para acesso às portas em linguagem Assembly utilizadas nos processadores AVR. VOCÊ SABIA? Portas não são fabricadas nem vendidas individualmente, mas em unidades denominadas circuitos integrados, muitas vezes denominados ICs ou chips. Um IC é um pedaço quadrado de silício de tamanho variado, dependendo de quantas portas são necessárias para executar os componentes do chip. Substratos pequenos medirão cerca de 2 × 2 mm, enquanto os maiores podem ter até 18 × 18 mm. ICs costumam ser montados em pacotes retangulares de plástico ou cerâmica, que podem ser muito maiores que os substratos que eles abrigam, se forem necessários muitos pinos para conectar o chip ao mundo exterior. Cada pino se conecta com a entrada ou saída de alguma porta no chip ou à fonte de energia, ou ao terra. (TANENBAUM; AUSTIN, 2013, p. 124) A porta mais usada é o registrador de status com 8 bits. O acesso à porta “é feito somente por alteração dos bits pela CPU ou acumulador. A maioria destes bits é alterada por operações de teste de bit, comparação ou cálculos” (SCHMIDT, 2003, p. 17). A seguinte lista tem todos os comandos em Assembly que podem alterar os bits de status, dependendo do resultado da execução. Linguagem de Montagem 32 Figura 7 – Comandos Assembly que podem alterar os bits de status Bit Cálculo Lógico Comparação Bits Rolagem Outro Z ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR Z, BSET Z, CLZ, SEZ, TST ASR, LSL, LSR, ROL, ROR CLR C ADD, ADC, ADIW, SUB, SUBI, SBC, SBCI, SBIW COM, NEG CP, CPC, CPI BCLR C, BSET C, CLC, SEC ASR, LSL, LSR, ROL, ROR - N ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR N, BSET N, CLN, SEN, TST ASR, LSL, LSR, ROL, ROR CLR V ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR CP, CPC, CPI BCLR V, BSET V, CLV, SEV, TST ASR, LSL, LSR, ROL, ROR CLR S SBIW - - BCLR S, BSET S, CLS, SES - - H ADD, ADC, SUB, SUBI, SBC, SBCI NEG CP, CPC, CPI BCLR H, BSET H, CLH, SEH - - T - - - BCLR T, BSET T, BST, CLT, SET - - I - - - BCLR I, BSET I, CLI, SEI - RETI Fonte: Schmidt (2003, p. 18). Linguagem de Montagem 33 Tão importante quanto ler os 8 bits de uma porta, o processador deve reagir a certos status de uma porta. Então, podemos ler a porta toda e, se for o caso, isolar o bit que interessa. Alguns comandos executam o procedimento dependendo do estado do bit. Podemos também setar ou zerar bits sem ter de ler e escrever outros bits na porta. Para esse procedimento, usamos dois comandos: SBI e CBI. A linha de código é: .EQU BitAtivo=0 ; O bit que será mudado SBI PortB, BitAtivo ; O bit será setado para um CBI PortB, BitAtivo ; O bit será setado para zero IMPORTANTE: As portas elencadas têm uma limitação: somente as portas que possuem endereço inferior a 0x20 conseguem ser manipuladas dessa forma. Na figura subsequente, temos as portas de entrada e saída dos comandos de acesso para serem utilizados em linguagem Assembly, para processadores AVR. Linguagem de Montagem 34 Figura 8 – Portas de entrada e saída do AVR PORTAS E/S Port Register Função Endereço da Porta Endereço da RAM A PORTA Registrador de Dados 0x1B 0x3B DDRA Registrador da Direção dos Dados 0x1A 0x3A PINA Endereço dos Pinos de Entrada 0x19 0x39 B PORTB Registrador de Dados 0x18 0x38 DDRB Registrador da Direção dos Dados 0x17 0x37 PINB Endereço dos Pinos de Entrada 0x16 0x36 C PORTC Registrador de Dados 0x15 0x35 DDRC Registrador da Direção dos Dados 0x14 0x34 PINC Endereço dos Pinos de Entrada 0x13 0x33 D PORTD Registrador de Dados 0x12 0x32 DDRD Registrador da Direção dos Dados 0x11 0x31 PIND Endereço dos Pinos de Entrada 0x10 0x30 Fonte: Schmidt (2003, p. 59). Linguagem de Montagem 35 RESUMINDO: E então? Gostou do que lhe mostramos? Aprendeu mesmo tudinho? Agora, só para termos certeza de que você realmente entendeu o tema de estudo deste capítulo, vamos resumir tudo o que vimos. Você deve ter aprendido que a linguagem de montagem Assembly permite que possamos manipular diversas portas com comandos específicos. Vimos também que cada porta tem um registrador de porta e ocupa um endereço, e que algumas portas podem ser manipuladas de modo específico, dependendo do endereço de acesso. Vimos ainda que os endereços referentes aos recursos classificados como porta (independentemente de serem portas simples ou não) possuem endereços fixos, ou seja, eles não alteram como as outras informações que vimos até aqui. Cada porta possui um endereço que pode ser acessado por linhas de código em Assembly. Os endereços de memória são indicados em notação hexadecimal. 2 Linguagem de Montagem 36 Utilização da memória em Assembly OBJETIVO: Ao término deste capítulo, você será capaz de manipular dados em memória por meio da linguagem de montagem Assembly e identificar o propósito dos recursos. E então? Motivado para desenvolver essa competência? Então, vamos lá. Avante! Sabemos que os computadores fazem uso da memória para armazenar os dados e buscá-los em outro momento, ou como uma fonte de consulta, ou para armazenamento. Em linguagem de montagem, podemos manipular os dados que estão na memória do computador e movimentá-los de acordo com os objetivos do projeto. VOCÊ SABIA? “Um componente essencial de todo computador é sua memória. Sem ela não poderiam existir os computadores que conhecemos. A memória é usada para armazenar instruções a serem executadas e dados” (TANENBAUM; AUSTIN, 2013, p. 133). Figura 9 – Memórias em forma de papel no mural Fonte: Pixabay Linguagem de Montagem 37 O processador 8088, por exemplo, possui uma organização de memória diferenciada, em que, para 1 MB de memória, utilizamos 20 bits para representar o endereço relacionado à memória. Isso torna inviável o armazenamento do ponteiro para memória em registradores no formato de 16 bits. Para resolver esse problema, a memória é organizada em segmentos de 64 KB, nos quais um endereço que fique dentro de um segmento pode ser representado em formato de 16 bits. Organização da memória e segmentos A memória do processador 8088 (que é um vetor de bytes de 8 bits e endereçáveis) é usada para o armazenamento de instruções, armazenamento de dados e armazenamento de pilhas. Para organizar e separar a memória usada para esse objetivo, o processador 8088 utiliza segmentos. O segmento tem 65.536 bytes consecutivos no processador 8088. São quatro segmentos: segmento de código, segmento de dados, segmento de pilha e segmento extra. • Segmento de código: é a parte que contém as instruções referentes ao programa. Os dados são interpretados como endereço de memória. Aqui no segmento de código, o valor 0 (zero) não é o endereço absoluto de memória, mas, sim, uma referência ao endereço mais baixo que está no segmento de código. • Segmento de dados: referente aos dados inicializados e não inicializados. BX contém um ponteiro que aponta para esse segmento de dados. • Segmento de pilha: variáveis locais e resultados passados para a pilha. Os endereços que estão nos registradores SP e BP estão nesse segmento. • Segmento extra: segmentos avulsos podem ser colocados em qualquer lugarda memória conforme desejar ou for necessário. Cada segmento tem um registrador de 16 bits: CS, DS, SS e ES. Linguagem de Montagem 38 Esses registradores sempre usam múltiplos de 16, em endereços de 20 bits. Figura 10 – Registradores de segmentos Registradores de segmentos CS Segmento de código DS Segmento de dados SS Segmento de pilha ES Segmento extra 15 Fonte: Tanenbaum e Austin (2013, p. 545). Quando temos um registrador de segmentos, ele sempre vai apontar para a base do segmento. IMPORTANTE: Qualquer referência usada na memória utiliza os registradores de segmentos para gerar o endereço de memória. Se por acaso aparecer instrução sem referência a nenhum dos registradores, esse endereço vai ficar na parte de segmento de dados, que é o registrador DS, muito usado para a base do segmento. VOCÊ SABIA? Podemos achar o endereço físico na memória do próximo código de instrução deslocando o que estiver no registrador CS por quatro casas binárias e depois somando o valor do contador de programa. Quando temos um segmento de pilha, dizemos que ele é composto por palavras de 2 bytes. Por isso, o ponteiro de pilha do registrador SP sempre possui um número par. Então, a pilha pode ser preenchida dos endereços altos em direção aos endereços baixos. Linguagem de Montagem 39 Usamos o comando PUSH para diminuir o ponteiro de pilha em 2 e depois armazenar o operando no endereço de memória que será indicado e calculado a partir dos registradores SS e SP. Usamos também a instrução POP para recuperar o valor e incrementar o registrador SP em 2. VOCÊ SABIA? Temos endereços livres, e eles são os endereços do segmento de pilha de valor mais baixo do que os indicados pelo registrador SP. Sendo assim, podemos limpar a pilha, conseguindo, desso modo, aumentar o registrador SP. Na verdade, os registradores DS e SS serão iguais. Caso os registradores DS e SS sejam diferentes, precisamos de um décimo sétimo bit para cada ponteiro, com a finalidade de diferenciar os ponteiros para o segmento de dados e os ponteiros para o segmento de pilha. Quando terminamos a compilação do programa, o tamanho do código de programa passa a ser conhecido. Nesse momento, é interessante começar os segmentos de dados e os segmentos de pilha no primeiro múltiplo de 16 depois da última instrução. Podemos concluir que os segmentos de código e dados nunca usarão os mesmos endereços físicos (TANENBAUM; AUSTIN, 2013, p. 549). Endereçamento Instruções precisam de dados, podendo ser da memória ou dos registradores. Muitos comandos têm dois operandos, chamados de destino e de origem. Por exemplo, a instrução de cópia, ou a instrução de adição: MOV AX,BX ou ADD CX,20 Linguagem de Montagem 40 Nos comandos apresentados, o primeiro operando é destino e o segundo é origem. O destino deve ser um lugar em que a informação pode ser armazenada. Ou seja, sempre teremos origens, mas nem sempre destinos. Temos casos em que os operandos não são mencionados, como na instrução MULB, na qual o registrador AX tem poder suficiente para agir como um destino. Temos várias instruções de um só operando, por exemplo: incrementos, deslocamentos, negações, entre outros. Para esses, não temos requisito de registrador, e o resultado da diferença entre as operações de palavras e bytes fica deduzido pelos opcodes (que são tipos de instrução). O processador 8088, por exemplo, pode suportar quatro tipos de dados: • Byte: o tipo byte tem o tamanho de 1 byte. • Palavra: o tipo palavra possui o tamanho de 2 bytes. • Longo: o tipo longo possui o tamanho de 4 bytes. • Decimal codificado em binário: é o tipo de dado em que os dois dígitos no formato decimal podem ser agrupados (empacotados) em uma palavra. O tipo de dado decimal codificado em binários não é suportado pelo interpretador. Sempre temos um endereço de memória que referencia um byte. Quando temos uma palavra ou um longo, os locais de memória logo acima do byte que é indicado também serão referenciados. Por exemplo, se a palavra 20 está na memória nos locais 20 e 21; então o longo no endereço 24 ocupará os endereços 24, 25, 26 e 27. Em linguagem de montagem Assembly, são os rótulos que representam os locais de memória, e não valores numéricos, e a conversão pode ser realizada durante o processo de montagem, ou seja, no tempo de montagem. Linguagem de Montagem 41 As instruções CALL e JMP também podem ser armazenadas na memória por um rótulo. Os parênteses nos rótulos são essenciais, pois ADD CX,20 é uma instrução, mas também significa somar a constante 20 a CX, e não somente somar o conteúdo da palavra de memória 20. IMPORTANTE: Se temos um endereço indireto de registrador, armazenamos o endereço do operando nos registradores BX, SI ou DI. Podemos também colocar a base no registrador BX e separar o registrador SI ou o registrador DI para efetuar a contagem. A esse processo chamamos de endereçamento de registrador com índice. Vamos exemplificar: PUSH (BX)(DI) A instrução precedente pesquisa o dado do local do segmento de dados em que o endereço é fornecido pela soma do registrador BX com o registrador DI. Em seguida, o resultado é transferido para a pilha. Podemos pegar os dois últimos endereços e combiná-los para conseguir o endereçamento de registrador que tem o índice e o deslocamento, como: NOT 20(BX)(DI) O comando apresentado complementa a palavra de memória das seguintes formas: BX + DI + 20 e BX + DI + 21. Temos também o modo de endereçamento pelo qual o operando é um valor constante de byte ou de palavra, usada na instrução, o qual chamamos de endereçamento imediato. Por exemplo: CMP AX,50 Esse comando compara AX com a constante 50 e depois ajusta os bits no registrador de flags de acordo com os resultados. Linguagem de Montagem 42 Finalmente, temos algumas das instruções de endereçamento implícito, em que o operando está implícito na instrução. Por exemplo: PUSH AX O comando anterior passa o conteúdo do registrador AX para a pilha, decrementando o registrador SP. Em seguida, copia o registrador AX para o local apontado pelo registrador SP. Por isso, o registrador SP não é indicado na instrução, pois a instrução PUSH implica que SP deve ser usado. Figura 11 – Endereçamento de operandos Modo Operando Exemplos Endereçamento de registrador Registrador de bytes Registrador de palavras Registrador de bytes Registrador de palavras AH, AL, BH, BL, CH, CL, DH, DL AX, BX, CX, DX, SP, BP, SI, DI Endereçamento de segmentos de dados Endereço direto Indireto de registrador Deslocamento de registrador Registrador com índice Registrador com índice e deslocamento Endereço vem após opcode Endereço em registrador Endereço em registrador + desloc. Endereço é BX + SI/ DI BX + SI DI + deslocamento (#) (SI), (DI), (BX) #(SI), #(DI), #(BX) (BX)(SI), (BX)(DI) #(BX)(SI), #(BX)(DI) Endereço de segmento de pilha Indireto de ponteiro de base Deslocamento de ponteiro de base Ponteiro de base com índice Desloc. de índice de ponteiro de base Endereço em registrador Endereço é BP + deslocamento Endereço é BP + SI/ DI BP + SI/DI + deslocamento (BP) #(BP) (BP)(SI), (BP)(DI) #(BP)(SI), #(BP)(DI) Dados imediatos Byte/palavra imediato Dados são parte da instrução # Endereço implícito Instrução push/pop Flags de load/store Traduzir XLAT Instruções de cadeias repetidas Instruções de E/S Converte byte, palavra Endereço indireto (SP) Registrador de fl ag de status AL, BX (SI), (DI), (CX) AX, AL AL, AX, DX PUSH, POP, PUSHF, POPF LAHF, STC, CLC, CMC XLAT MOVS, CMPS, SCAS IN #, OUT # CBW, CWD Linguagem de Montagem 43 Modo Operando Exemplos Endereçamento de registrador Registrador de bytes Registrador de palavras Registrador de bytes Registrador de palavras AH, AL, BH, BL, CH, CL, DH, DL AX, BX, CX, DX, SP, BP, SI, DI Endereçamento de segmentos de dadosEndereço direto Indireto de registrador Deslocamento de registrador Registrador com índice Registrador com índice e deslocamento Endereço vem após opcode Endereço em registrador Endereço em registrador + desloc. Endereço é BX + SI/ DI BX + SI DI + deslocamento (#) (SI), (DI), (BX) #(SI), #(DI), #(BX) (BX)(SI), (BX)(DI) #(BX)(SI), #(BX)(DI) Endereço de segmento de pilha Indireto de ponteiro de base Deslocamento de ponteiro de base Ponteiro de base com índice Desloc. de índice de ponteiro de base Endereço em registrador Endereço é BP + deslocamento Endereço é BP + SI/ DI BP + SI/DI + deslocamento (BP) #(BP) (BP)(SI), (BP)(DI) #(BP)(SI), #(BP)(DI) Dados imediatos Byte/palavra imediato Dados são parte da instrução # Endereço implícito Instrução push/pop Flags de load/store Traduzir XLAT Instruções de cadeias repetidas Instruções de E/S Converte byte, palavra Endereço indireto (SP) Registrador de fl ag de status AL, BX (SI), (DI), (CX) AX, AL AL, AX, DX PUSH, POP, PUSHF, POPF LAHF, STC, CLC, CMC XLAT MOVS, CMPS, SCAS IN #, OUT # CBW, CWD Modos de endereçamento de operandos. O símbolo # indica um valor numérico ou rótulo. Fonte: Tanenbaum e Austin (2013, p. 550). O processador 8088 pode usar instruções específicas para mover (MOVS), comparar (CMPS) ou também para examinar (SCAS) cadeias de caracteres. Para exemplificar o uso de pilha, vamos analisar um jogo chamado Torre de Hanói. “Torres de Hanói” é um antigo problema que tem solução simples envolvendo recursão. Em certo mosteiro em Hanói, havia três estacas de ouro. Ao redor da primeira havia uma série de 64 discos concêntricos de ouro, cada um com um orifício no meio para a estaca. Cada disco tem um diâmetro um pouco menor do que o que está abaixo dele. A segunda e terceira estacas estavam inicialmente vazias. Os monges desse mosteiro estão muito ocupados transferindo todos os discos para a estaca 3, um por vez, mas nunca um disco maior pode ficar por cima de um menor. Diz a lenda que, quando eles terminarem, o mundo acaba. Se você quiser fazer uma experiência prática, pode usar discos de plástico e em número menor, mas, quando resolver o problema, nada acontecerá. Para conseguir o efeito do fim do mundo, você precisa ter 64 discos, e de ouro. (TANENBAUM; AUSTIN, 2013, p. 321) Linguagem de Montagem 44 Figura 12 – Torres de Hanói com cinco discos Fonte: Tanenbaum e Austin (2013, p. 321). A seguir, temos um código de resolução do problema de Torres de Hanói para um processador Core i7, em linguagem de montagem Assembly. .686 ; compile para processador da classe Core i7 .MODEL FLAT PUBLIC _torres ; exporte ‘torres’ EXTERN _printf:NEAR ; importe printf .CODE _torres: PUSH EBP ; salve EBP (ponteiro de quadro) e decremente ESP MOV EBP, ESP ; ajuste novo ponteiro de quadro acima de ESP CMP [EBP+8], 1 ; se (n == 1) JNE L1 ; desvie se n não for 1 MOV EAX, [EBP+16] ; printf(“ ...”, i, j); PUSH EAX ; note que os parâmetros i, j e a cadeia MOV EAX, [EBP+12] ; de formato são passados para a pilha em ordem PUSH EAX ; inversa. Essa é a convenção de chamada em C PUSH OFFSET FLAT:format ; offset flat significa endereço de formato Linguagem de Montagem 45 CALL _printf ; chame printf ADD ESP, 12 ; retire parâmetros da pilha JMP Fim ; terminamos L1: MOV EAX, 6 ; inicie k = 6 − i − j SUB EAX, [EBP+12] ; EAX = 6 − i SUB EAX, [EBP+16] ; EAX = 6 − i − j MOV [EBP+20], EAX ; k = EAX PUSH EAX ; inicie torres(n − 1, i, k) MOV EAX, [EBP+12] ; EAX = i PUSH EAX ; passe i MOV EAX, [EBP+8] ; EAX = n DEC EAX ; EAX = n − 1 PUSH EAX ; passe n − 1 CALL _torres ; chame torres(n − 1, i, 6 − i − j) ADD ESP, 12; retire parâmetros da pilha MOV EAX, [EBP+16] ; inicie torres(1, i, j) PUSH EAX ; passe j MOV EAX, [EBP+12] ; EAX = i PUSH EAX ; passe i PUSH 1 ; passe 1 CALL _torres ; chame torres(1, i, j) ADD ESP, 12 ; retire parâmetros da pilha MOV EAX, [EBP+12] ; inicie torres(n − 1, 6 − i − j, i) PUSH EAX ; passe i MOV EAX, [EBP+20] ; EAX = k Linguagem de Montagem 46 PUSH EAX ; passe k MOV EAX, [EBP+8] ; EAX = n DEC EAX ; EAX = n − 1 PUSH EAX ; passe n − 1 CALL _torres ; chame torres(n − 1, 6 − i − j, i) ADD ESP, 12 ; ajuste ponteiro de pilha Fim: LEAVE ; prepare para sair RET 0 ; retorne ao chamador .DATA format DB “Mova disco de %d para %d\n” ; cadeia de formato END No código apresentado, foram usados comandos recursivos (que chamam ele mesmo) e analisando o código recursivo em linguagem de máquina, podemos observar que eles são diretos. Linguagem de Montagem 47 RESUMINDO: E então? Gostou do que lhe mostramos? Aprendeu mesmo tudinho? Agora, só para termos certeza de que você realmente entendeu o tema de estudo deste capítulo, vamos resumir tudo o que vimos. Você deve ter aprendido que a memória pode ser arrumada em segmentos, como segmento de código, de dados, de pilha e extra. Cada segmento tem um registrador, mesmo sendo de memória, e são de 16 bits. Podemos usar comandos específicos para manipular dados da memória, pois temos segmentos distintos, como segmento de dados, segmento de pilha, segmento de código e segmento extra. Cada segmento tem uma função específica e utiliza o Assembly utiliza instruções para acessar esses segmentos que possuem registradores específicos, como o CS, DS, SS e ES. Os segmentos de ponteiro também utilizam registradores específicos, que são o SP, BP, SI e DI. Os comandos POP e PUSH podem manipular os valores da pilha, aumentando e diminuindo os valores do ponteiro, em que o PUSH incrementa dados na pilha e o POP retira. Vale lembrar que a pilha cresce para baixo; então, os valores de memória diminuem à medida que a pilha cresce. Linguagem de Montagem 48 Aplicação de macro em linguagem Assembly OBJETIVO: Ao término deste capítulo, você será capaz de implementar uma macro em linguagem de montagem Assembly. E então? Motivado para desenvolver essa competência? Então, vamos lá. Avante! A macro é um recurso muito usado em ambientes de programação e produtividade. O recurso de macro permite executar procedimentos repetitivos com menos esforço. Em planilha eletrônica, por exemplo, podemos usar o recurso de macro para otimizar procedimentos, aumentando, assim, a produtividade. Em linguagem de programação, tanto na linguagem de alto nível como na linguagem de montagem, que é considerada de baixo nível, podemos usar o recurso de macro para otimizar rotinas e procedimentos também, executando os comandos da macro por meio da chamada à macro. Programadores de linguagem de montagem com frequência precisam repetir sequências de instruções várias vezes dentro de um programa. O modo mais óbvio de fazer isso é escrever as instruções requeridas onde quer que sejam necessárias. Se uma sequência for longa, entretanto, ou tiver de ser usada muitas vezes, escrevê-la repetidas vezes torna- se tedioso. (TANENBAUM; AUSTIN, 2013, p. 413) Figura 13 – Destacando procedimentos Fonte: Pixabay Linguagem de Montagem 49 Definindo uma macro Em alguns momentos, durante o processo de elaboração de um programa de montagem, podemos elaborar algum procedimento com o objetivo de deixar o código enxuto e otimizado. A macro pode ser usada para otimizar um programa, em que instruções podem ser executadas fazendo uso de um nome. Uma definição de macro é um modo de dar um nome a um pedaço de texto. Após uma macro ser definida, o programador pode escrever o nome dela em vez do pedaço de programa. Uma macro é, na verdade, uma abreviatura para um pedaço de texto. (TANENBAUM; AUSTIN, 2013, p. 413) Podemos definir uma macro como um grupo de instruções repetitivas que estão em um programa e que são codificadas apenas uma vez, permitindo poupar espaço de memória, além de ser possível utilizar várias. IMPORTANTE: Procedimento e macro são diferentes. Procedimento é uma extensão de um programa para realizar tarefas. A macro é um móduloque possui funções e pode ser utilizado por diversos programas. Procedimento e macro são diferentes. Para efetuar uma chamada a um procedimento, podemos usar a instrução CALL; e para fazer chamada a uma macro em Assembly, podemos usar as instruções normais, comuns na programação de linguagem de montagem. Quando uma macro é definida e encontrada durante a execução do programa, o sistema armazena o nome da macro em uma tabela para futuros acessos, a qual irá substituir pelo conteúdo da macro sempre que o nome da macro for encontrado durante a execução do programa. Quando o assembler encontra uma definição de macro, ele a salva em uma tabela de definição para uso subsequente. Desse ponto em diante, sempre que o nome da macro aparecer como um opcode, o assembler o substitui pelo Linguagem de Montagem 50 corpo da macro. A utilização de um nome de macro como um opcode é conhecida como chamada de macro e sua substituição pelo corpo da macro é denominada expansão de macro. (TANENBAUM; AUSTIN, 2013, p. 414) Podemos definir um procedimento como uma coleção de instruções na qual é possível direcionar o curso de um programa. Quando a execução dessas instruções do procedimento acabarem, o controle volta para linha que segue à que solicitou o procedimento. Os procedimentos podem nos ajudar a criar programas legíveis e também fáceis de ajustar. No momento em que chamamos um procedimento, é mantido na pilha o endereço da instrução seguinte, que é utilizado quando o programa retorna da execução do procedimento. Ou seja, após executar um procedimento, o programa retorna ao ponto anterior para continuar a execução das instruções. Temos dois tipos de procedimentos: • Intrassegmentos: que se localizam no mesmo segmento de origem da instrução que o chama. • Intersegmentos: que podem estar localizados em diferentes segmentos de memória. No momento em que os procedimentos intersegmentos são usados, temos o valor de IP, que é armazenado na pilha, e quando os procedimentos intersegmentos são usados, temos o valor de CS:IP, que é armazenado. Vale lembrar que o registrador CS pode indicar qual é o segmento de código. O comando para chamar um procedimento é como segue (sintaxe): CALL NomedoProcedimento Em que as partes que compõem o procedimento são: • Declaração do procedimento. • Código do procedimento. • Diretiva de retorno. Linguagem de Montagem 51 • Término do procedimento. Para exemplificar, se quisermos somar dois bytes que estão armazenados nas partes AH e AL, teremos o resultado da soma em BX: Soma Proc Near ; Declaração do Procedimento Mov BX, 0 ; Conteúdo do Procedimento... Mov BL, AH Mov AH, 00 Add BX, AX Ret ; Diretiva de retorno Soma EndP ; Fim do Procedimento No código apresentado, temos a primeira palavra, Soma, que corresponde ao nome do procedimento. O comando “Proc” declara-o e a palavra “Near” indica o procedimento como sendo do tipo intrassegmento, ou seja, no mesmo segmento. Temos também a diretiva “Ret” que tem o IP com o endereço armazenado para a pilha, e então retornar ao programa que chamou. Finalmente, temos o comando “Soma EndP”, que indica ao programa o fim do procedimento. Para estabelecer o momento de declaração de um procedimento intersegmento, basta substituir a palavra Near para FAR. Para efetuar a chamada a esse procedimento, usamos a linha de comando feito de modo idêntico: Call Soma IMPORTANTE: O uso de macros permite ao programador otimizar o programa, deixando o código mais enxuto e flexível em relação ao procedimento. Linguagem de Montagem 52 Sintaxe de uma macro As macros podem ser compostas pelas seguintes partes: • Declaração da macro. • Código da macro. • Diretiva de término da macro. Podemos declarar uma macro assim: NomeMacro MACRO [parametro1, parametro2...] Assim como podemos usar parâmetros, também podemos criar uma macro sem eles. IMPORTANTE: Para finalizar uma macro, usamos o comando: ENDM. Chamadas de macro não devem ser confundidas com chamadas de procedimento. A diferença básica é que a primeira é uma instrução para o assembler substituir o nome da macro pelo corpo dela. Uma chamada de procedimento é uma instrução de máquina que é inserida no programa- objeto e que mais tarde será executada para chamar o procedimento. (TANENBAUM; AUSTIN, 2013, p. 414) VOCÊ SABIA? MACRO e ENDM são instruções que indicam início e fim de uma macro. Mesmo que o formato da linguagem de montagem mude de acordo com os tipos de processadores, a programação de macro possui um padrão, independentemente de ser uma programação para o Core Linguagem de Montagem 53 da Intel ou um ARM. Ou seja, todos requerem as mesmas estruturas ou partes básicas no momento de definição de macro: • É necessário possuir um cabeçalho de macro, com o nome da macro que está sendo criada ou definida. • Possuir um texto que abranja toda a estrutura da macro. • Uma finalização, ou uma pseudoinstrução que delimite o final da definição (por exemplo, ENDM). Quadro 4 – Comparação de chamadas de macro e chamadas de procedimento ITEM Chamada de macro Chamada de procedimento Quando a chamada é feita? Durante a montagem Durante a execução do programa O corpo é inserido no programa- objeto em todos os lugares em que a chamada é feita? SIM NÃO Uma instrução de chamada de procedimento é inserida no programa-objeto e executada mais tarde? NÃO SIM Deve ser usada uma instrução de retorno após a conclusão da chamada? NÃO SIM Quantas cópias do corpo aparecem no programa-objeto? Uma por chamada de macro Uma Fonte: Tanenbaum e Austin (2013, p. 414). Para exemplificar, vamos usar uma macro para colocar o cursor em uma determinada posição da tela: Linguagem de Montagem 54 Pos MACRO Linha, Coluna PUSH AX PUSH BX PUSH DX MOV AH, 02H MOV DH, Linha MOV DL, Coluna MOV BH, 0 INT 10H POP DX POP BX POP AX ENDM Aqui em linguagem de montagem, assim como nas linguagens de alto nível, para usar uma macro é necessário chamar pelo nome. Adiante temos um exemplo de macro com a estrutura condicional IF (TANENBAUM; AUSTIN, 2013, p. 416): M1 MACRO IF WORDSIZE GT 16 M2 MACRO ... ENDM ELSE M2 MACRO … ENDM Linguagem de Montagem 55 ENDIF ENDM Na figura que se segue, temos um comparativo de um código com uso de macro e o mesmo código sem o uso de macro. Figura 14 – Sequências de instruções com e sem uso da macro – (a) Sem uma macro; (b) Com uma macro MOV EAX,P CHANGE MACRO P1, P2 MOV EBX,Q MOV EAX,P1 MOV Q,EAX MOV EBX,P2 MOV P,EBX MOV EBX,P2 MOV P1,EBX MOV EAX,R ENDM MOV EBX,S MOV S,EAX CHANGE P, Q MOV R,EBX CHANGE R, S (a) (b) Fonte: Tanenbaum e Austin (2013, p. 415). Biblioteca de macros Ao usar o recurso de macro, uma das facilidades é que podemos criar bibliotecas, que são conjuntos de macros, pelo qual podemos incluir em um programa diferentes arquivos. Para criar bibliotecas, é bastante simples. É necessário criar um arquivo com todas as macros necessárias e salvar como um arquivo do tipo texto. Para incluir a biblioteca em um programa, precisamos colocar a instrução Include NomedoArquivo no início do programa, antes de ocorrer a declaração do modelo de memória. Linguagem de Montagem 56 Para exemplificar, vamos supor que o arquivo de macros tenha sido salvo com o nome de MACROS.TXT, a instrução Include poderia ser usada da seguinte maneira: ;Início do programa Include MACROS.TXT .MODEL SMALL .DATA ;Os dados vão aqui .CODE Inicio: ;O código do programa começa aqui .STACK ;A pilha é declarada End Inicio ;Fim do programa A seguir, temos outro exemplo de macro: PrintX MACRO mov al,’X’ call WriteChar ENDM Para chamar a macro anterior em Assembly, usamos o comando que se segue: .code PrintX Quando o processador chamar o programa, vai descobrir que temde chamar PrintX. Linguagem de Montagem 57 Por fim, macros podem chamar outras macros, incluindo elas mesmas. Se uma macro for recursiva, isto é, chamar a si mesma, deve passar para si mesma um parâmetro que é trocado a cada expansão, e a macro deve testar o parâmetro e encerrar a recursão quando alcançar certo valor. Caso contrário, o assembler pode ser colocado em um laço infinito. Se isso acontecer, o Assembly deve ser terminado explicitamente pelo usuário. (TANENBAUM; AUSTIN, 2013, p. 416) A seguir temos um exemplo de macro em formato NASM (Netwide Assembly), que é um tipo de montador de código-fonte aberto. Figura 15 – Macro em NASM %MACRO PROLOGUE 1 PUSH EBP ; coloca conteúdo de EBP na pilha ; apontada por ESP e ; decrementa conteúdo de ESP em 4 MOV EBP, ESP ; copia conteúdo de ESP para EBP SUB ESP, %1 ; subtrai o valor do primeiro parâmetro de ESP %ENOMACRO Fonte: Stalling (2010, p. 587). “O número 1 depois do nome da macro na linha %MACRO define o número de parâmetros que a macro espera receber. O uso de %1 dentro da definição da macro refere-se ao primeiro parâmetro da chamada da macro” (STALLING, 2010, p. 588). Então, quando chamamos a macro PROLOGUE MYFUNC: PROLOGUE 12 Será expandido as linhas de código subsequente: MYFUNC: PUSH EBP MOV EBP, ESP SUB ESP, 12 Muitos programas de linguagem de montagem fazem uso do recurso de macro permitindo que o programador possa dar nomes simbólicos para cada sequência de código, além de ser muito usada para inclusão subsequente. De forma geral, o uso de macros pode ser Linguagem de Montagem 58 parametrizado em um formato direto. As macros podem ser executadas por um algoritmo de processamento literal de cadeias (TANENBAUM; AUSTIN, 2013, p. 433). RESUMINDO: E então? Gostou do que lhe mostramos? Aprendeu mesmo tudinho? Agora, só para termos certeza de que você realmente entendeu o tema de estudo deste capítulo, vamos resumir tudo o que vimos. Você deve ter aprendido que podemos usar a macro para otimizar procedimentos. As macros são utilizadas em diversos programas e podem ser utilizadas no programa Assembly também. Para otimizar ainda mais os procedimentos de programação, podemos fazer uso da biblioteca de macros para deixar o código elegante e arrumado. Para incluir esse arquivo de macro, em formato de biblioteca, nos programas desenvolvidos em Assembly, podemos usar os comandos “Include”. Essa linha de programação deve ser inserida no início do programa. As chamadas de macro podem ocorrer durante todo o código do programa, basta chamar a macro pelo nome, e todos os procedimentos que estão na macro serão executados. Para finalizar a macro, usamos comando específico, o “ENDM”, por exemplo, podendo ser alterado de acordo com o processador que estamos programando, como o NASM, no qual finalizamos com o comando “%ENOMACRO”. Linguagem de Montagem 59 REFERÊNCIAS MENTE BINÁRIA. Fundamentos da engenharia reversa, [s.d.]. Disponível em: https://mentebinaria.gitbook.io/engenharia-reversa/ assembly/registradores. Acesso em: 15 maio 2022. SCHMIDT, G. Introdução para o iniciante à Linguagem Assembly dos microprocessadores ATMEL-AVR, 2003. Disponível em: www.avr- asm-tutorial.net. Acesso em: 20 abr. 2022. STALLINGS, W. Arquitetura e organização de computadores. 8. ed. São Paulo: Pearson, 2002. Disponível em: http://www.telecom.uff. br/orgarqcomp/arq/arquitetura-e-organizacao-computadores-8a.pdf. Acesso em: 07 abr. 2022. TANENBAUM, A.; AUSTIN, T. Organização estruturada de computadores. 6. ed. São Paulo: Pearson, 2013. Linguagem de Montagem http://www.telecom.uff.br/orgarqcomp/arq/arquitetura-e-organizacao-computadores-8a.pdf http://www.telecom.uff.br/orgarqcomp/arq/arquitetura-e-organizacao-computadores-8a.pdf