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:
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 |
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.
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...
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...
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.
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.
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.
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.
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).
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.
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.
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