The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Five

Table of Content

Chapter Six (Part 2)

CAPÍTULO SEIS:
O CONJUNTO DE INSTRUÇÕES 80x86 (Parte 1)
6.0 - Chapter Overview
6.1 - The Processor Status Register (Flags)
6.2 - Instruction Encodings
6.3 - Data Movement Instructions
6.3.1 - The MOV Instruction
6.3.2 - The XCHG Instruction
6.3.3 - The LDS, LES, LFS, LGS, and LSS Instructions
6.3.4 - The LEA Instruction
6.3.5 - The PUSH and POP Instructions
6.3.6 - The LAHF and SAHF Instructions
6.4 - Conversions
6.4.1 - The MOVZX, MOVSX, CBW, CWD, CWDE, and CDQ Instructions
6.4.2 - The BSWAP Instruction
6.4.3 - The XLAT Instruction
6.5 - Arithmetic Instructions
6.5.1 - The Addition Instructions: ADD, ADC, INC, XADD, AAA, and DAA
6.5.1.1 - The ADD and ADC Instructions
6.5.1.2 - The INC Instruction
6.5.1.3 - The XADD Instruction
6.5.1.4 - The AAA and DAA Instructions
6.5.2 - The Subtraction Instructions: SUB, SBB, DEC, AAS, and DAS
6.5.3 - The CMP Instruction
6.5.4 - The CMPXCHG, and CMPXCHG8B Instructions
6.5.5 - The NEG Instruction
6.5.6 - The Multiplication Instructions: MUL, IMUL, and AAM
6.5.7 - The Division Instructions: DIV, IDIV, and AAD
6.6 - Logical, Shift, Rotate and Bit Instructions
6.6.1 - The Logical Instructions: AND, OR, XOR, and NOT
6.6.2 - The Shift Instructions: SHL/SAL, SHR, SAR, SHLD, and SHRD
6.6.2.1 - SHL/SAL
6.6.2.2 - SAR
6.6.2.3 - SHR
6.6.2.4 - The SHLD and SHRD Instructions
6.6.3 - The Rotate Instructions: RCL, RCR, ROL, and ROR
6.6.3.1 - RCL
6.6.3.2 - RCR
6.6.3.3 - ROL
6.6.3.4 - ROR
6.6.4 - The Bit Operations
6.6.4.1 - TEST
6.6.4.2 - The Bit Test Instructions: BT, BTS, BTR, and BTC
6.6.4.3 - Bit Scanning: BSF and BSR
6.6.5 - The "Set on Condition" Instructions
6.7 - I/O Instructions
6.8 - String Instructions
6.9 - Program Flow Control Instructions
6.9.1 - Unconditional Jumps
6.9.2 - The CALL and RET Instructions
6.9.3 - The INT, INTO, BOUND, and IRET Instructions
6.9.4 - The Conditional Jump Instructions
6.9.5 - The JCXZ/JECXZ Instructions
6.9.6 - The LOOP Instruction
6.9.7 - The LOOPE/LOOPZ Instruction
6.9.8 - The LOOPNE/LOOPNZ Instruction
6.10 - Miscellaneous Instructions
6.11 - Sample Programs
6.11.1 - Simple Arithmetic I
6.11.2 - Simple Arithmetic II
6.11.3 - Logical Operations
6.11.4 - Shift and Rotate Operations
6.11.5 - Bit Operations and SETcc Instructions
6.11.6 - String Operations
6.11.7 - Conditional Jumps
6.11.8 - CALL and INT Instructions
6.11.9 - Conditional Jumps I
6.11.10 - Conditional Jump Instructions II
Copyright 1996 by Randall Hyde All rights reserved.

Duplication other than for immediate display through a browser is prohibited by U.S. Copyright Law.
This material is provided on-line as a beta-test of this text. It is for the personal use of the reader only. If you are interested in using this material as part of a course, please contact rhyde@cs.ucr.edu

Supporting software and other materials are available via anonymous ftp from ftp.cs.ucr.edu. See the "/pub/pc/ibmpcdir" directory for details. You may also download the material from "Randall Hyde's Assembly Language Page" at URL: http://webster.ucr.edu

Notes:
This document does not contain the laboratory exercises, programming assignments, exercises, or chapter summary. These portions were omitted for several reasons: either they wouldn't format properly, they contained hyperlinks that were too much work to resolve, they were under constant revision, or they were not included for security reasons. Such omission should have very little impact on the reader interested in learning this material or evaluating this document.

This document was prepared using Harlequin's Web Maker 2.2 and Quadralay's Webworks Publisher. Since HTML does not support the rich formatting options available in Framemaker, this document is only an approximation of the actual chapter from the textbook.

If you are absolutely dying to get your hands on a version other than HTML, you might consider having the UCR Printing a Reprographics Department run you off a copy on their Xerox machines. For details, please read the following EMAIL message I received from the Printing and Reprographics Department:

Hello Again Professor Hyde,

Dallas gave me permission to take orders for the Computer Science 13 Manuals. We would need to take charge card orders. The only cards we take are: Master Card, Visa, and Discover. They would need to send the name, numbers, expiration date, type of card, and authorization to charge $95.00 for the manual and shipping, also we should have their phone number in case the company has any trouble delivery. They can use my e-mail address for the orders and I will process them as soon as possible. I would assume that two weeks would be sufficient for printing, packages and delivery time.

I am open to suggestions if you can think of any to make this as easy as possible.

Thank You for your business,
Kathy Chapman, Assistant
Printing and Reprographics University of California Riverside (909) 787-4443/4444

We are currently working on ways to publish this text in a form other than HTML (e.g., Postscript, PDF, Frameviewer, hard copy, etc.). This, however, is a low-priority project. Please do not contact Randall Hyde concerning this effort. When something happens, an announcement will appear on "Randall Hyde's Assembly Language Page." Please visit this WEB site at http://webster.ucr.edu for the latest scoop.

Redesigned 10/2000 with "MS FrontPage 98" using 17" monitor 1024x768
(c)  2000 BIRCOM Entertainment'95


Até agora, houve pouca discussão sobre as instruções disponíveis no microprocessador 80x86. Este capítulo retifica a situação. Note que esse capítulo é principalmente para referência. Ele explica o que cada instrução faz, não explica como combinar essas instruções para formar programas completos em assembly. O resto deste livro vai explicar como fazer isso.

6.0 Visão Geral do Capítulo

Este capítulo discute o conjunto de instruções 80x86 em modo real. Como qualquer linguagem de programação, haverá muitas instruções que você verá o tempo todo, algumas você usa ocasionalmente, e algumas raramente, se usar. Este capítulo organiza sua apresentação por classe de instrução, e não por importância. Já que programadores principiantes da linguagem assembly não têm que aprender o conjunto inteiro de instruções de modo a escrever programas em assembly significantes, você provavelmente não terá que aprender como toda isntrução opera. A lista seguinte descreve que instruções este capítulo discute. Um símbolo "" marca as instruções importantes em cada grupo. Se você aprender somente essas instruções, você provavelmente será capaz de escrever qulaquer programa em assembly que você quiser. Há muitas instruções mais, especialmente no processador 80386 e nos mais recentes. Essas instruções adicionais fazem a programação em assembly mais fácil, mas você não precisa saber delas para começar a escrever programas.

As instruções 80x86 podem ser (a grosso modo) divididas em 8 classes diferentes:

1) Instruções de Movimento de Dados

mov, lea, les , push, pop, pushf, popf

2) Conversões

cbw, cwd, xlat

3) Instruções Aritméticas

add, inc sub, dec, cmp, neg, mul, imul, div, idiv

4) Lógicas, shift, rotate, e instruções com bits

and, or, xor, not, shl, shr, rcl, rcr

5) Instruções de E/S

in, out

6) Instruções de String

movs, stos, lods

7) Instruções de controle de fluxo do programa

jmp, call, ret, conditional jumps

8) Instruções variadas

clc, stc, cmc

As seguintes seções descrever todas as instruções nesses grupos e como elas funcionam.

Em tempo, um texto como esse recomendaria contra o uso do conjunto extendido do 80386. Afinal de contas, programas que usam tais instruções não rodarão propriamente no 80286 e processadores mais antigos. Usar essas instruções adicionais poderia limitar o número de máquinas em que seu código rodaria. Contudo, o processador 80386 está em vias de desaparecer enquanto esse texto está sendo escrito. Você pode seguramente assumir que a maioria dos sistemas conterá um processador 80386sx ou superior. Esse texto freqüentemente utiliza o conjunto de instruções 80386 em vários programas de exemplo. Tenha em mente, porém, que isso é apenas por conveniência. Não há programa algum que apareça neste texto que não possa ser recodificado usando apenas instruções da linguagem assembly do 8088.

Uma palavra de conselho, particularmente para aqueles que aprenderem apenas as instruções acima: ao passo que você lê sobre as instruções 80x86 você descobrirá que as instruções não são muito complexas, e têm semântica simples. Contudo, quando você chegar perto do fim desse capítulo poderá descobrir que não tem nem pista de como colocar essas instruções juntas para formar um programa complexo. Não tema, esse é um problema comum. Nos últimos capítulos descreveremos como formar programas complexos a partir dessas instruções simples.

Nota rápida: este capítulo lista muitas instruções como "disponíveis somete no processador 80286 e superiores". De fato, muitas dessas instruções estavam disponíveis no microprocessador 80186 também. Mas já que poucos sistemas empregam o 80186, este texto ignora essa CPU. Contudo, para manter o registro direito...

6.1 O Registrador de Status do Processador (Flags)

O registrador de flags mantém o modo de operação da CPU atual e algumas informações sobre estados de instruções. A figura abaixo mostra o layout do resgitrador de flags:

Os flags de carry (vai-um), zero, sinal, e overflow são especiais porque você pode testar seus estados (zero ou um) com o setcc e as instruções condicionais de jump (veja "As Instruções de "Set on Condition"" e "As Intruções Condicionais de Jump"). O 80x86 usa esses bits, os códigos de condição, para fazer decisões durante a execução do programa.

Várias instruções artiméticas, lógicas e miscelâneas afetam o flag de overflow. Após uma operação artimética, esse flag contém "um" se o resultado não couber no operando de destino com sinal. Por exemplo, se você tentar somar os números de 16 bits com sinal 7FFFh e 0001h, o resultado é muito grande, e a CPU seta o flag de overflow. Se o resultado da operação aritmética não produz um overflow com sinal, então a CPU limpa esse flag.

Já que as operações lógicas geralmente se aplicam a valores sem sinal, as instruções lógicas do 80x86 simplesmente limpam o flag de overflow. Outras instruções deixam o flag de overflow contendo um valor arbitrário.

As instruções de string do 80x86 usam o flag de direção. Quando o flag de direção está limpo, o 80x86 processa elementos de string do endereço mais baixo para o endereço mais alto; quando está setado, a CPU process a string na direção oposta. Veja "Instruções de String" para mais detalhes.

O flag de habilitar/desabilitar interrupção controla a capacidade de o 80x86 responder a eventos externos conhecidos como requisições de interrupção. Alguns programas contêm certas seqüências de instrução que a CPU não deve interromper. O flag de habilitação/desabilitação de interrupções liga ou desliga as interrupções para garantir que a CPU não interrompa essas seções críticas de código.

O flag de trace habilita ou desabilita o modo de trace do 80x86. Depuradores (como CodeView) usam esse bit para habilitar ou desabilitar a operação de trace/step. Quando setado, a CPU interrompe cada instrução e passa o controle para o depurador, permitindo ao depurador rodar a aplicação passo a passo. Se o bit de trace estiver zerado, o 80x86 executa as intruções sem interrupção. As CPUs 80x86 não possuem instruções para manipular diretamente esse flag. Para setar ou resetar o flag de trace, você deve:

Se o resultado de algum cálculo for negativo, o 80x86 seta o flag de sinal. Você pode testar esse flag após uma operação aritmética para checar resultados negativos. Lembre-se, um valor é negativo se o bit mais significativo for "um". Assim, operações em valores sem sinal setarão o flag de sinal se o resultado tiver "um" no bit mais alto.

Várias instruções setam o flag de zero quendo elas geram um resultado nulo. Você vai usar isso freqüentemente para ver se dois valores são iguais (por exemplo, depois de subtrair dois números, eles são iguais se o resultado for zero). Esse flag também é útil depois de várias operações lógicas para ver se um bit num registrador ou posição da memória contém zero ou um.

O flag auxiliar de carry suporta operações especiais de decimas codificados em binário (BCD). Já que a maioria dos programas não lidam com números BCD, você raramente usará esse flag e mesmo assim você não o acessará diretamente. As CPUs 80x86 não provêem instruções que deixem você testar, setar ou zerar esse flg diretamente. Apenas o add, adc, sub, sbb, mul, imul, div, idiv, e instruções de BCD manipulam esse flag.

O flag de paridade é setado de acordo com a paridade dos oito bits mais baixos de qualquer operação de dados. Se uma operação produz um número par de bits "um", a CPU seta esse flag. Ele limpa esse flag se a operação produz um número ímpar de bits "um". Esse flag é útil em certos programas de comunicação de dados, contudo, a Intel o introduziu principalmente para prover alguma compatibilidade com o antigo 8080.

O flag de carry tem muitos propósitos. Primeiramente, ele notifica um overflow sem sinal (parecido com o flag de overflow que detecta um overflow com sinal). Você também vai usá-lo durante operações de multiprecisão aritmética e lógica. Certas instruções de teste, set, clear e inversão de bits no 80386 afetam diretamente esse flag. Finalmente, desde que você pode facilmente limpar, setar, inverter e setá-lo, ele é útil para operações booleanas. O flag de carry tem muitos propósitos, e saber como usá-lo, e para que propósito, pode confundir programadores principiantes em assembly. Felizmente, para qualquer instrução dada, o significado do flag de carry é claro.

O uso desses flags ficarão repidamente aparentes nas próximas seções e capítulos. Essa seção é mais uma introdução formal para cada flag no registrador do que uma tentativa de explicar a função exata de cada flag. Para mais detalhes na operação de cada flag, continue lendo...

6.2 Codificação das Instruções

O 80x86 usa codificação binária para cada operação de máquina. Embora seja importante ter um entendimento geral de como o 80x86 codifica as instruções, não é importante que você memorize os códigos para todas as instruções no conjunto. Se você fosse escrever um assemblador ou desassemblador (depurador), você precisaria edfinitivamente de conhecer os códigos exatos. Para programação geral em assembly, contudo, você não vai precisar dos códigos exatos.

Porém, ao passo que você se torna mais experiente com o assembly, você provavelmente vai querer estudar a codificação do conjunto de instruções do 80x86. Certamente você deveria estar prevenido de termos tais como opcode, mod-reg-r/m byte, valor de deslocamento, e assim por diante. Embora você não precise de memorizar os parâmetros para cada instrução, é sempre uma boa idéia saber os tamanhos e ciclos de cada instrução que você usa regularmente, já que isso o ajudará a escrever programas melhores. O Capítulo Três e o Capítulo Quatro provêem uma olhada detalhada na codificação das instruções para várias instruções (80x86 e x86); tal discussão foi importante por que você precisa entender como a CPU codifica e executa instruções. Este capítulo não lida com tais detalhes. Este capítulo apresenta uma visão de nível mais alto de cada instrução e assume que você não se importa como a máquina trata bits na memória. Para aquelas poucas vezes que você precisará saber a codificação para uma instrução em particular, uma lista completa dos códigos das instruções aparece no Apêndice D.

6.3 Instruções de Movimento de Dados

As instruções de movimento de dados copiam valores de uma posição para outra. Essas instruções incluem mov, xchg, lds, lea, les, lfs, lgs, lss, push, pusha, pushad, pushf, pushfd, pop, popa, popad, popf, popfd, lahf, e sahf.

6.3.1 A Instrução MOV

A instrução mov tem vários formatos:

                mov     reg, reg
                mov     mem, reg
                mov     reg, mem
                mov     mem, dado direto
                mov     reg, dado direto
                mov     ax/al, mem 
                mov     mem, ax/al 
                mov     segreg, mem16
                mov     segreg, reg16
                mov     mem16, segreg
                mov     reg16, segreg

O último capítulo discutiu o mov em detelhes, apenas alguns poucos detalhes menores são válidos aqui. Primeiramente, há variações do mov que são mais rápidas e mais curtas que outras instruções mov e que fazem o mesmo serviço. Por exemplo, tanto o mov ax, mem quanto o mov reg, mem podem carregar ax com uma posição na memória. Em todos os processadores a primeira versão é mais curta. Nos membros mais novos da família 80x86, ela é mais rápida também.

Há dois detalhes importantes a se notar sobre a instrução mov. Primeiramente, não há operação para mover da memória para a memória. O byte de endereçamento modo mod-reg-r/m (veja o Capítulo Quatro) permite operandos serem dois registradores, ou um registrador e uma posição de memória. Não há formato da instrução mov que permita você programar dois endereços de memória na mesma instrução. Em segundo, você não pode mover dados diretamente para um registrador de segmento. As únicas instruções que movem dados para ou de um registrador de segmento têm bytes mod-reg-r/m associados com eles; não há formato que mova um valor imediato para um registrador de segmento. Dois erros comuns que programadores principiantes fazem é tentar mover dados de memória para a memória e tentar carregar um registrador de segmento com uma constante.

Os operandos da instrução movpodem ser bytes, words, ou double words. Ambos os operandos devem ser do mesmo tamanho, ou o MASM vai gerar um erro quando for montar seu programa. Isso se aplica a operandos de memória e registradores. Se você declarar uma variável B, usando byte, e tentar carregar essa variável no registrador AX, o MASM vai reclamar de conflito de tipos.

A CPU extende dados imediatos ao tamanho do operando destino (a menos que ele seja muito grande para caber no destino, o que dá erro). Note que você pode mover um valor imediato para uma posição na memória. A mesma regra a respeito de tamanho se aplica. Contudo, MASM não pode determinar o tamanho de certos operandos de memória. Por exemplo, a instrução mov [bx], 0 armazena um valor de oito bits, dezesseis bits ou trinta e dois bits? MASM não pode dizer, então ele dá erro. Esse problema não existe quando você move um valor imediato para uma variável que você tenha declarado no programa. Por exemplo, se você declarou B como uma variável byte, o MASM sabe armazenar um "zero" de oito bits dentro de B para a instrução mov B, 0. Apenas para operandos de memória envolvendo ponteiros sem operandos variáveis sofrem esse problema. A solução pe explicitamente dizer para o MASM se o operando é um byte, word, ou double word. Você pode conseguir fazer isso com as seguintes formas de instrução:

                mov     byte ptr [bx], 0
                mov     word ptr [bx], 0
                mov     dword ptr [bx], 0       (3)

(3) Disponível apenas e 80386 e superiores

Para mais detalhes sobre o operando de tipo, ptr, veja o Capítulo Oito.

Movimentações para e de registradores de segmento são sempre em 16 bits; o operando mod-reg-r/m deve ser de 16 bits, ou o MASM vai gerar um erro. Já que você não pode carregar uma constante diretamente para um registrador de segmento, uma solução comum é carregar uma constante para um registrador de uso geral do 80x86 e então copiá-la para o registrador de segmentos. Por exemplo, a seguinte instrução carrega o registrador es com o valor 40h:

                mov     ax, 40h
                mov     es, ax

Note que quase qualquer registrador de uso geral bastaria. Aqui, ax foi escolhido arbitrariamente.

As instruções mov não afetam flag algum. Em particular, o 80x86 preserva os flags durante a execução da instrução mov.

6.3.2 A Instrução XCHG

A instrução xchg (exchange) troca dois valores um pelo outro. A forma geral é

                xchg    operando1, operando2

Há quatro formas específicas dessa instrução no 80x86:

                xchg    reg, mem
                xchg    reg, reg
                xchg    ax, reg16
                xchg    eax, reg32              (3)

(3) Disponível somente no processador 80386 e posteriores

As duas prmeiras formas requerem dois ou mais bytes para o opcode e mod-reg-r/m (um deslocamento, se necessário, requer bytes adicionais). A terceira e a quarta formas são formatos especiais da segunda, que mudam dados no registrados (e)ax com outro registrador de 16 ou 32 bits. O formato de 16 bits usa um opcode de apenas um byte, que é mais curto que as outras formas que usam um byte para o opcode e outro byte mod-reg-r/m.

Você já deveria notar um padrão: a família 80x86 freqüentemente provê versões mais rápidas e mais curtas de instruções que usam o resgistrador AX. Sendo assim, você deveria tentar arrumar suas computações de modo a que elas usem o registrador (e)ax o quanto for possível. A intrução xchg é um exemplo perfeito, a forma que troca registradores de 16 bits só tem um byte de tamanho.

Note que a ordem dos operandos no xchg não importa. Isso é, você pode fazer xchg mem,reg e obter o mesmo resultado de um xchg reg,mem. A maioria dos assemblers modernos vão emitir automaticamente o opcode para a instrução mais curta xchg ax,reg se você especificar xchg reg,ax.

Ambos os operandos devem ser do mesmo tamanho. Em processadores pré-80386 os operandos podem ter 8 ou 16 bits. Em processadores a partir do 80386, os operandos podem ser de 32 bits também.

A instrução xchg não altera flag algum.

6.3.3 As Instruções LDS, LES, LFS, LGS, e LSS

As instruções lds, les, lfs, lgs, e lss fazem você carregar um par "registrador de uso geral e registrador de segmento" com uma única instrução. No 80286 e antes, as instruções lds e les são as únicas instruções que processam diretamente valores maiores que 32 bits. A forma geral é

                LxS     destino, fonte

Essas instruções possuem essas formas específicas:

                lds     reg16, mem32
                les     reg16, mem32
                lfs     reg16, mem32    (3)
                lgs     reg16, mem32    (3)
                lss     reg16, mem32    (3)

(3) Disponível apenas em processadores 80386 e posteriores

Reg16 é qualquer registrador de 16 bits e mem32 é uma posição de memória double word (declarada com o comando dword).

Essas instruções vão carregar a double word de 32 bits no endereço especificado por mem32 para dentro de reg16 e os registradores ds, es, fs, gs ou ss. Elas carregam o registrador de uso geral com a word de ordem baixa do operando da memória, e o registrador de segmento com a word de ordem mais alta. Os seguintes algoritmos descrevem a operação exata:

lds reg16, mem32:
                reg16 := [mem32]
                ds := [mem32 + 2]
les reg16, mem32:
                reg16 := [mem32]
                es := [mem32 + 2]
lfs reg16, mem32:
                reg16 := [mem32]
                fs := [mem32 + 2]
lgs reg16, mem32:
                reg16 := [mem32]
                gs := [mem32 + 2]
lss reg16, mem32:
                reg16 := [mem32]
                ss := [mem32 + 2]

Já que as instruções LxS carregar os registradores de segmento do 80x86, você não deve usar essas instruções arbitrariamente. Use-as para setar ponteiros (far) para certos objetos de dados como discutido no Capítulo Quatro. Qualquer outro uso pode causa problemas com seu programa se você tentar portá-lo para Windows, OS/2 ou UNIX.

Tenha em mente que essas instruções carregam os quatro bytes de uma posição da memória dada para dentro do par de registradores; elas não carregam o endereço de uma variável para o par de registradores (ou seja, essa instrução não tem modo imediato). Para aprender como carregar um endereço de uma variável no par de registradores, veja o Capítulo Oito.

As instruções LxS não afetam qualquer flag do 80x86.

6.3.4 A Instrução LEA

A instrução lea (Load Effective Address) é outra instrução usada para preparar valores de ponteiros. A instrução lea toma a forma:

                lea     dest, fonte

As formas específicas no 80x86 são

                lea     reg16, mem
                lea     reg32, mem      (3)

(3) Disponível somente no 80386 e posteriores.

Ela carrega o registrador de 16 ou 32 bits de uso geral com o endereço efetivo da posição de memória especificada. O endereço efetivo é o endereço de memória final obtido depois de todos as computações de modo de endereçamento. Por exemplo, lea ax, ds:[1234h] carrega o registrador AX com o endereço da posição de memória 1234h; aqui ele apenas carrega AX com o valor 1234h. Se você pensar por um momento, essa não é uma operação muito excitante. Afinal, o mov ax,dado pode fazer isso. Então por que se importar com a lea? Bem, há muitas outras formas de operando de memória além de operandos apenas de deslocamento. Considere as seguintes instruções lea:

                lea     ax, [bx]
                lea     bx, 3[bx]
                lea     ax, 3[bx]
                lea     bx, 4[bp+si]
                lea     ax, -123[di]

A instrução lea ax, [bx] copia o endereço da expressão [bx] para o registrador AX. Já que o endereço efetivo é o valor no registrador BX, essa instrução copia o valor de BX para AX. Novamente, essa instrução não é muito interessante porque podemos fazer um mov para fazer a mesma coisa, até mais rápidamente.

A instrução lea bx,3[bx] copia o endereço efetivo de 3[bx] para o registrador BX. Já que o endereço efetivo é igual ao valor atual de BX mais três, essa isntrução na verdade soma três ao registrador BX. Já existe uma instrução add que deixa você fazer isso, então, novamente, a instrução lea é supérflua para essa proposta.

A terceira instrução lea acima mostra onde o lea realmente começa a brilhar. lea ax,3[bx] copia o endereço da posição de memória 3[bx] para AX, ou seja, ela soma três com o valor no BX e move a soma para AX. Isso é um exemplo excelente de como você pode usar a instrução lea para fazer uma operação mov e uma adição com uma instrução só.

As duas instruções finais acima, lea bx,4[bp+si] e lea ax,-123[di] provêem exemplos adicionais de instruções lea que são mais eficientes que seus equivalentes mov/add.

No 80386 e mais novos, você pode usar o endereçamento escalado indexado para multiplicar por dois, quatro ou oito, bem como adicionar registradores e deslocamentos juntos. A Intel recomenda fortemente o uso de lea já que é muito mais rápida que uma seqüência de instruções para calcular o mesmo resultado.

O propósito (real) da lea é carregar um registrador com um endereço de memória. Por exemplo, lea bx,128[bp+di] seta BX com o endereço do byte referenciado por 128[BP+DI]. Desse modo, uma instrução da forma mov al,[bx] roda mais rápido que uma da forma mov al,128[bp+di]. Se esta instrução executar muitas vezes, é provavelmente mais eficiente carregar o endereço efetivo de 128[bp+di] para BX e usar o modo de endereçamento [bx]. Essa é uma otimização comum em programas de alta performance.

A instrução lea não afeta qualquer bit de flag do 80x86.

6.3.5 As Instruções PUSH e POP

As instruções push e pop do 80x86 manipulam dados na pilha do hardware do 80x86. Há 19 variedades de instruções push e pop instructions, que são:

                push    reg16
                pop     reg16
                push    reg32           (3)
                pop     reg32           (3)
                push    segreg
                pop     segreg          (exceto CS)
                push    memory
                pop     memory
                push    dado_imediato   (2)
                pusha                   (2)
                popa                    (2)
                pushad                  (3)
                popad                   (3)
                pushf
                popf
                pushfd                  (3)
                popfd                   (3)
                enter   imm, imm        (2)
                leave                   (2)

(2)- Disponível apenas no 80286 e posteriores.
(3)- Disponível apenas no 80386 e posteriores.

As duas primeiras instruções fazem pop e push de um registrador de uso geral de 16 bits. Essa é uma versão compacta (um byte) projetada especificamente para registradores. Note que há uma segunda forma que provê um byte mod-reg-r/m que poderia fazer push em registradores também; a maioria dos assembladores só usam aquela forma para fazer push do valor de uma posição de memória.

O segundo par de instruções fazem push ou pop num registrador de 32 bits de uso geral do 80386. Isso é realmente nada mais que a instrução de push de registrador descrita no parágrafo anterior com um prefixo de tamanho de byte.

O terceiro par de instruções push/pop deixam você fazer push e pop de um registrador de segmento do 80x86. Note que as intruções que fazem push de fs e gs são maiores que aquelas para cs, ds, es e ss, veja o Apêndice D para detalhes exatos. Você só pode fazer push do registrador cs (fazer pop de cs criaria alguns problemas interessantes de controle de fluxo do programa).

O quarto par de instruções push/pop lhe permitem fazer push ou pop do conteúdo de uma posição da memória. No 80286 e superior, isso deve ser um valor de 16 bits. Para operações de memória sem um tipo explícito (ex. [bx]) você deve ou usar o menmônico pushw ou explicitar o tamanho usando uma instrução como word ptr [bx]. No 80386 e superior, você pode fazer push e pop de valores de 16 e 32 bits. Você pode usar o operando de memória dword, pode usar o mnemônico pushd, ou pode usar o perador dword ptr para forçar a operação em 32 bits. Exemplos:

                push    DblWordVar
                push    dword ptr [bx]
                pushd   dword

As instruções pusha e popa (disponíveis no 80286 e posteriores) fazem push e popo de todos os registradores de usu geral de 16 bits do 80x86. Pusha faz push de todos os registradores na seguinte ordem: ax, cx, dx, bx, sp, bp, si, e di. Popa faz o pop desses registradores na ordem inversa. Pushad e Popad (disponíveis a partir do 80386) fazem a mesma coisa com o conjunto de registradores de 32 bits do 80386. Note que esses "push all" e "pop all" não fazem push ou pop dos flags ou dos registradores de segmento.

O pushf e o popf permitem que você faça push/pop do registrador de status do processador (o registrador de flags). Note que essas duas instruções provêem um mecanismo para modificar o flag de trace do 80x86. veja a descrição desse processo mais acima nesse capítulo. É claro, você pode setar e limpar os outros flags desse modo também. Contudo, a maioria dos outros flags que você vai querer modificar (especificamente, os códigos de condição) provêem instruções específicas ou outras seqüências simples para esse propósito.

Entre e Leave fazem push/pop do registrador bp e alocam lugar para armazenar variáveis locais na pilha. Você vai ver mais sobre essas isntruções num capítulo posterior. Esse capítulo não as considera, já que não são particularmente úteis fora do contexto de entrada e saída de procedures.

"Então, o que essas instruções fazem?" você deve estar se perguntando agora. As intruções de push move dados para a pilha do 80x86 e as instruções de pop da pilha para a memória ou para um registrador. O seguinte é uma descrição algorítimica de cada instrução:

instruções push (16 bits):

	SP := SP - 2
	[SS:SP] := operando de 16 bits (armazena o resultado na posição SS:SP.)

instruções pop (16 bits): 
	operando de 16-bits := [SS:SP]
	SP := SP + 2

instruções push (32 bits): 
	SP := SP - 4
	[SS:SP] := operando de 32 bits

instruções pop (32 bits): 
	operando de 32 bit := [SS:SP]
	SP := SP + 4

Você pode tratar as instruções pusha/pushad e popa/popad cmo equivalentes à seqüência correspondente de operações de push/pop de 16 ou 32 bit (ex: push ax, push cx, push dx, push bx, etc.).

Note três coisas sobre a pilha de hardware do 80x86. Primeiro, ela sempre está no segmento de pilha (seja lá onde ss apontar). Em segundo, a pilha cresce para baixo na memória. Isto é, ao passo que você faz push de valores na pilha, a CPU as armazena em posições de memória inferiores. E finalmente, o ponteiro da pilha do 80x06 (ss:sp) sempre contém o endereço do topo da pilha (o último valor colocado nela).

Você pode usar a pilha do 80x86 para salvar registradores e variáveis temporariamente, passar parâmetros para uma procedures, alocar armazenamento para variáveis locais, e outros usos. As instruções de push e pop são extremamente valiosas para manipular esses ítens na pilha. Você terá uma chance de ver como usá-las mais tarde nesse texto.

A maioria das instruções push e pop não afetam quaiquer dos flags no registrador de status do processador 80x86. As instruções popf/popfd, por sua natureza, podem modificar todos os bits de flag no registrador de flags. Pushf e Pushfd colocam os flags na pilha, mas não alteram quaisquer flags quando fazem isso.

Todos os pushes e pops são operações de 16 ou 32 bits. Não existe nenhum modo (fácil) de fazer push de um valor de 8 bits na pilha. Para fazer push de u valor de 8 bits, você teria que carregá-lo no byte de ordem superior de um registrador de 16 bits, fazer o push desse registrador, e adicionar 1 ao ponteiro da pilha. Em todos os processadores, exceto o 8088, isso diminuiria a velocidade ao acesso à pilha, já que agora sp contém um valor ímpar, desalinhando os futuros pushes e pops. Portanto, a maioria dos programas fazem push e pop de 16 bits, mesmoquando lidam com valores de 8 bits.

Embora seja relativamente seguro fazer push de uma variável de memória de 8 bits, tenha cuidado quando fizer o pop da pilha para uma posição na memória de 8 bits. Fazer push de uma variável de 8 bits com push word ptr ByteVar faz o push de 2 bytes, o byte na variável ByteVar e o byte imediatamente seguinte a ele. Seu código pode simplesmente ignorar o byte extra que essainstrução joga na pilha. Pegar o valor de vota é que não é tão direto. Geralmente, não machuca muito se você fizer push desses dois bytes. Mas pode ser um desastre se você fizer pop de um valor e apagar o byte seguinte na memória. Há duas soluções para esse problema. Primeiro, você poderia fazer o pop do valor de 16 bits para um registrador, como ax e armazenar o byte de ordem inferior daquele registrador na variável byte. A segunda solução é reservar um byte extra após o byte daquela variável para segurar o word inteiro que você vai popar. A maioria dos programas usam a aproximação anterior.

6.3.6 As Instruções LAHF e SAHF

O lahf (carrega ah dos flags) e o sahf (armazena ah nos flags) são instruções arcaicas incluídas no 80x86 para ajudar a melhorar a compatibilidade com o antigo processador 8080 mP da Intel. Como tal, essas instruções têm muito pouco uso nos programas modernos do 80x86. A instrução lahf não afeta os bits de flag. A instrução sahf, por sua natureza, modifica os bits S, Z, A, P e C no registrador de flags. Essas instruções não requerem quaisquer operandos e você as usa da seguinte maneira:

		sahf
		lahf

Sahf só afeta os 8 bits de ordem inferior do registrador de flags. Semelhantemente, lahf só afeta os 8 bits de ordem inferior do registrador AH. Essas instruções não lidam com os flags de overflow, direção, desligamento de interrupção ou trace. O fato de que essas instruções não lidam com o flag de overflow é uma limitação importante,

Sahf tem um uso principal. Quando se usa um processador de ponto flutuante (8087, 80287, 80387, 80486, Pentium, etc.) você pode usar sahf para copiar o registrador de flags de ponto flutuante para o registrador de flags do 80x86. Você vai ver isso no capítulo de aritmética de ponto flutuante (Veja o capítulo 14).

6.4 Conversões

O conjunto de instruções do 80x86 provêem muitas instruções de conversão. Elas incluem movzx, movsx, cbw, cwd, cwde, cdq, bswap, e xlat. A maioria dessas instruções extendem o sinal ou o zero de valores, os últimos dois convertem entre formatos de armazenamento e traduzem valores via uma tabela de lookup. Essas instruções levam a forma geral:

                movzx   dest, src       ;Dest deve ser duas vezes o tamanho de src.
                movsx   dest, src       ;Dest deve ser duas vezes o tamanho de src.
                cbw
                cwd
                cwde
                cdq
                bswap   reg32
                xlat                    ;Forma especial permite um operando
6.4.1 As Instruções  MOVZX, MOVSX, CBW, CWD, CWDE, e CDQ 

Essas instruções extendem zero e sinal de valores. O cbw e o cwd estão disponívels em todos os processadores 80x86. Os movzx, movsx, cwde e cdq só estão disponíveis em processadores a partir do 80386.

O cbw (converte byte para word) extende o sinal do valor de 8 bits em al para ax. Isso é, ela copia o bit 7 de AL para os bits de 8 a 15 de AX. Essa instrução é especialmente importante antes de executar uma divisão de 8 bits (como você vai ver na seção "Instruções Aritméticas"). Essa instrução não requer operandos, e você a usa dessa forma:

		cbw

A instrução cwd (converte word para double word) extendende o sinal do valor de 16 bits em AX para 32 bits e coloca o resultado em dx:ax. Ela copia o bit 15 de ax para os bits de dx. Ela está disponível em todos os processadores 80x86, o que explica porque ela não extende o sinal para eax. Como a instrução cbw, ela é muito importante para operações de divisão. Cwd não requer operando algum, e é usada como se segue:

		cwd

A instrução cwde extende o valor de 16 bit em ax para 32 bits e coloca o resultado em eax, copiando o bit 15 de ax para os bits 16..31 de eax. Essa instrução só existe a partir do 80386. Bem como cbw e cwd, a instrução não tem operandos e é assim que se usa:

		cwde

A instrução cdq extende o sinal do valor de 32 bits em eax para 64 bits e coloca o resultado em edx:eax, copiando o bit 31 de eax para todos os bits de edx. Essa instrução está disponível a partir do 80386. Você normalmente usaria essa isntrução antes de uma divisão longa. Como cbw, cwd e cwde, essa instrução não requer operandos, e se usa dessa forma:

		cdq

Se você quiser extender o sinal de um valor de 8 bits para 32 ou 64 bits usando essas instruções, você poderia usar seqüências como as seguintes:

; Extende o sinal de al para dx:ax

                cbw
                cwd

; Extende o sinal de al para eax

                cbw
                cwde

; Extende o sinal de al para edx:eax

                cbw
                cwde
                cdq

Você pode também usar o movsx para extensões de sinal de 8 para 16 ou 32 bits.

A instrução movsx é uma forma generalizada das instruções cbw, cwd e cwde. Ela vai extender o sinal de um valor de 8 bits para um valor de 16 ou 32 bits, ou extender um valor de 16 bits para um de 32 bits. Essa instrução usa um byte mod-reg-r/m para especificar os dois operandos. As formas permitidas para essa instrução são:

                movsx   reg16, mem8
                movsx   reg16, reg8
                movsx   reg32, mem8
                movsx   reg32, reg8
                movsx   reg32, mem16
                movsx   reg32, reg16

Note que qualquer coisa que você pode fazer com cbw e cwde, você pode fazer com um movsx:

                movsx   ax, al          ;CBW
                movsx   eax, ax         ;CWDE
                movsx   eax, al         ;CBW seguido de CWDE

Contudo, cbw e vwde são mais curtos e algumas vezes mais rápidos. Essa instrução está disponível só no 80386 e superiores. Note que não há equivalentes diretos de movsx para cwd e cdq.

A instrução movzx funciona como a movsx, exceto que ela etende valores sem sinal, extendendo zeros ao invés de valores de sinal, com extensão de sinal. A sintaxe é a mesma do movsx, exceto, é claro, que você usa movzx ao invés de movsx.

Note que se você quiser extender zeros de um registrador de 8 bits para 16 bits (por exemplo, de AL para AX) um simples MOV é mais rápido que movzx. Por exemplo,

		mov	bh, 0

é mais rápido e mais curto que

		movzx	bx, bl

Obviamente, se você mover os dados para um registrador diferente de 16 bits (ex: movzx bx, al), a instrução movzx é melhor.

Como o movsx, a intrução movzx está disponível somente a partir dos processadores 80386. As instruções de extensão de sinal e zero não afetam quaisquer flags.

6.4.2 A Instrução BSWAP

A intrução bswap, disponível somente no 80486 (sim, 486) e posteriores, converte valores de 32 bits entre little endian e big endian. Essa instrução aceita só um único registrador de 32 bits como operando. Ele troca o primeiro byte com o quarto e o segundo com o terceiro. A sintaxe para a instrução é:

		bswap	reg32

onde reg32 pe um registrador de uso geral do 80486.

A família de processadores Intel usa uma organização de memória conhecida como little endian byte. Na organização little endianm o byte de menor ordem de uma seqüência multi-byte aparece como no menor endereço na memória. Por exemplo, os bits de zero a sete de um valor de 32 bits aparecem no enredeço mais baixo; os bits de 8 a 15 aparecem no segundo endereço de memória; os bits de 16 a 23 aparecem no terceiro byte, e os bits de 24 a 31 aparecem no quarto byte.

Outra organização popular de memória é o big endian. No esquema big endian, os bits de 24 a 31 apareem no primeiro endereço (endereço mais baixo), os bits de 16 a 23 aparecem no segundo byte, os bits 8-15 aparecem em terceiro, e os bits de 0 a 7 aparecem no quarto byte. CPUs como os da família Motorola 68000 usados pela Apple no seu Macintosh e muitos chips de tecnologia RISC empregam o esquema big endian.

Normalmente, você não se preocuparia com a organização dos bytes na memória, já que os programas escritos para um processador Intel em assembly não rodam num processador 68000. Porém, é muito comum a troca de dados entre máquinas com organização de bytes diferentes. Infelizmente, valores em 16 e 32 bits em máquinas big endian não produzem resultados corretos quando você os utiliza em máquinas little endian. É aí que entra a instrução bswap. Ela lhe permite converter facilmente valores big endian de 32 bits para valores little endian de 32 bits.

Um uso interessante do bswap é prover acesso a um segundo conjunto de registradores de 16 bits. Se você está usando somente registradores de 16 bits no seu código, você pode dobrar o número de registradores disponíveis usando a instrução bswap para trocar dados de um registrador de 16 bits com a word de ordem superior de um registrador de 32 bits. Por exemplo, você pode ter dois valores de 16 bits em EAX e mover o valor apropriado em AX como se segue:

        < Algum cálculo que deixe algum valor em AX >

                bswap   eax

        < Mais alguns cálculos involvendo AX >

                bswap   eax

        < Alguns cálculos envolvendo o valor original em AX >

                bswap   eax

        < Cálculos nvolvendo a segunda cópia de AX acima >

Você pode usar essa técnica no 80486 para obter duas cópias de ax, bx, cx, dx, si, di e bp. Você deve prestar muita atenção se usar essa técnica com o registrador sp.

Nota: para converter valores de 16 bits big endian para valores 16 bits little endian, use a instrução do 80x86 xchg. Por exemplo, se AX contém um valor big endian de 16 bits, você pode convertê-lo para um valor de 16 bits little endian (ou vice-versa) usando:

		xchg	al, ah

A instrução bswap não afeta qualquer flag no registrador de flags do 80x86.

6.4.3 A Instrução XLAT

A instrução XLAT traduz o valor em AL baseado numa tabela de lookup na memória. Ela faz o seguinte:

		temp := al+bx
		al := ds:[temp]

isso é, BX aponta para a tabela no segmento de dados atual. XLAT substitiu o valor em AL com o byte no desclocamento originalmente em AL. Se AL contém 4, XLAT substitui o valor em AL com o quinto item (deslocamento 4) de dentro da tabela apontada por ds:bx. A instrução XLAT leva a forma:

		xlat

Tipicamente, ela não tem nenum operando. Você pode especificar um, mas o assemblador o ignora, virtualmente. O único propósito para especificar um operando é que você pode especificar um prefixo de segmento:

		xlat	es:Tabela

Isso diz ao assembler para emitir um byte de prefixo de segmento "es:" antes da instrução. Você ainda deve carregar BX com o endereço da Tabela; a forma acima não provê o endereço de Tabela à instrução. Somente o prefixo de override do segmento no operando é siginificativo.

A instrução XLAT não afeta o registrador de flags do 80x86.

Chapter Six: The 80x86 Instruction Set (Part 1)
26 SEP 1996

Tradução: Renato Nunes Bastos
02/Dez/2002