Baixe o app para aproveitar ainda mais
Prévia do material em texto
Linguagem Assembly Família Intel última revisão Ago/2007 1 Linguagem ASSEMBLY da Família INTEL 1 Introdução Este texto apresenta uma descrição da linguagem de montagem do microprocessador 8086/88. Este processador se tornou um enorme sucesso comercial, não apenas mas sobretudo devido ao lançamento em 1981 do primeiro microcomputador pessoal (PC) da IBM, que se baseava no 8088. Figura 1: Diagrama de blocos da arquitetura interna de um processador 8086/88 Com o sucesso dos PCs e interessada em preservar ao investimento em software dos seus usuários a Intel, fabricante do processador, preocupou-se em preservar a compatibilidade das novas gerações de processadores com a arquitetura básica do 8086/88. Como resultado, mesmo os atuais membros da família Pentium ainda executam código escrito para os fundadores desta dinastia de Linguagem Assembly Família Intel última revisão Ago/2007 2 processadores Intel. Levando ainda em conta a simplicidade da arquitetura do 8086/88 é didaticamente conveniente construir o estudo sobre linguagem de montagem a partir deste processador. Este texto apresenta inicialmente a arquitetura interna destes microprocessadores visível ao programador Assembly. Em seguida é apresentada a sua linguagem Assembly. O aluno que desejar uma documentação completa, incluindo as novas instruções da família Pentium e as instruções para dados em ponto flutuante, poderão fazer o download do Manual correspondente (http://developer.intel.com/design/pentium4/manuals/245471.htm). 2 Arquitetura interna A Figura 1 mostra a arquitetura interna dos microprocessadores 8086 e 8088. Há basicamente duas diferenças entre eles: a largura do barramento externo de dados (8 bits no 8088, e 16 bits no 8086), e o tamanho do bloco "fila de instruções" (4 bytes no 8088 e 6 bytes no 8086). Os principais blocos são: a) ULA capaz de executar operações sobre 8 ou 16 bits. b) Banco de Registradores, constituído de: • Quatro registradores gerais, de 16 bits, AX, BX, CX e DX, que podem ser subdivididos em (e referenciados separadamente como) registradores de 8 bits, AH, AL, BH, BL, CH, CL, DH e DL. Neste caso, X representa o registrador de 16 bits, enquanto H ("high") e L ("low") representam respectivamente seus 8 bits mais e menos significativos. Os registradores A têm a função de acumulador para algumas operações lógicas/aritméticas. Os registradores B são utilizados em algumas instruções como registradores de base. Há instruções que utilizam implicitamente os registradores C como contadores. Os registradores D não têm nenhuma função específica, além de funcionarem como registradores de rascunho. • Dois Registradores de Índice, de 16 bits, SI e DI. • Apontador de Pilha, de 16 bits, SP. • Um segundo Registrador de Base, de 16 bits, BP. • Contador de Programa, de 16 bits, IP. • Quatro Registradores de Segmento, de 16 bits, CS, DS, ES, SS. Estes registradores participam na formação do endereço físico que é gerado nos pinos de endereço do processador. Sua função será descrita, mais adiante. • Registrador de Estado, de 16 bits. Na verdade somente 9 destes 16 bits têm significado, conforme mostra a Figura 2. Cada sinalizador ("flag") tem o seguinte significado: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Figura 2: Registrador de Estado do 8086/8088 Linguagem Assembly Família Intel última revisão Ago/2007 3 ♦ OF - sinalizador de "overflow". Este bit é ativado quando a ALU ao executar uma operação o transporte para dentro do bit mais significativo é diferente do transporte para fora do bit mais significativo. ♦ DF - sinalizador de direção. Há instruções que envolvem os registradores de índice na formação do endereço efetivo dos operandos. Algumas destas instruções implicam na alteração do conteúdo do registrador de índice. Dependendo do conteúdo deste sinalizador, os registradores de índice serão incrementados ou decrementados automaticamente, quando tais instruções são executadas. ♦ IF - sinalizador de interrupção. Este sinalizador determina, se o processador está habilitado ou desabilitado a atender interrupções. Interrupção será o tema do próximo capítulo. ♦ TF - "trap flag". Quando este sinalizador está ativado, é gerada internamente uma interrupção a cada instrução executada. A compreensão de seu funcionamento será postergada para o próximo capítulo. ♦ SF - sinalizador de sinal. Corresponde ao bit mais significativo do resultado produzido pela ULA, ao executar operações artiméticas. ♦ ZF - sinalizador de zero. Indica, quando ligado, que o resultado produzido pela ULA é nulo. ♦ AF - indica o transporte para fora do nibble menos significativo - do bit 3 para o bit 4 - do resultado produzido pela ULA. É utilizado quando se manipula operandos representados em código BCD ou ASCII. ♦ PF - sinalizador de paridade. Este sinalizador é ativado, quando o número de 1s nos 8 bits menos significativos do resultado produzido pela ULA é par, e desativado caso contrário. ♦ CF - sinalizador de transporte. c) Controle. No esquema apresentado na Figura 1 o controle está dividido em dois blocos: "Sistema de Controle da EU" e "Lógica de Controle das Barras". Esta divisão se justifica tendo em vista que o processador está dividido em duas unidades funcionais que cooperam entre si: • A Unidade de Execução - "Execution Unit" - (EU): Esta unidade é responsável exclusivamente pela execução das instruções. Ela não se ocupa da transferência de informações entre o microprocessador e os demais componentes do sistema. Quando a execução de uma instrução exige o acesso a algum operando externo ao microprocessador, a EU gera uma solicitação à • A Unidade de Interface com o Barramento - "Bus Interface Unit" - (BIU): Esta unidade se ocupa exclusivamente com a transferência de informações entre o microprocessador e o restante do sistema. A forma como estas duas unidades cooperam será vista adiante. A Figura 1 contém, além disso, alguns outros elementos. São eles: um Linguagem Assembly Família Intel última revisão Ago/2007 4 somador (Σ), a fila de instruções, além dos quatro registradores de segmento já mencionados. A função destes elementos será discutida mais adiante. 3 Linguagem Assembly do 8086/8088 Nas linhas que se seguem são apresentadas de modo resumido as principais características da linguagem Assembly do 8086/8088 (ASM86). Para ter uma descrição detalhada desta linguagem. O aluno deverá recorrer aos manuais e livros indicados. 3.1 Convenção para o armazenamento de variáveis. Todos os sistemas reais são capazes de armazenar variáveis que ocupam mais do que um byte, em posições consecutivas da memória. Assim, se uma variável ocupa uma palavra de 16 bits, esta variável estará armazenada em dois endereços consecutivos. Há aqui duas convenções possíveis: a) armazenar o byte menos significativo no endereço A+1 e o mais significativo no endereço A, e b) armazenar o byte menos significativo no endereço A e o mais significativo no endereço A+1. A primeira convenção é chamada de “big endien” e é utilizada pelos processadores da Motorola, por exemplo. A segunda alternativa é chamada “little endien” e é utilizada por todos os processadores da linha Intel. A Figura 3 ilustra as duas convenções para armazenar o valor 3456H. Deve-se ainda acrescentar que nos processadores Intel o endereço de umapalavra de dois ou quatro bytes é o endereço do byte menos significativo. Em outras palavras, se uma variável tem comprimento igual a n bytes, e está armazenadas nos endereços A,..., A+n-1 , o endereço desta variável será A. endereço conteúdo endereço conteúdo A A A+1 A+1 “little endien” “big endien” Figura 3: Convenções “little endien” e “big endien”. 3.2 Comandos Existem dois tipos de comandos na linguagem Assembly-86 (ASM86): instruções e diretivas. Linguagem Assembly Família Intel última revisão Ago/2007 5 As instruções correspondem a códigos binários que são as instruções de máquina executadas pelo microprocessador. Enquanto as instruções assembler são simbólicas, as chamadas instruções de máquinas são binárias. A razão para usar instruções Assembly ao invés de instruções binárias é evidente. É muito mais fácil para o programador desenvolver de dar manutenção num programa escrito na forma de símbolos que sugerem a função de cada instrução do que num programa constituído por uma cadeia de 0’s e 1’s. O papel do programa montador (Assembler) é traduzir as instruções Assemby para as instruções de máquina. Ao contrário do que ocorre com as instruções, o montador não gera para as diretivas nenhuma instrução de máquina. As diretivas são informações fornecidas pelo programador que auxiliam o montador no processo de montagem. As instruções podem ter até cinco campos: ((rótulo :) (prefixo) mnemônico (operando(s)) (;comentários)) onde os parênteses denotam que se trata de campos opcionais (os parênteses não são escritos pelo programador). O campo rótulo fornece um nome à posição de memória que contém a instrução, de tal maneira que se pode fazer referência a ela simbolicamente numa instrução de desvio (p. ex.: JMP) em algum outro ponto do programa. Um prefixo leva o montador a gerar um byte de prefixo que modifica de alguma forma a execução normal da instrução. O uso de tais prefixos será melhor esclarecido mais adiante neste texto. O mnemônico identifica o tipo de instrução (p. e. MOV para movimentação, ADD para adição, etc.) que deve ser gerada.. Uma instrução pode ter zero, um ou dois operandos separados por uma vírgula. Os comentários não afetam a execução de um programa, mas constituem meramente um recurso que facilita a compreensão da lógica implementada no programa. É muitíssimo útil durante o desenvolvimento ou posteriormente na manutenção do programa. Recomenda-se enfaticamente que o aluno utilize extensivamente comentários em seus programas. As diretivas podem ter até quatro campos: (nome) mnemônico (operando(s)) (;comentários) Algumas diretivas exigem um nome, enquanto outras proíbem um nome. O montador reconhece a diretiva a partir do mnemônico escrito no segundo campo.Todos os eventuais operandos são escritos em seguida. 3.2.1 Algumas Diretivas A relação de diretivas apresentada nesta seção é apenas adequada aos objetivos deste curso sem ser completa. Outras diretivas serão apresentadas nas Linguagem Assembly Família Intel última revisão Ago/2007 6 aulas de laboratório. 3.2.1.1 Constantes Constantes numéricas podem ser apresentadas em binário, decimal, octal e hexadecimal. A base utilizada deve ser indicada pelo programador colocando à esquerda do numeral, respectivamente as letras B, D, Q e H. Quando nenhuma letra for indicada o montador assume que se trata da base decimal. Muitos montadores exigem que o primeiro dígito de um número em base hexadecimal seja um numeral. Na prática isso significa que os números hexadecimais que iniciam com A, B, C, D, E ou F, devem ser digitados tendo um 0 (zero) na frente. Todas as constantes devem ser inteiras representáveis em 16 bits, incluindo sinal. Números negativos são representados em complemento de dois. Caracteres são apresentados entre apóstrofes e podem aparecer em cadeias de até 255 caracteres, quando utilizadas para iniciar posições de memória. Quando utilizadas como operandos imediatos, caracteres podem ter no máximo dois bytes. A diretiva EQU é utilizada para associar um símbolo a uma constante. O uso de constantes é ilustrado a seguir. MOV STRING[SI],’A ; caracter MOV STRING[SI], 41h ; equivalente em hexadecimal ADD AX, 0C4H ; constantes hexa iniciam com numeral OCTAL_8 EQU 10Q ; 8 em octal OCTAL_9 EQU 11Q ; binário UMS EQU 11111111B ; 9 em octal MENOS_5 EQU -5 ; decimal (default) MENOS_8 EQU -6D ; ‘D’ indica que é decimal 3.2.1.2 Definição de Dados A maioria dos programas iniciam difinindo as variáveis com que eles vão trabalhar. Três diretivas, DB, DW e DD são usadas para alocar e dar nome a posições de memória. São usadas para definir variáveis em três unidades: DB significa “define byte”, DW significa “define word” e DD significa “define doubleword”. Linguagem Assembly Família Intel última revisão Ago/2007 7 Os operandos destas diretivas indicam ao montador quantas unidades de armazenamento alocar - respectivamente 1, 2 e 4 bytes - e qual o valor inicial, se houver. Os exemplos abaixo esclarecem como utilizar estas diretivas: ALFA DB ? ; não inicializada BETA DW ? ; não inicializada GAMA DD ? ; não inicializada UPSILON DW 5 ; constante 05H IOTA DB ‘HELLO’ ; contém 48 45 4C 4C 4F H MU DD 100 DUP 0 ; aloca e inicializa 100 bytes com 0 (zero) Para cada variável num programa ASM86, o montador registra a informação do tipo, ou seja, do número de bytes alocados a ela. Quando uma variável é referenciada no programa, o montador utiliza esta informação para determinar a forma da instrução de máquina que deve ser gerada. Se o uso da variável conflita com o seu atributo, o montador gera uma mensagem de erro. Há casos em que o programador deve indicar explicitamente ao montador qual é o tipo do operando. Por exemplo, a instrução: MOV [BX],5 escreve o valor 5 na posição de memória, cujo endereço está definido pelo conteúdo do registrador BX. Esta instrução não identifica o tipo da variável, se é um byte, uma palavra ou uma palavra dupla. Note, que dependendo do tipo da variável, a execução desta instrução envolverá a modificação de 1, 2 ou 4 bytes da memória. A informação do tipo de variável pode ser fornecido através das diretivas: byte ptr, word ptr, dword ptr, como nos exemplos a seguir MOV word ptr [BX],5 ; indica que é palavra SUB byte ptr [BX][SI],1 ; indica que é byte ANA BETA,32H ; implícito que é palavra CMP byte ptr ALFA[SI], 10 ; indica que é byte (redundante) DEC byte ptr GAMA ; indica que é byte apesar da definição A primeira e segunda linhas do exemplo esclarecem que o primeiro operando da instrução é um byte. Na terceira linha não é necessário utilizar estas diretivas, pois o montador assume o tipo indicado quando da definição da variável, neste caso, uma palavra (vide exemplo anterior). Na quarta linha o uso da diretiva é redundante e, portanto desnecessário, por que a variável já havia sido definida (vide Linguagem Assembly Família Intel última revisão Ago/2007 8 exemplo anterior), como byte. A última linha tem um aspecto interessante. Embora a variável GAMA tenha sido definida como palavra dupla, a instrução decrementa apenas o byte apontado pelo endereço GAMA, sem alterar os demais 3 bytes da palavra dupla. A diretiva TYPE identifica a unidade de alocaçãode memória (1 = byte; 2 = palavra, 4 = palavra dupla). A linguagem provê duas diretivas: LENGHT e SIZE, que podem ser escrito nas instruções com informações de atributo. LENGHT faz o montador retornar com o número de unidades de armazenamento (bytes, palavras ou palavras duplas) ocupadas por um vetor. SIZE leva o montador a retornar o número de bytes ocupados pela variável ou vetor. Estas diretivas permitem que se escrevam seqüências de instruções que não precisam ser mudadas (somente montadas novamente), se os atributos das variáveis mudarem, por exemplo, um vetor de bytes muda para um vetor de palavras. O exemplo a seguir ilustra uma aplicação típica destas diretivas. ; soma do conteúdo de uma tabela em AX ; observe que a mesma instrução funciona para ; TABELA DB 50 DUP(?) ; TABELA DD 50 DUP(?) SUB AX,AX ; zera contador MOV CX, LENGHT TABELA ; número de iterações MOV SI, SIZE TABELA ; aponta p/ o fim do vetor SOMA_OUTRO: SUB SI, TYPE TABELA ; pega elemento anterior ADD AX, TABELA [SI] ; soma um elemento ; AX contém a soma Existe também a diretiva OFFSET. Basta por enquanto considerar que esta diretiva retorna o próprio endereço da variável referenciada no programa logo em seguida a esta diretiva. Do mesmo modo, algumas outras diretivas relacionadas com o mecanismo de segmentação do processador, não serão apresentadas aqui. Embora todo programa Linguagem Assembly Família Intel última revisão Ago/2007 9 deva contê-las para que o montador funcione corretamente, este texto limita-se a mencioná-las sem discutir em detalhes o seu papel. 3.2.1.3 Início e Final de Subrotinas Em ASM86 uma subrotina (rotina) é ativada através da instrução CALL, como se verá adiante. O código da subrotina termina necessariamente com a instrução RET, que desvia o fluxo controle para a instrução seguinte ao CALL. As instruções de uma rotina são emolduradas pelas diretivas PROC e ENDP, como no exemplo a seguir: HISTOGRAMA PROC FAR ; Esta rotina assume que o parâmetro de entrada está em AL, ; e incrementa um contador numa tabela de frequência, ; baseada no valor do parâmetro de entrada. XOR AH,AH ; zera o byte mais significativo de AX MOV SI,AX ; índice para a tabela INC FREQ[SI] ; incrementa o contador RET HISTOGRAMA ENDP O uso da diretiva NEAR/FAR será esclarecido posteriormente. 3.3 Modos de Endereçamento O processador 8086 dispõe de várias formas de referenciar os operandos das instruções. Os operandos podem estar em registradores, dentro da própria instrução, na memória ou em portas de E/S. Além disso, o endereço de memória e de portas de E/S, podem ser calculados de várias maneiras. Estes modos de endereçamento conferem flexibilidade ao conjunto de instruções do processador. Esta seção apresenta uma breve descrição dos modos de endereçamento disponíveis neste processador. 3.3.1 Registrador Neste modo de endereçamento, o operando está contido num dos registradores internos da máquina. Estas instruções são muito compactas pois o “endereço” do registrador está codificado em “uns poucos bits”. O acesso a este operando é realizado inteiramente dentro do próprio processador, e não envolve acesso externo à memória. Exemplos: MOV AL,BL SUB CX,DX DEC CL Linguagem Assembly Família Intel última revisão Ago/2007 10 3.3.2 Imediato O operando está contido na própria instrução. Este operando pode ter 8 ou 16 bits. Exemplos: SUB CL,2 ADD DX,MENOS_5 Note nos exemplos que as instruções utilizam também o modo de endereçamento registrador. De um modo geral, uma mesma instrução pode usar modos de endereçamento diferentes para cada um dos seus operandos. No caso deste processador cada instrução pode ter no máximo dois operandos. Cada operando pode ser referenciado usando um modo de endereçamento distinto. Quando o operando está na memória, o processador calcula o endereço da posição correspondente das formas que serão explicitadas a seguir. Este endereço é chamado de endereço efetivo (effective address - EA). Na realidade o endereço efetivo não é propriamente o endereço do operando, mas a distância entre o endereço da posição de memória e um certo endereço de referência chamado endereço de início de segmento. Por enquanto convém admitir que o endereço efetivo é o próprio endereço do operando. Maiores esclarecimentos serão dados em seções posteriores.1 Figura 4: Ilustração do modo de endereçamento direto Nos modos de endereçamento que serão apresentados a seguir, o endereço efetivo será dado pela soma de até três componentes: base (fornecida por um dos registradores de base - BX ou BP), índice (fornecido pelo conteúdo de um dos registradores de índice - SI ou DI) e um deslocamento (contido na própria instrução). O que caracteriza cada um dos modos de endereçamento a seguir é qual ou quais destes três componentes estarão presentes na soma. 1 Na realidade o endereço efetivo não é propriamente o endereço do operando, mas a distância deste a um certo endereço de referência, chamado endereço de início de segmento. opcode mod reg r/m camento desloc Endereço Efetivo Linguagem Assembly Família Intel última revisão Ago/2007 11 3.3.3 Direto Trata-se do modo de endereçamento de memória mais simples. Não envolve nenhum registrador. O endereço efetivo é tomado diretamente do deslocamento contido na própria instrução. Este modo de endereçamento é tipicamente utilizado para referenciar escalares. A Figura 4 ilustra o formato destas instruções. Alguns exemplos: MOV AX,[8000H] SUB CX,BETA DEC byte ptr GAMA 3.3.4 Registrador Indireto O endereço do operando pode ser tirado de um dos registradores de base (BX ou BP) ou índice (SI ou DI), como mostra a Figura 5. Qual destes registradores é usado está definido num dos campos da instrução. Figura 5: Ilustração do modo de endereçamento registrador indireto. Somente um registrador de base ou de índice pode estar presente neste modo de endereçamento. Os registradores de 8 bits, assim como AX, DX, CX, SP, além dos registradores de segmento (CS, DS, ES, SS) são proibidos aqui, a menos de raras exceções. Exemplos: MOV AL,[BX] SUB [SI],DX Convém, contudo, notar que qualquer registrador de uso geral pode ser utilizado neste modo de endereçamento nas instruções do tipo JMP e CALL. Exemplos destes casos são: JMP [AX] CALL [DX] opcode mod reg r/m Endereço Efetivo BX ou BP ou SI ou DI Linguagem Assembly Família Intel última revisão Ago/2007 12 3.3.5 Relativo a uma Base Neste modo de endereçamento, o endereço efetivo é dado pela soma de um deslocamento (definido na própria instrução) e pelo conteúdo de um registrador de base, mais especificamente, do registrador BX ou do registrador BP. Figura 6: Ilustração do modo de endereçamento com base. Este modo de endereçamento é freqüentemente utilizado para referenciar estruturas que podem estar armazenadas em endereços diferentes na memória. Antes da instrução que referencia o dado propriamente dita normalmente se introduzem instruções que carregam no registrador de base o endereço inicial da estrutura. O elemento da estrutura é indicado pelo deslocamento. Cópias diferentes deuma mesma estrutura podem ser referenciadas simplesmente mudando o conteúdo do registrador de base. Abaixo são dados alguns exemplos deste modo de endereçamento. MOV AL,[BX+0B000H] SUB CL,MU[BX] ADD dword ptr [BP][1000H], 43H 3.3.6 Indexado No modo de endereçamento indexado o endereço efetivo é calculado a partir da soma de um deslocamento mais o conteúdo de um registrador de índice (SI ou DI, conforme definido no campo mod_reg_r/m), como mostra a Figura 7. Este modo de endereçamento é freqüentemente utilizado para referenciar elementos de um vetor: o deslocamento apontando para o início do vetor e o valor do registrador de índice selecionando um dos seus elementos. Como todos os elementos de um vetor têm o mesmo comprimento, uma aritmética simples com o registrador de índice selecionará qualquer elemento. opcode mod reg r/m camento desloc Endereço Efetivo BX ou BP + Linguagem Assembly Família Intel última revisão Ago/2007 13 Figura 7: Ilustração do modo de endereçamento Indexado. Abaixo são dados alguns exemplos deste modo de endereçamento. MOV AL,[SI+0B000H] SUB CL,MU[SI] ADD word ptr [DI][1000H], 43H 3.3.7 Indexado com Base O modo de endereçamento indexado com base gera o endereço efetivo somando o conteúdo de um registrador de base (BP ou BX), o conteúdo de um registrador de índice (SI ou DI) e um deslocamento, como mostra a figura 8. É importante salientar aqui que neste modo de endereçamento está presente um registrador de base e um registrador de índice. Qualquer outra combinação de registradores é inválida. Este modo é freqüentemente utilizado para referenciar vetores localizados na pilha, como será discutido adiante. Novamente neste caso o campo mod_reg_r/m da instrução define quais os registradores participam do cálculo do endereço efetivo. Abaixo são dados alguns exemplos deste modo de endereçamento. MOV AL,[BX+SI+ 5H] ADD [BP][DI][1000H], AX opcode mod reg r/m camento desloc Endereço Efetivo SI ou DI + Linguagem Assembly Família Intel última revisão Ago/2007 14 Figura 8: Ilustração do modo de endereçamento indexado com base. 3.4 Conjunto de Instruções do 8086/8088 A apresentação que se segue não contém todos os detalhes de cada instrução. Ela se propõe a ser um esquema a ser seguido em aula e ao mesmo tempo um resumo do assunto. É importante ter em mente que todas estas instruções estão presentes no conjunto de instruções dos demais processadores da família 80X86, de tal sorte que o conhecimento aqui adquirido pode ser utilizado na programação de sistemas mais modernos. 3.4.1 Convenções Na apresentação a seguir adota-se a seguinte convenção: d - uma quantidade de 8 ou 16 bits. d8 - uma quantidade de 8 bits. d16 - uma quantidade de 16 bits. r, r1, r2 - um registrador de 8 ou 16 bits de uso geral. r8 - um registrador de 8 bits. r16 - um registrador de 16 bits. ( ) - o conteúdo de um registrador, ou posição de memória, dependendo do que estiver entre os parênteses. m - endereço de um byte ou uma palavra na memória opcode mod reg r/m camento desloc Endereço Efet-ivo BX ou BP + SI ou DI Linguagem Assembly Família Intel última revisão Ago/2007 15 m8 - endereço de um byte na memória m16 - endereço de uma palavra de 16 bits. m32 - endereço de uma palavra dupla de 32 bits. AX/AL - o registrador AX ou AL, dependendo do operando envolvido ser de 16 ou 8 bits. alvo - a definição de um endereço na área de código da memória. flags - os 16 bits do registrador de estado. r1:r2 - indica um valor formado pela composição do conteúdo de r1 e r2. Esta composição pode se dar pela concatenação dos dois valores (p. ex. DX:AX) ou pelo modo próprio do microprocessador formar o endereço (p. ex. CS:DX). As convenções utilizadas na descrição que se segue, acabará se tornando clara na medida em que as instruções forem sendo apresentadas. Para cada tipo de instrução aparece do lado esquerdo uma descrição sintática e uma breve descrição semântica da instrução. Ao centro, são dados alguns exemplos, e do lado esquerdo o número de ciclos de relógio consumidos na execução da instrução. 3.4.2 Instruções de Transferência de Dados MOV r1,r2 MOV AL,CH 2 MOV CX,BP MOV r,m MOV AX,TEMP_RESULT 10/8+EA MOV CL,ARRAY[SI], MOV m,r MOV COUNT[DI],CX 10/9+EA MOV [SI+1000],DX MOV r,d MOV AX,99 4 4 MOV SI,61CH MOV m,d MOV byte ptr [SI], 0FH 10+EA MOV word ptr [BX+1234],57 XCHG r1,r2 XCHG AX,BX 3/4 XCHG CL,CH XCHG m,r XCHG SEMAFORO,AX 17+EA XCHG AL,[BX+2] XLAT XLAT 11 (AL) ← ((BX+AL)) Linguagem Assembly Família Intel última revisão Ago/2007 16 LAHF LAHF 4 (AH7,6,4,2,0)← (SF,ZF,AF,PF,CF) SAHF SAHF 4 (SF,ZF,AF,PF,CF) ←(AH7,6,4,2,0) 3.4.3 Instruções de Manipulação de Cadeias de Caracteres (string) Cada uma das instruções deste grupo tem três formatos diferentes. Para o primeiro formato (terminado com um "S"), o montador entende que o string é de bytes ou de palavras. O segundo (terminado com um "SB") e terceiro (terminado com um "SW") indicam explicitamente que se trata respectivamente de um string de bytes e palavras. MOVS/MOVSB/MOVSW MOVSB 18 MOVSW CMPS/CMPSB/CMPSW CMPSB 22 CMPSW SCAS/SCASB/SCASW SCASB 15 SCASW LODS/LODSB/LODSW LODSB 12 LODSW STOS/STOSB/STOSW STOSB 11 STOSW REP REP MOVSB 9+17*rep REP REP LODSW 9+13*rep REP REP STOSW 9+10*rep REPZ/REPE REPZ CMPS 9+22*rep REPNZ/REPNE REPNZ SCAS 9+15*rep Nas cinco últimas linhas da tabela contém prefixos usados para repetir a Linguagem Assembly Família Intel última revisão Ago/2007 17 execução da instrução de manipulação de string escrita em seguida pelo número de vezes especificado em CX. Diante das instruções MOVS/MOVSB/MOVSW, LODS/LODSB/LODSW e STOS/STOSB/STOSW, pode-se usar o prefixo REP, que decrementa o registrador CX, sem afetar os flags, e repete a instrução de string, enquanto CX ≠ 0. Diante das instruções CMPS e SCAS, pode-se usar os prefixos REPZ(ou REPE) ou REPNZ(ou REPNE). REPZ decrementa CX, sem afetar os flags, e repete a instrução de string, enquanto CX ≠ 0 e ZF = 1. O prefixo REPNZ decrementa CX, sem afetar os flags, e provoca a repetição da instrução de string, enquanto CX ≠ 0 e ZF = 0. 3.4.4 Instruções de Manipulação de Endereços LEA r16,m16 LEA BX,[BP][DI] 2+EA (r16) ← m16 LDS r16,m32 LDS SI,DATA_SEG[DI] 16+EA (r16) ← (m32) (DS) ← (m32)+2 LES r16,m32 LES SI,DATA_SEG[DI] 16+EA (r16) ← (m32) (ES) ← (m32)+2 3.4.5 Instruções Aritméticas 3.4.5.1 Adição ADD r1,r2 ADD AX,SI 3 ADD CL,DH ADD r,m ADD DL,[300] 9+EA ADD CX,[BX+1234H] ADD m,r ADD byte ptr [300],DL 16+EA ADD word ptr [SI+BX+2],AX ADD r,d ADD AL,4 4 (r) ← (r) + d ADD AX,50 ADD m,d ADD byte ptr [DI],54 17+EA (m) ← (m) + d ADD word ptr [BX+4],4FFH Linguagem Assembly Família Intel última revisão Ago/2007 18 ADC r1,r2 ADCBX,DX 3 ADC DH,AL ADC r,m ADC BL,[456] 9+EA (r) ←(r) + (m) + (CF) ADC CX,word ptr [300H] ADC m,r ADC byte ptr [300],DL 16+EA (m) ←(m) + (r) + (CF) ADC word ptr [SI],BX ADC r,d ADC CL,24 4 (r) ← (r) + d + (CF) ADC BX,50 ADC m,d ADC byte ptr [DI],54 17+EA (m) ← (m) + d + (CF) ADC word ptr [BX+4],4FFH INC r INC AX 2 (r) ←(r) + 1 INC CL 3 INC m 15+EA (m) ←(m) + 1 INC word ptr [BX+100H] 3.4.5.2 Subtração SUB r1,r2 SUB AX,SI 3 SUB CL,DH SUB r,m SUB DL,[300] 9+EA (r) ← (r) - (m) SUB CX,[BX+1234H] SUB m,r SUB byte ptr [300],DL 16+EA (m) ← (m) - (r) SUB word ptr [SI+BX+2],AX SUB r,d SUB AL,4 4 4 (r) ← (r) - d SUB AX,50 SUB m,d SUB byte ptr [DI],54 17+EA (m) ← (m) - d SUB word ptr [BX+4],4FFH SBB r1,r2 SBB BX,DX 3 (r1) ←(r1) - (r2) - (CF) SBB DH,AL Linguagem Assembly Família Intel última revisão Ago/2007 19 SBB r,m SBB BL,[456] 9+EA SBB CX,word ptr [300H] SBB m,r SBB byte ptr [300],DL 16+EA (m) ←(m) - (r) - (CF) SBB word ptr [SI],BX SBB r,d SBB CL,24 4 (r) ←(r) - d - (CF) SBB BX,50 SBB m,d SBB byte ptr [DI],54 17+EA (m) ← (m) - d - (CF) SBB word ptr [BX+4],4FFH DEC r DEC AX 2 (r) ←(r) - 1 DEC Cl 3 DEC m DEC byte ptr [SI] 15+EA DEC word ptr [BX+100H] NEG r NEG BX 3 NEG AL NEG m NEG byte ptr [BX+DI] 16+EA (m) ←0 - (m) NEG MULTIPLICADOR CMP r1,r2 CMP BX,CX 3 CMP AL.BL CMP r,m CMP DH, byte ptr ALPHA 9+EA (r) - (m) CMP CH,[SI+BX+10] CMP m,r CMP [BP+2],SI 9+EA (m) - (r) CMP [SI],AX CMP r,d CMP BL,02H 4 (r) - d CMP DL,85H CMP m,d CMP RADAR[DI],3420H 10+EA (m) - d Linguagem Assembly Família Intel última revisão Ago/2007 20 3.4.5.3 Multiplicação MUL r MUL BL 70-77 MUL CX 118-133 MUL m MUL byte ptr MES[SI] (76-83)+EA (AX) ← (AL) * (m8) MUL word ptr DIA[SI] (124-139)+EA (DX:AX) ← (AX) * (m16) MUL BAUD_RATE (sem sinal) IMUL r IMUL BL 80-98 (AX) ← (AL) * (r8) IMUL CX 128-154 (DX:AX) ← (AX) * r16) (com sinal) IMUL m IMUL byte ptr MES[SI] (86-104)+EA (AX) ← (AL) * (m8) IMUL word ptr DIA[SI] (134-160)+EA (DX:AX) ← (AX) * (m16) (com sinal) 3.4.5.4 Divisão DIV r8 DIV CL 80-90 DIV r16 DIV DX 144-162 (sem sinal) Linguagem Assembly Família Intel última revisão Ago/2007 21 DIV m8 DIV byte ptr [SI] (88-96)+EA (sem sinal) DIV m16 DIV word ptr [BX] (150-168)+EA (AX)←(DX:AX)/(m16) (DX)←(DX:AX)mod(m16) (sem sinal) IDIV r8 IDIV CL 101-112 (AL)←(AX)/(r8) (AH)←(AX) mod (r8) (com sinal) IDIV r16 IDIV DX 165-184 (AX)←(DX:AX)/(r16) (DX)←(DX:AX) mod (r16) (com sinal) IDIV m8 IDIV byte ptr [SI] (107-118)+EA (AL)←(AX)/(m8) (AH)←(AX) mod (m8) (com sinal) IDIV m16 IDIV word ptr [BX] (171-190)+EA (AX)←(DX:AX)/(m16) (DX)←(DX:AX)mod(m16) (com sinal) CBW CBW 2 Se (AL)<0, então (AH) ←0FF caso contrário, (AH) ←00 Linguagem Assembly Família Intel última revisão Ago/2007 22 CWD CWD 5 Se (AX)<0, então (DX) ← FFFFH caso contrário, (DX) ← 0 3.4.5.5 nstruções de Ajuste Estas instruções são utilizadas quando os dados manipulados pelo programa estão em formato ASCII ou BCD. Tendo em vista que estas são instruções de uso infreqüente, este trabalho limita-se a mencioná-las. São elas: AAA AAA 4 DAA DAA 4 AAS AAS 4 DAS DAS 4 AAM AAM 83 AAD AAD 60 3.4.6 Instruções Lógicas 3.4.6.1 E AND r1,r2 AND AL,BL 3 AND CL,AH AND r,m AND DL,[SI+5] 9+EA AND CX,FLAG_WORD AND m,r AND ASCII[DI],DL 16+EA (m) ← (m) • (r) AND word ptr [SI+BX+2],AX AND r,d AND AX,7F7FH 4 (r) ← (r) • d AND CX,0F0H AND m,d AND BETA,01H 17+EA (m) ← (m) • d AND byte ptr [BX],6 TEST r1,r2 TEST SI,DI 3 TEST CX,DX Linguagem Assembly Família Intel última revisão Ago/2007 23 TEST r,m TEST SI,END_COUNT 9+EA (r) • (m) TEST CX,[SI] TEST r,d TEST BX,0CC4H 4/5 (r) • d TEST AL,8 TEST m,d TEST RETURN_CODE,01H 11+EA (m) • d TEST byte ptr [DI],3 3.4.6.2 OU OR r1,r2 OR AL,BL 3 OR CL,AH OR r,m OR DL,[SI+5] 9+EA OR CX,FLAG_WORD OR m,r OR ASCII[DI],DL 16+EA (m) ← (m) + (r) OR word ptr [SI+BX+2],AX OR r,d OR AX,7F7FH 4 (r) ← (r) + d OR CX,0F0H OR m,d OR BETA,01H 17+EA (m) ← (m) + d OR byte ptr [BX],6 3.4.6.3 OU exclusivo XOR r1,r2 XOR AL,BL 3 XOR CL,AH XOR r,m XOR DL,[SI+5] 9+EA XOR CX,FLAG_WORD XOR m,r XOR ASCII[DI],DL 16+EA (m) ← (m) ⊕ (r) XOR word ptr [SI+BX+2],AX XOR r,d XOR AX,7F7FH 4 (r) ← (r) ⊕ d XOR CX,0F0H Linguagem Assembly Família Intel última revisão Ago/2007 24 XOR m,d XOR BETA,01H 17+EA (m) ← (m) ⊕ d XOR byte ptr [BX],6 3.4.6.4 Complemento NOT r NOT AX 3 NOT CL NOT m NOT byte ptr [BX] 16+EA (m)← not (m) NOT word ptr [BX+SI] 3.4.6.5 Deslocamento SAL/SHL r,1 SAL AL,1 2 SAL/SHL r,CL SHL DX,CL 8+4*(CL) SAL/SHL m,1 SAL word ptr [BX+2],1 15+EA SAL/SHL m,CL SHL STORE_COUNT,CL 20+EA+4*(CL) CF tantas vezes quantas forem definidas pelo segundo operando SHR r,1 SHR AL,1 2 SHR r,CL SHR DX,CL 8+4*(CL) SHR m,1 SHR word ptr [BX+2],1 15+EA SHR m,CL SHR STORE_COUNT,CL 0+EA+4*(CL ) CF tantas vezes quantas forem definidas pelo segundo operando Linguagem Assembly Família Intel última revisão Ago/2007 25 SAR r,1 SAR AL,1 2 SAR r,CL SAR DX,CL 8+4*(CL) SAR m,1 SAR word ptr [BX+2],1 15+EA SAR m,CL SAR STORE_COUNT,CL 20+EA+4*(CL) CF 3.4.6.6 Rotação ROL r,1 ROL AL,1 2 ROL r,CL ROL DX,CL 8+4*(CL) ROL m,1 ROL word ptr [BX+2],1 15+EA ROL m,CL ROL STORE_COUNT,CL 0+EA+4*(CL ) CF tantas vezes quantas forem definidas pelo segundo operando ROR r,1 ROR AL,1 2 ROR r,CL ROR DX,CL 8+4*(CL) ROR m,1 ROR word ptr [BX+2],1 15+EA ROR m,CL ROR STORE_COUNT,CL 20+EA+4*(CL) CF tantas vezes quantas forem definidas pelo segundo operando Linguagem Assembly Família Intel última revisão Ago/2007 26 RCL r,1 RCL AL,1 2 RCL r,CL RCL DX,CL 8+4*(CL) RCL m,1 RCL word ptr [BX+2],1 15+EA RCL m,CL RCL STORE_COUNT,CL 20+EA+4*(CL) CF tantas vezes quantas forem definidas pelo segundo operando RCR r,1 RCR AL,1 2 RCR r,CL RCR DX,CL 8+4*(CL) RCR m,1 RCR word ptr [BX+2],1 15+EA RCR m,CL RCR STORE_COUNT,CL 20+EA+4*(CL) CF tantas vezes quantas forem definidas pelo segundo operando 3.4.7 Instruções de Desvio 3.4.7.1 Desvio incondicional JMP CX (CS:IP) ← ‘alvo’ JMP dword ptr [SI] CALL CX (CS:IP) ← ‘alvo’. O endereço de retorno é armazenado na pilha. CALL dword ptr [SI] RET (CS:IP)←valor armazenado no topo da pilha Linguagem Assembly Família Intel última revisão Ago/200727 RET 8 (CS:IP)←valor armazenado no topo da pilha. (SP)←(SP)+d16 INT num_vetor INT 21H 51 (esclarecido noutro capítulo) INTO INTO 53 ou 4 (esclarecido noutro capítulo) IRET IRET (esclarecido noutro capítulo) 3.4.7.2 Desvio condicional instrução descrição da condição de desvio JA / JNBE Jump if above/not below nor equal JAE / JNB Jump if above or equal/not below JB / JNAE Jump if below/not above JBE / JNA Jump if below or equal / not above JC Jump if carry JE / JZ Jump if equal / zero JG / JNLE Jump if greater / not less nor equal JGE / JNL Jump if greater or equal / not less JLE / JNG Jump if less or equal/ not greather JNC Jump if not carry JNE / JNZ Jump if not equal / not zero JNO Jump if not overflow JNP / JPO Jump if not parit / parity odd JNS Jump if not sign JO Jump if overflow JP / JPE Jump if parity / parity even JS Jump if sign Tabela 1: Instruções de desvio condicional Linguagem Assembly Família Intel última revisão Ago/2007 28 JA DESTINO 16 ou 4 Como JMP ‘alvo’, desde que ‘cond’ seja satisfeita. JC CX 3.4.8 Controle de Iteração LOOP LABEL 16 ou 5 Se (CX) ≠ 0, (IP)←alvo LOOPE/LOOPZ alvo LOOPE LABEL 18 ou 6 (CX) ← (CX) -1 LOOPZ LABEL Se (CX) ≠ 0 e, (ZF) =0 então (IP) ← alvo LOOPNE/LOOPNZ alvo LOOPE LABEL 19 ou 5 (CX) ← (CX) -1 LOOPZ LABEL Se (CX) ≠ 0 e, (ZF) ) =0 então (IP) ← alvo JCXZ alvo JCXZ ROTINA 18 ou 6 Se (CX)=0, então (IP) ← alvo 3.4.9 Manipulação da Pilha PUSH r16 PUSH SI 11(reg) ou 10 (seg-reg) Linguagem Assembly Família Intel última revisão Ago/2007 29 PUSH m16 PUSH RETURN_CODE[SI] 16+EA PUSHF PUSHF 10 (SP) ←(SP) -2 ((SP)) ←flags POP r16 POP SI 8 POP m16 POP PARAMETRO 17+EA (m16) ← ((SP)) (SP) ← (SP) + 2 POPF POPF 8 (flags) ← ((SP)) (SP) ← (SP) + 2 3.4.10 Operações sobre ‘flags’ CLC 2 (CF) ← 00 CMC 2 (CF) ← (CF) STC 2 (CF) ←00 CLD CLD 2 (DF) ← 0 STD STD 2 (DF) ← 1 Linguagem Assembly Família Intel última revisão Ago/2007 30 CLI CLI 2 (IF) ← 0 STI STI 2 (IF) ← 1 3.4.11 Instruções de Entrada e Saída IN AL,d8 IN AL,3FH 10 IN AL,00H IN AL,DX IN AL,DX 8 IN AX,DX IN AX,DX OUT d8,AL OUT 24H,AL 10 OUT DX,AX OUT DX,AX 8 OUT DX,AL OUT DX,AL 3.4.12 "NO OPERATION" NOP NOP 3 não realiza nenhuma operação 3.4.13 Outras Instruções Para tornar esta relação completa, serão mencionadas instruções, cuja compreensão demanda conhecimento ainda não disponíveis ao estudante nesta altura do curso. Por este motivo não será apresentada uma descrição destas instruções. São elas: ESC, HLT, LOCK, WAIT. Linguagem Assembly Família Intel última revisão Ago/2007 31 4 Subrotinas A Figura 9 ilustra algumas técnicas que são utilizadas para escrever subrotinas em Assembly 86. Neste exemplo um programa chama uma subrotina (chamada EXEMPLO) passando a ela um vetor de bytes. Dois parâmetros são passados na pilha: o primeiro contém o número de elementos do vetor, e o segundo contém o endereço do primeiro elemento do vetor. Desta forma, embora a subrotina receba sempre dois parâmetros, eles podem ser usados para referenciar indiretamente qualquer número de variáveis na memória. Os resultados que a rotina retorna devem estar em registradores ou em memória, mas não podem estar na pilha. AX ou AL são freqüentemente utilizados para conter o resultado de uma rotina quando se trata de um byte ou palavra. Este é comumente o caso das chamadas functions. Alternativamente o programa principal pode passar à subrotina como parâmetro o endereço (ou endereços) de uma área da memória onde deve ser escrito o resultado. Cada linguagem de programação de alto nível tem uma convenção diferente para passagem de parâmetros na pilha. A convenção aqui apresentada é, portanto, apenas um exemplo. A Figura 10a mostra como está a pilha antes do programa principal armazenar os parâmetros na pilha. A Figura 10b mostra a pilha como a subrotina a vê, logo após a execução da instrução CALL. A rotina EXEMPLO pode ser dividida em quatro seções. o prólogo, em que o reigstrador SP é preparado para ser utilizado como registrador de base nos acessos aos dados contidos na pilha. O próximo passo no prólogo é salvar o estado da máquina. Isto é feito armazenando na pilha todos os registradores alterados pela subrotina (no caso, apenas CX e BX). Se a subrotina modifica os flags, e o programa principal conta com que os flags estejam inalterados após a execução da subrotina, o registrador de estado também deve ser armazenado na pilha. A última instrução do prólogo aloca três palavras na pilha para serem usadas como variáveis locais. A Figura 10c mostra a pilha após o prólogo. O conjunto de palavras alocadas na pilha para esta rotina é chamado de “quadro” (“frame”) da rotina. O corpo da rotina faz o processamento propriamente dito, que, em EXEMPLO, corresponde a nenhum processamento. Os parâmetros na pilha são endereçados relativamente ao registrador BP, como mostra a Figura 10. Linguagem Assembly Família Intel última revisão Ago/2007 32 Figura 9: Exemplo do uso de passagem de parâmetros e criação de variáveis locais pela pilha. EX_SEG SEGMENT VETOR DB 10 DUP(?) ASSUME CS:EX_SEG,DS:EX_SEG;SS:EX_SEG;ES:NOTHING EXEMPLO PROC NEAR ;Prólogo PUSH BP ; salva BP MOV BP,SP ; estabelece base PUSH CX ; salva estado PUSH BX ; “ “ PUSHF ; e flags SUB SP,6 ; aloca espaço para variáveis locais ; fim do prólogo ; Corpo MOV CX,[BP+6] ; pega nº de elementos MOV BX,[BP+4] ; pega endereço inicial do vetor ; variáveis locais estão em [BP-8], [BP-10] e [BP-12] ; fim do corpo ;Epílogo ADD SP,6 ; desaloca variáveis locais POPF ; restaura flags POP BX ; restaura estado POP CX ; “ “ POP BP ; “ “ ; fim do epílogo ;Retorno da subrotina RET 4 EXEMPLO ENDP ; Programa principal START: MOV AX, SIZE VETOR PUSH AX MOV AX,OFFSET VETOR PUSH AX CALL EXEMPLO EX_SEG ENDS END START Linguagem Assembly Família Intel última revisão Ago/2007 33 O epílogo desaloca o espaço das variáveis locais e restaura o valor antigo do estado da máquina. A Figura 10d mostra a configuração na pilha logo após o epílogo, pouco antes da instrução de retorno. A última seção da rotina é o retorno: instrução RET 4, além de restaurar o antigo valor de IP, descarta os parâmetros de entrada, acrescentado 4 ao conteúdo de SP. endereço baixo → Endereço alto → Í SP (a) (b) ( c) (d) Figura 10: Condição da pilha: (a) antes do programa principal armazenar os parâmetros; (b) após a instrução CALL; ( c) após o prólogo;(d) após o epílogo (antes de RET) 5 Codificação das Instruções de Máquina do 8086/8088 Esta seção fornece uma idéia de como as instruções codificadas em linguagem Assembly são traduzidas para as instruções de máquina. O texto apresentado a seguir é praticamente uma tradução de parte do “The 8086 Family User’s Manual”. A instrução em ASM-86 "MOV" pode assumir 28 formas diferentes. Um programador raramente precisa conhecer os detalhes da codificação das instruções para escrever programas. Quando se deseja, por exemplo, depurar um programa a partir de informações obtidas pela monitoração das barras do sistema através de algum aparelho qualquer. Nesta seção são apresentadas as informações necessárias para traduzir ou decodificar uma instrução do 8086/8088. O esquema aqui apresentado é o mesmo dos processadores posteriores da família (80286, 80386, 80486, Pentium etc.), a menos de algumas extensões que foram incorporadas às novas versões. Uma instrução de máquina pode ocupar entre 1 e 6 bytes. Instruções de 1 byte normalmente operam sobre um único registrador ou sinalizador e são facilmente Linguagem Assembly Família Intel última revisão Ago/2007 34 identificáveis. Nas instruções mais longas são os primeiros dois bytes que contém a chave para a decodificação. O formato destes bytes varia mas a maioria das intruções segue o formato apontado na Figura 11 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 Figura 11: Formato típico de uma instrução de máquina do 8086/8088 Os primeiros seis bits de uma instrução de mais de 1 byte geralmente contém o código da operação ("opcode") que identifica o tipo básico da instrução: ADD, XOR, etc. O bit seguinte, o campo D, geralmente especifica a direção da operação: 1 → o campo REG no segundo byte identifica o operando de destino, 0 → o campo REG identifica o operando fonte. O campo W distingui entre operações sobre 8 bits ou 16 bits: 0 = byte, 1 = palavra de 16 bits. Um entre três campos de um bit, S, V, ou, Z aparece no formato de algumas instruções. S é usado em conjunto com W para indicar a extensão do campo imediato nas instruções aritméticas. V distingui entre deslocamentos e rotações de uma ou várias posições. Z é usado como bit de comparação com o indicador ZF (zero) nas instruções de repetição e iterações condicionais. A Tabela 2 mostra um resumo do significado associado a cada um destes campos de um bit. O segundo byte da instrução usualmente identifica os operandos da instrução. O campo MOD indica, se um dos operandos está na memória ou, se ambos os operandos são registradores (veja Tabela 3). O campo REG identifica o registrador que conte um dos operandos. Sua codificação é igual à no campo R/M para MOD=11 (3 primeiras colunas da Tabela 3 Em várias instruções, principalmente as do tipo "operando imediato para a memória", REG é usado como uma extensão do "opcode" para identificar o tipo de operação. A codificação do campo R/M (registrador/memória) depende do conteúdo Linguagem Assembly Família Intel última revisão Ago/2007 35 do campo MOD. Se MOD = 11 (modo registrador-registrador), R/M identifica o segundo registrador operando. Se MOD seleciona o modo memória, então o campo R/M indica como deve ser calculado o endereço efetivo do operando. campo valor função S 0 1 nenhuma estensão do sinal sinal do valor de 8 bits é extendido se W=1 W 0 1 a instrução opera sobre byte a instrução opera sobre palavra de 16 bits D 0 1 o campo REG identifica o operando fonte o campo REG identifica o operando destino V 0 1 deslocamento/rotação de uma posição deslocamento/rotação especificado em CL Z 0 1 repetição/iteração, enquanto ZF = 0 repetição/iteração, enquanto ZF = 1 Tabela 2: Codificação dos campos de um bit Os bytes 3 a 6 de uma instrução, quando estão presentes, contém usualmente o valor do deslocamento do operando na memória e/ou o valor de um operando imediato. Pode haver um ou dois bytes contendo o deslocamento do endereço efetivo do operando. O campo MOD indica quantos campos deslocamento estão presentes. Seguindo a convenção da Intel, "little endian", se o deslocamento contém dois bytes, o byte mais significativo é armazenado em segundo lugar na instrução. Se o deslocamento é apenas um único byte, o 8086/8088 estende este valor internamente mantendo o sinal, para um valor de 16 bits, para então utilizá-lo nos cálculos de endereço. Valores imediatos sempre vêm depois dos valores de deslocamento, quando presentes. O segundo byte de um valor imediato de dois bytes é o mais significativo. O data sheet deste processador possui tabelas com a decodificação de cada instrução, baseada na descrição apresentada. MOD=11 Cálculo do Endereço Efetivo R/M W=0 W=1 MOD=00 MOD=01 MOD=10 000 AL AX (BX)+(SI) (BX)+(SI)+d8 (BX)+(SI)+d16 001 CL CX (BX)+(DI) (BX)+(DI) +d8 (BX)+(DI) +d16 010 DL DX (BP)+(SI) (BP)+(SI) +d8 (BP)+(SI) +d16 011 BL BX (BP)+(DI) (BP)+(DI) +d8 (BP)+(DI) +d16 100 AH SP (SI) (SI) +d8 (SI) +d16 101 CH BP (DI) (DI) +d8 (DI) +d16 110 DH SI end. direto (BP)+d8 (BP)+d16 111 BH DI (BX) (BX) +d8 (BX) +d16 Tabela 3: Codificação dos modos de endereçamento do 8086/8088 Linguagem Assembly Família Intel última revisão Ago/2007 36 6 Formação do Endereço no 8086/8088 No 8086/88 o conteúdo do registrador apontador de programa - IP - não é propriamente o endereço da próxima instrução a ser executada, mas a diferença ("offset") entre o endereço da próxima instrução a ser executada e um determinado endereço inicial, conforme ilustra a Figura 12. Figura 12: Relação entre o endereço lógico e físico no 8086/88 De modo semelhante, o endereço efetivo de um operando calculado pela EU do 8086/88 não é o endereço físico da posição de memória que contém o operando, mas a diferença ("offset") entre o endereço da posição de memória que contém o operando e um determinado endereço inicial. A este endereço inicial dá-se o nome de endereço de início de segmento ou endereço de base. Tanto num caso como no outro, o endereço de início de segmento está em um dos registradores de segmento (CS, DS, SS ou ES). O endereço efetivo calculado pela EU, bem como o conteúdo do apontador de programa - IP, são valores de 16 bits. Isto implica em que, usando um mesmo endereço de início de segmento pode-se endereçar até 64 Kbytes de memória. A área contígua de memória endereçada pelo 8086/88, ao executar um programa, utilizando um mesmo endereço de início de segmento é chamada segmento. A Figura 13 mostra como é formado o endereço físico das instruções no 8086/88. O endereço de início de segmento é, no caso de instruções, sempre definido pelo conteúdo do registrador CS ("code segment register") e o "offset" pelo conteúdo do registrador IP ("instruction pointer"). Para formar o endereço da instrução, o 8086/88 multiplica o conteúdo do registrador de segmento CS por 16D e adiciona o resultado ao conteúdo do registrador IP. Note que a multiplicação por 16D tem o mesmo efeito de deslocar o conteúdo de CS 4 bits à esquerda, preenchendo os 4 bits menos significativos com "0s". Linguagem Assembly Família Intel última revisão Ago/2007 37Figura 13: Formação do endereço físico no 8086/88 para acesso a código. No caso dos endereços de operandos, o microprocessador busca o endereço de início de segmento em qualquer dos registradores de segmento (CS, DS, SS e ES). Já o "offset" dos endereços de operandos pode envolver até 3 componentes: • Base, • Índice, e • Deslocamento A Figura 14 ilustra como o 8086/88 calcula o "offset" e como é formado o endereço físico dos operandos. Os fatores base, índice e deslocamento podem ser contabilizados ou não para o cálculo do "offset" dependendo do modo de endereçamento utilizado pela instrução executada. Uma questão a ser ainda esclarecida é: num acesso a um operando qual dos registradores de segmento deve ser usado para determinar o endereço de início de segmento? De um modo geral vale a seguinte regra: Linguagem Assembly Família Intel última revisão Ago/2007 38 Figura 14: Formação do endereço no 8086/800 (a) para acesso a código, (b) para acesso a operando. • para cesso a código usa-se sempre o registrador CS, • para acesso a dados fora da pilha usa-se normalmente DS , • para acesso a dados na pilha usa-se SS , o conteúdo do registrador BP é usado para compor o endereço efetivo do operando, o registrador de segmento é o SS Existem instruções, no entanto, que utilizam o registrador ES ao invés do registrador DS para acessar dados. As instruções de manipulação de "strings" implicam no uso do registrador ES na formação do endereço efetivo do operando nos acessos de escrita. Desta forma a instrução MOVS transfere o byte ou palavra contida no endereço (DS:SI) para o endereço (ES:DI). De modo semelhante a instrução CMPS compara o byte ou palavra contido no endereço (DS:SI) com o byte ou palavra do endereço (ES:DI). Também a instrução SCAS compara o conteúdo do acumulador (registrador AL ou AX) com o conteúdo do endereço definido por (ES:DI). Linguagem Assembly Família Intel última revisão Ago/2007 39 Além disso, é possível forçar o microprocessador a utilizar no cálculo do endereço efetivo um registrador de segmento diferente do previsto pela regra acima ("default"). Nestes casos a instrução recebe um prefixo que impõe a utilizarão de um registrador de segmento distinto do definido por essas regras. A instrução código instrução clocks 88 9C 00 40 9+EA por exemplo, move o conteúdo da posição de memória, cujo endereço efetivo é dado pelo conteúdo do registrador DS ("default" neste caso) deslocado 4 bits à esquerda, adicionado ao valor do deslocamento, que neste caso é o endereço do primeiro byte do vetor ARRAY, adicionado ainda ao conteúdo do registrador de índice SI, para o registrador BL. Se, ao invés de utilizar o registrador de segmento "default", se deseja utilizar, digamos, o registrador ES, a instrução em Assembly toma a seguinte forma: código instrução clocks 26 88 9C 00 40 9+EA Há casos, no entanto, em que não se pode utilizar outro registrador de segmento diferente do "default". Por exemplo, nos acessos a instrução o registrador CS é sempre utilizado. Da mesma forma, quando se acessa a pilha utilizando o registrador apontador de pilha - SP - necessariamente se utiliza o registrador SS como registrador de segmento. O conteúdo dos registradores de segmento é normalmente definido no início da execução do programa, utilizando instruções apropriadas. Nada impede porém que o conteúdo dos registradores de segmento seja alterado várias vezes ao longo do programa. A grande vantagem do uso destes registradores de segmento é que tanto os códigos como os dados e a pilha podem ser carregados em qualquer região da memória, sem que seja necessário modificar o código objeto. Basta carregar os registradores de segmento de modo adequado. O sistema operacional de um microcomputador pessoal, por exemplo, antes de carregar um programa na memória, verifica qual a região da memória que está disponível naquele momento e carrega o programa da memória de massa naquela região. Antes de passar o controle para o programa carregado, o sistema operacional carrega os registradores de segmento com os valores adequados. Na maioria dos microprocessador de 8 bits, como o 8085, por exemplo, os endereços estão fixos no código objeto. Para se alterar a região de memória onde um programa do 8085 será carregado é necessário alterar o código objeto. Programas que podem ser carregados em qualquer região da memória sem que o código objeto precise ser alterado, são chamados programas relocáveis. Linguagem Assembly Família Intel última revisão Ago/2007 40 7 O Mecanismo de Segmentação e as Instruções de Desvio Evidentemente o mecanismo de segmentação da memória tem implicações sobre o conjunto de instruções do microprocessador. O primeiro aspecto a abordar aqui diz respeito às instruções de desvio: JMP, CALL e RET. Existem dois tipos de desvio: o intra-segmento e o inter-segmento. 7.1 Desvios intra-segmento Numa instrução JMP intra-segmento o endereço para onde o programa deve ser desviado, pertence ao mesmo segmento onde está armazenada a instrução JMP em execução. Neste caso, a instrução deve conter, além do código do JMP, a especificação do novo valor do registrador IP. O seguintes exemplos são esclarecedores: código instrução clocks E9 00 40 15 FF E3 11 FF A7 00 40 18+EA No primeiro exemplo o valor a ser carregado no IP é definido na própria instrução. O valor codificado na instrução neste caso é na realidade a diferença entre o conteúdo atual e o novo conteúdo de IP. O que o microprocessador faz na prática é somar este valor contido na instrução ao conteúdo de IP. Este tipo de desvio é chamado desvio direto. No segundo e terceiro exemplos tem-se desvios indiretos. No segundo caso o conteúdo do registrador BX é copiado no registrador IP. No terceiro exemplo carrega- se em IP a palavra armazenada no endereço de memória especificado na instrução. Em todos os exemplos acima se diz que o desvio é do tipo próximo (NEAR), um sinônimo de desvio intra-segmento. Deve-se distinguir um tipo de instrução de desvio intra-segmento. Quando a diferença entre o endereço que contém a instrução JMP e o endereço de desvio está entre -128 e 127, pode se utilizar o desvio curto (SHORT). Esta situação é muito frequente em pequenos "loops". Neste caso é especificado na instrução o código da instrução JMP e a diferença entre o valor atual e o novo valor do registrador IP expresso em 8 bits. Este tipo de desvio é chamado desvio relativo. Exemplo: Linguagem Assembly Família Intel última revisão Ago/2007 41 código instrução clocks EB 0F 15 Neste caso ao conteúdo de IP é acrescido o valor 0FH. Observe que o código neste caso é mais compacto. código instrução clocks E9 78 01 15 Observe que o primeiro byte do código do JMP é diferente em cada caso. 7.2 Desvios inter-segmento Diz-se que o desvio é do tipo inter-segmento, quando o endereço para onde o programa deve desviar está em outro segmento. Neste caso é preciso especificar na instrução os novos valores para os registradores IP e CS, como nos exemplos abaixo. código Instrução clocks EA 00 30 34 02 15 FF AC 28 00 24+EA No primeiro exemplo têm-se um desvio direto, e no segundo caso um desvio indireto. Diz-se em ambos os casos que o desvio é dotipo distante (FAR). 7.3 Desvios condicionais Para desvios condicionais o processador só permite desvios do tipo curto direto, em que a distância entre a instrução e o endereço de desvio está na faixa -128 e 127. Exemplo: código Instrução clocks 76 3F 16 ou 4 Linguagem Assembly Família Intel última revisão Ago/2007 42 7.4 Instruções CALL A subrotina para instruções CALL é semelhante. Há 5 formas distintas de se chamar uma subrotina, conforme mostram os exemplos a seguir. código Instrução clocks E8 00 40 ; direto próximo 19 FF D3 ; indireto próximo 16 FF 10 ; indireto próximo 21+EA 9A 00 03 27 11 ; direto longo 28 FF AD 00 04 ; indireto longo 37+EA Uma diferença importante entre os CALLs próximos e distantes deve ser mencionada aqui. Instruções CALL intra-segmento (próximas) salvam na pilha apenas o conteúdo do IP para o retorno, portanto uma palavra de 16 bits. Quando ocorre um CALL inter-segmento (distantes), são salvos na pilha tanto o conteúdo de IP quanto o de CS, portanto duas palavras de 16 bits. 7.5 Instruções RET De modo análogo ao que ocorre com a instrução CALL, existem dois tipos de instruções de retorno: o primeiro para o retorno de subrotinas distantes e outro para o retorno de subrotinas próximas. As instruções do primeiro tipo restauram apenas o conteúdo do IP, enquanto as do segundo tipo restauram tanto IP quanto CS. Exemplos: código instrução clocks CB ; intra-segmento (próximo 8 CA 08 00 ; intra-segmento (próximo) 12 C3 ; inter-segmento (distante) 18 C2 08 00 ; inter-segmento (distante) 17 Deve-se deixar claro que o programador não precisa se preocupar com o tipo de instrução a utilizar em cada caso. O próprio montador Assembly identifica qual é o tipo de instrução adequado para cada situação e produz o código correspondente. Antes de concluir esta seção cabe relacionar este assunto com o que foi Linguagem Assembly Família Intel última revisão Ago/2007 43 discutido na seção 3, sobre convenção de passagem de parâmetro e alocação de variáveis locais em subrotinas. O exemplo considerado então se baseava numa rotina próxima. Se a subrotina EXEMPLO fosse do tipo distante a instrução CALL armazenaria na pilha os valores de CS e de IP. A distância do conteúdo de BP em relação a cada parâmetro de entrada seria maior, mais exatamente acrescida de 2. 8 Operação EU + BIU - Pré-busca de Instruções Já foi mencionado no início do texto que o 8086/8088 é constituído de duas unidades capazes de operar independentemente. A EU, que cuida exclusivamente da execução das instruções, e a BIU, que cuida exclusivamente da comunicação com o restante do sistema. Quando a EU necessita alguma informação da memória, seja para ler o código de uma instrução ou ler/escrever um operando, ela gera uma solicitação à BIU que providencia a execução do acesso. Associada à BIU existe uma fila de instruções com capacidade de 6 bytes no 8086 e 4 bytes no 8088, onde a BIU vai armazenando códigos de instruções que ainda não começaram a ser executadas, mas que o serão "provavelmente" logo em seguida. Na verdade a BIU vai carregando bytes armazenados na memória em endereços consecutivos àquele que contém a instrução presentemente sendo executada. Devido à tendência do microprocessador de executar instruções na mesma ordem em que estas estão armazenadas na memória, há uma alta probabilidade de que, ao a EU solicitar à BIU o código da próxima instruções, este já se encontre armazenado na fila de instruções. Neste caso a EU não necessitará aguardar ociosa até que a BIU vá à memória buscar as instruções, para só então iniciar sua execução. Ao contrário, a EU poderá iniciar imediatamente a execução da instrução. O esquema de pré-busca de instruções executado pela BIU erra na estimativa de qual será a próxima instrução a ser executada quando ocorrem instruções de desvio. Nestes casos a BIU providencia o esvaziamento da fila de instruções e passa a carregar bytes/palavras da memória a partir do endereço da nova instrução. A BIU inicia um acesso de pré-busca de instrução sempre que as seguintes duas condições estiverem satisfeitas: 1. há pelo menos 2 bytes (1 byte no caso do 8088) livres na fila de instruções. 2. não existe nenhuma solicitação de acesso ao barramento pendente vinda da EU. A Figura 15 ilustra as ações do 8086 e o conteúdo da fila de instruções quando o 8086 executa uma instrução. Admite-se que esta instrução esteja contida em dois bytes e que a fila se encontra vazia imediatamente antes do início da execução. Esta instrução gasta 7 ciclos de relógio para calcular o endereço efetivo do operando que se encontra na memória e mais 6 ciclos de relógio para executar a operação. A EU está ativa quando está executando instruções e inativa quando espera Linguagem Assembly Família Intel última revisão Ago/2007 44 que a BIU lhe forneça um dado ou instrução. A BIU só está ativa quando atende alguma solicitação da EU ou quando há pelo menos 2 (no 8088 1) bytes livres na fila de instruções. Quando estas duas condições estão satisfeitas ao mesmo tempo, a prioridade é sempre dada à solicitação da EU. Quando chega uma solicitação da EU enquanto a BIU realiza uma busca de instrução, a solicitação da EU só passa a ser atendida depois de que o acesso que está em andamento for completado. Neste caso a EU é forçada a esperar ociosa. 1º ciclo 2º ciclo 3º ciclo 4º ciclo 5º ciclo 6º ciclo E U B I U Figura 15: Diagrama de atividades da EU e da BIU 9 Tempo de Execução das Instruções O exemplo apresentado na seção anterior torna claro que o tempo consumido na execução de um programa depende tanto das instruções como da seqüência em que estas instrução são executadas. Um mesmo conjunto de instruções pode ter um tempo de execução distinto dependendo da ordem em que as instruções do conjunto são executadas. Esta característica dificulta a tarefa de determinar por inspeção de um programa em Assembly o tempo de execução correspondente. Nesta seção é descrito um procedimento que permite estimar o tempo de execução de um programa a partir de uma simples inspeção das instruções que o constituem. Na descrição das instruções nas seções anteriores, é apresentado no lado direito de cada uma o número de ciclos de relógio necessários para executá-la. Nas instruções que envolvem operandos da memória "+EA" denota o número de ciclos de relógio (clocks) adicionais necessários para calcular o endereço efetivo do operando. Linguagem Assembly Família Intel última revisão Ago/2007 45 A Tabela 4 apresenta os tempos de cálculo do endereço efetivo em número de ciclos de relógio. Para as instruções de desvio, os valores incluem os ciclos adicionais para reiniciar a fila de instruções, bem como o tempo necessário para trazer da memória a instrução contida no endereço de destino. Para os desvios condicionais são dados dois valores separados por um "/". O valor mais alto corresponde à situação em que o desvio se consuma e o valor mais baixo à situação contrária. Componentes do Endereço Efetivo clocks somente deslocamento 6 somente base ou índice 5 deslocamento +base ou índice 9 base + índice BX+SI ou BX+DI BP+SI, BP+ DI 7 8 deslocamento BP+DI+deslocamento + base BX+SI+deslocamento + índice BP+SI+deslocamento BX+DI+deslocamento 11 11 12 12 Tabela 4: Tempo consumido no cálculo do endreço efetivo dos operandos em número de ciclos de relógio. Além disso, deve-se acrescentar 4 ciclos de relógio para cada instrução que referencia uma palavra de 16 bits contida num endereço ímpar, pois o 8086 só é capaz de ler uma palavra de 16 bits num único acesso se o byte menos significativo da palavra estiver num endereço par. Se esta condição não for satisfeita, o 8086 gera dois acessos, cada um referenciando uma das duas metades da palavra. Quando se utiliza um 8088 deve ser acrescentado 4 ciclos para todos os acessos a operandos de 16 bits, independentemente de estarem ou não armazenados em endereços pares. Quando se utiliza um prefixo definindo um registrador de segmento diferente do "default", devem ser acrescentados mais dois ciclos de relógio. O tempo total de execução de um trecho de programa pode ser obtido multiplicando-se o tempo de execução de cada instrução particular pelo número de vezes que ela é executada. A soma destes produtos produz uma estimativa otimista, mas bastante aproximada do valor real. Vários fatores podem aumentar o tempo de execução relativamente a esta estimativa. Estes tempos admitem que as instruções estão disponíveis na fila de instruções assim que a EU está pronta para executá-las. Quando se tem uma sequência de instruções rápidas (com menos do que 2 ciclos de relógio) a fila de instruções pode esvaziar e obter-se um tempo de execução maior. Um outro aspecto evidenciado no exemplo da seção anterior, é que a interação Linguagem Assembly Família Intel última revisão Ago/2007 46 entre a EU e a BIU pode ter efeitos sobre o tempo de execução. Se a EU necessita buscar um operando da memória, ela poderá ter que esperar até que a BIU complete o acesso em andamento antes de iniciar o acesso solicitado pela EU. Eventuais ciclos espera no acesso a operandos podem também afetar o tempo de execução (vide exercício 27). A partir de experimentos realizados pelo fabricante do integrado, concluí-se, contudo, que este método de estimativa produz valores próximo dos valores reais. Os tempos de execução para outros processadores da família podem ser obtidos nos data sheets específicos. O procedimento para estimativa é, contudo, aproximadamente o mesmo para todos eles. 10 Exercícios 1ª Questão Sejam m e k dois números hexadecimais de 32 bits armazenados respectivamente nos registradores DX:AX e CX:BX, de tal forma que AX e BX contém os 16 bits menos significativos de m e k respectivamente. Escreva um trecho de programa em ASM86 que subtraia k de m e coloque o resultado em DX:AX. Se m < k o resultado produzido deve ser zero. 2ª Questão Repita o exercício anterior admitindo que m e k estão armazenados respectivamente a partir dos endereços MMMM e KKKK. DX:AX deve conter o resultado. 3ª Questão: Escreva um trecho de programa em ASM86 que multiplique o conteúdo do registrador AL pelo conteúdo do registrador CL, deixando o resultado em AX. Você não poderá usar a instrução MUL ou IMUL. Utilize sucessivas adições. 4ª Questão: Escreva um trecho de programa em ASM86 que divida o conteúdo do registrador DX pelo conteúdo do registrador AX, deixando o quociente inteiro em AX. Você não poderá usar a instrução DIV ou IDIV. Utilize sucessivas subtrações. 5ª Questão: Escreva um trecho de programa em ASM86 que eleve o conteúdo do registrador AX ao conteúdo do registrador DX. O resultado de 32 bits deve ser colocado no par de registradores CX:AX. Caso o valor obtido não possa ser representado em 32 bits, o conteúdo de DX:AX deverá ser nulo. Linguagem Assembly Família Intel última revisão Ago/2007 47 6ª Questão: Escreva um trecho de programa em ASM86 que zere todo o registrador AL, se o seu nibble2 menos significativo for igual a zero e carregue o valor 0FFH em AL, caso contrário. 7ª Questão: Escreva um trecho de programa em ASM86 que produza nos bits de ordem ímpar os correspondentes bits do registrador BL, e nos bits de ordem par de AL os correspondentes bits de CL. 8ª Questão: Escreva um trecho de programa em ASM86 que produza nos bits de ordem par de AL o ou exclusivo dos bits correspondentes dos registradores BL e CL, e nos bits de ordem ímpar mantenha o mesmo valor inicial. 9ª Questão: Escreva um trecho de programa em ASM86 que faça o ou exclusivo entre os dois nibbles2 do registrador AL. 10ª Questão: Escreva um trecho de programa em ASM86 que transforme um número em formato BCD armazenado em AL no seu correspondente valor hexadecimal Você não pode usar as instruções de multiplicação, divisão, e de ajuste a BCD. 11ª Questão: Escreva um trecho de programa em ASM86 devolva no registrador CL a paridade do conteúdo de AX, isto é, se o número de 1s em AX for ímpar, CL=0, e CL=1, caso contrário. 12ª Questão: Você tem uma região da memória no segmento apontado por DS do tipo: BUFFER DB 80 DUP(?) onde são armazenados strings de caracteres vindos de um teclado. Um string pode ter no máximo 80 caracteres e é sempre terminado pelo caracter "CR" (0DH). Escreva uma subrotina em ASM86 que determine o tamanho do string desta região. Admita que os registradores de segmento estão convenientemente carregados. 13ª Questão: A tabela abaixo é construída de tal maneira que o conteúdo de i-ésimo elemento é exatamente o correspondente código EBCEDIC de i, onde i está codificado em ASCII. CONV_TAB DB 64 DUP(0H) DB 1 20H DB 9 DUP(0H) 2 Nibble é o termo em inglês que denota os quatro bits de cada uma das metades de um byte. Linguagem Assembly Família Intel última revisão Ago/2007 48 DB 7 'ç','.','<','(','+',0h,'&' D B 9 DUP(0H) DB 8 '!','$','*',')',';',' ','-','/' DB ..... Você tem ainda duas regiões da memória no segmento apontado por DS do tipo: BUFFER_ASCII DB 80 DUP(?) BUFFER_EBCDIC DB 80 DUP(?) Na primeira região são armazenados strings de caracteres vindos de um teclado. Um string pode ter no máximo 80 caracteres e é sempre terminado pelo caracter "CR" (0DH). Escreva um programa em ASM86 que coloque na segunda região de memória o string produzido pelo teclado, devidamente traduzido para o código EBCDIC. 14ª Questão: Embora o 8086/8088 possua instruções de multiplicação e divisão inteira, escreva um programa (MULT) que multiplique dois números inteiros sem sinal m, armazenado em AH, e k, armazenado em AL, utilizando o seguinte algoritmo MULT(m,k)=[(k x 20 x m0]+[(k x 21 x m1]+...[(k x 27 x m7] O resultado deve estar no registrador AX. 15ª Questão: Escreva um programa que divida o conteúdo de AX pelo conteúdo de AL, utilizando algoritmo análogo ao do exercício anterior, sem usar as instruções de divisão do 8086/8088. O quociente deve ser colocado em AL e o resto em AH. 16ª Questão: Escreva uma rotina (SROT) que tome o parâmetro de entrada, um valor de 16 bits, e troque de posição o byte mais e o byte menos significativo, retornando este valor em AX. O parâmetro de entrada é passado para a subrotina SROT pelo programa
Compartilhar