Buscar

Curso de Assembly RBT 1994 Frederico Lamberti Pissarra

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 3, do total de 132 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 6, do total de 132 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você viu 9, do total de 132 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Prévia do material em texto

Curso de Assembly
Frederico Lamberi Pissarra
Atualização dos textos originais para UTF-8.
Publicado originalmente no ano de 1994, na Rede Brasileira de Telemática (RBT)
Revisado em 28 de julho de 2016
Prefácio
Já fazem 20 anos desde a primeira publicação dessa série de artigos que foi chamado de “Curso”, na 
RBT. Eu tinha 25 anos de idade, tinha um PC com processador 286 e, quando adquiri um 386DX 
40MHz, escrevi sobre os registradores e novas instruções para 386. Estava fazendo experimentos com 
o TURBO ASSEMBLER e com o assembler in-line do PASCAL e do Borland C++ 3.1 (que era o meu 
IDE preferido, na época). De lá para cá, muita coisa mudou:
• Processadores de 16 bits estão limitados aos microcontroladores. Há quase uma década não 
ouço falar do 8086 ou o antigo 286;
• Com o advento da moderna arquiteura x86-64 o uso de registradores de segmento (ou 
“seletores”, como são chamados desde o 386) tornou-se obsoleto – pelo menos à nível de 
aplicação;
• Os compiladores C/C++ tornaram-se tão poderosos que desenvolver diretamente em assembly é 
raramente praticado;
• As modernas placas de vídeo e os sistemas operacionais não possibilitam mais o acesso direto à 
memória de vídeo e ao chipset. Tudo tem que ser feito por bibliotecas como OpenGL ou 
DirectX;
• A aritimética de ponto-fixo era uma alternativa à lenta aritimética de ponto-flutuante, que era 
muito lenta. Hoje, trabalhar com “floats” e “doubles” é tão performático quanto trabalhar com 
aritimética inteira;
• EMM386 não é usado há muito tempo!
• Novos conjuntos de registradores estão disponíveis para o desenvolvedor em assembly. Desde 
às extensões para 64 bits (RAX, RBX etc), até a quantidade deles (15 registradores de uso geral 
na arquitetura x86-64). Ainda, temos SIMD (SSE), que disponibiliza registradores para uso em 
ponto-flutuante, e de forma paralela!
• Multithreading é uma realidade, com CPUs de multiplos “cores”;
O “curso” a seguir é uma velharia. Mas, ao que parece, ainda ajuda muita gente a entender a base do 
assembly. Eu penso nesses textos como documentos históricos...
Frederico Lamberti Pissarra
18 de março de 2014
Conteúdo
Aula 1 (Embasamento)
Aula 2 (Aritimetica binária)
Aula 3 (Registradores Pilha e Flags)
Aula 4 (Instruções de armazenamento e blocos)
Aula 5 (Instruções Lógicas)
Aula 6 (Instruções Aritiméticas)
Aula 7 (Instruções de comparação)
Aula 8 (Saltos)
Aula 9 (Interrupções)
Aula 10 (Shifts)
Aula 11 (Mais instruções de comparação)
Aula 12 (Usando assembly no TURBO PASCAL)
Aula 13 (Usando assembly em C)
Aula 14 (Usando o TURBO ASSEMBLER)
Aula 15 (Mais TASM)
Aula 16 (Entendendo o EMM386)
Aula 17 (Usando o EMM386)
Aula 18 (O processador 386)
Aula 19 (strlen e strcpy em Assembly)
Aula 20 (Aritimética em ponto-fixo)
Aula 21 (Mexendo com a VGA)
Aula 22 (Latches e bitmasks)
Aula 23 (Mexendo ainda mais na VGA)
Aula 24 (O modo de escrita 1 da VGA)
Aula 25 (O modo de escria 3 da VGA)
Aula 26 (O modo de escrita 2 da VGA)
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 01 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 A linguagem ASSEMBLY (e não assemblER!) dá medo em muita gente!
 Só não sei porque! As liguagens ditas de "alto nível" são MUITO
 mais complexas que o assembly! O programador assembly tem que
 saber, antes de mais nada, como está organizada a memória da máquina
 em que trabalha, a disponibilidade de rotinas pré-definidas na ROM
 do micro (que facilita muito a vida de vez em quando!) e os demais
 recursos que a máquina oferece.
 Uma grande desvantagem do assembly com relação as outras
 linguagens é que não existe tipagem de dados como, por exemplo,
 ponto-flutuante... O programador terá que desenvolver as suas
 próprias rotinas ou lançar mao do co-processador matemático (o TURBO
 ASSEMBLER, da Borland, fornece uma maneira de emular o
 co-processador). Não existem funções de entrada-saída como PRINT do
 BASIC ou o Write() do PASCAL... Não existem rotinas que imprimam
 dados numéricos ou strings na tela... Enfim... não existe nada de
 útil! (Será?! hehehe)
 Pra que serve o assembly então? A resposta é: Para que você
 possa desenvolver as suas próprias rotinas, sem ter que topar com
 bugs ou limitações de rotinas já existentes na ROM-BIOS ou no seu
 compilador "C", "PASCAL" ou qualquer outro... Cabe aqui uma
 consideração interessante: É muito mais produtivo usarmos uma
 liguagem de alto nível juntamente com nossas rotinas em assembly...
 Evita-se a "reinvenção da roda" e não temos que desenvolver TODAS as
 rotinas necessárias para os nossos programas. Em particular, o
 assembly é muito útil quando queremos criar rotinas que não existem
 na liguagem de alto-nível nativa! Uma rotina ASM bem desenvolvida
 pode nos dar a vantagem da velocidade ou do tamanho mais reduzido em
 nossos programas.
 O primeiro passo para começar a entender alguma coisa de
 assembly é entender como a CPU organiza a memória. Como no nosso
 caso a idéia é entender os microprocessadores da família 80x86 da
 Intel (presentes em qualquer PC-Compatível), vamos dar uma olhadela
 no modelamento de memória usado pelos PCs, funcionando sob o MS-DOS
 (Windows, OS/2, UNIX, etc... usam outro tipo de modelamento...
 MUITO MAIS COMPLICADO!).
 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 Modelamento REAL da memória - A segmentação ┃ ┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 A memória de qualquer PC é dividida em segmentos. Cada segmento
 tem 64k bytes de tamanho (65536 bytes) e por mais estranho que
 pareça os segmentos não são organizados de forma sequencial
 (o segmento seguinte não começa logo após o anterior!). Existe uma
 sobreposiçao. De uma olhada:
 64k (tamanho do segmento 0)
 ┌─────────────────────────────────────────────────────┐
 ┌─────────────────────────────────────────────────────┬───────
 │ ┊ ┊ ┊ ┊
 │ ┊ ┊ ┊ ┊
 │ ┊ ┊ ┊ ┊
 └─────────────────────────────────────────────────────┴───────
 0 1 2 ← Numero dos segmentos
 └─────────────┘
 16 16
 bytes bytes
 O segundo segmento começa exatamente 16 bytes depois do
 primeiro. Deu pra perceber que o inicio do segundo segmento está
 DENTRO do primeiro, já que os segmentos tem 64k de tamanho!
 Este esquema biruta confunde bastante os programadores menos
 experientes e, até hoje, ninguém sabe porque a Intel resolveu
 utilizar essa coisa esquisita. Mas, paciência, é assim que a coisa
 funciona!
 Para encontrarmos um determinado byte dentro de um segmento
 precisamos fornecer o OFFSET (deslocamento, em inglês) deste byte
 relativo ao inicio do segmento. Assim, se queremos localizar o
 décimo-quinto byte do segmento 0, basta especificar 0:15, ou seja,
 segmento 0 e offset 15. Esta notação é usada no restante deste e de
 outros artigos.
 Na realidade a CPU faz o seguinte cálculo para encontrar o
 "endereço físico" ou "endereço efetivo" na memória:
 ┌─────────────────────────────────────────────────────────────────┐
 │ ENDEREÇO-EFETIVO = (SEGMENTO ∙ 16) + OFFSET │
 └─────────────────────────────────────────────────────────────────┘
 Ilustrando a complexidade deste esquema de endereçamento,
 podemos provar que existem diversas formas de especificarmosum
 único "endereço efetivo" da memória... Por exemplo, o endereço
 0:13Ah pode ser também escrito como:
 0001h:012Ah 0002h:011Ah 0003h:010Ah 0004h:00FAh
 0005h:00EAh 0006h:00DAh 0007h:00CAh 0008h:00BAh
 0009h:00AAh 000Ah:009Ah 000Bh:008Ah 000Ch:007Ah
 000Dh:006Ah 000Eh:005Ah 000Fh:004Ah 0010h:003Ah
 0011h:002Ah 0012h:001Ah 0013h:000Ah
 Basta fazer as contas que você verá que todas estas formas darão
 o mesmo resultado: o endereço-efetivo 0013Ah. Generalizando,
 existem, no máximo, 16 formas de especificarmos o mesmo endereço
 efetivo! As únicas faixas de endereços que não tem equivalentes e só
 podem ser especificados de uma única forma são os desesseis
 primeiros bytes do segmento 0 e os últimos desesseis bytes do
 segmento 0FFFFh.
 Normalmente o programador não tem que se preocupar com esse tipo
 de coisa. O compilador toma conta da melhor forma de endereçamento.
 Mas, como a toda regra existe uma excessão, a informação acima pode
 ser útil algum dia.
 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 A BASE NUMÉRICA HEXADECIMAL E BINARIA (para os novatos...) ┃ ┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 Alguns talvez não tenham conhecimento sobre as demais bases
 numéricas usadas na área informata. É muito comum dizermos "código
 hexadecimal", mas o que significa?
 É bastante lógico que usemos o sistema decimal como base para
 todos os cálculos matemáticos do dia-a-dia pelo simples fato de
 temos DEZ dedos nas mãos... fica facil contar nos dedos quando
 precisamos.
 Computadores usam o sistema binário por um outro motimo simples:
 Existem apenas dois níveis de tensão presentes em todos os circuitos
 lógicos: níveis baixo e alto (que são chamados de 0 e 1, por
 conveniência... para podermos medi-los sem ter que recorrer a um
 multímetro!). O sistema hexadecimal também tem o seu lugar: é a
 forma mais abreviada de escrever um conjunto de bits.
 Em decimal, o número 1994, por exemplo, pode ser escrito como:
 1994 = (1 ∙ 10³) + (9 ∙ 10²) + (9 ∙ 10¹) + (4 ∙ 10 )⁰
 Note a base 10 nas potências. Faço agora uma pergunta: Como
 representariamos o mesmo númer se tivessemos 16 dedos nas mãos?
 Primeiro teriamos que obter mais digitos... 0 até 9 não são➠
 suficientes. Pegaremos mais 6 letras do alfabeto para suprir
 esta deficiencia.
 Segundo, Tomemos como inspiração um odômetro (equipamento➠
 disponível em qualquer automóvel - é o medidor de
 quilometragem!): Quando o algarismo mais a direita (o menos
 significativo) chega a 9 e é incrementado, o que ocorre?...
 Retorna a 0 e o próximo é incrementado, formando o 10. No
 caso do sistema hexadecimal, isto só acontece quando o último
 algarismo alcança F e é incrementado! Depois do 9 vem o A,
 depois o B, depois o C, e assim por diante... até chegar a
 vez do F e saltar para 0, incrementando o próximo algarismo,
 certo?
 Como contar em base diferente de dez é uma situação não muito
intuitiva, vejamos a regra de conversão de bases. Começaremos pela
base decimal para a hexadecimal. Tomemos o número 1994 como
exemplo. A regra é simples: Divide-se 1994 por 16 (base
hexadecimal) até que o quoeficiente seja zero... toma-se os restos
e tem-se o númer convertido para hexadecimal:
 ┌───────────────────────────────────────────────────────────────┐
 │ 1994 ÷ 16 Quociente = 124, Resto = 10 10=A │⇒ ⇒
 │ 124 ÷ 16 Quociente = 7, Resto = 12 12=C │⇒ ⇒
 │ 7 ÷ 16 Quociente = 0, Resto = 7 7=7 │⇒ ⇒
 └───────────────────────────────────────────────────────────────┘
 Toma-se então os restos de baixo para cima, formando o número em
hexadecimal. Neste caso, 1994=7CAh
 Acrescente um 'h' no fim do número para sabermos que se trata da
base 16, do contrário, se olharmos um número "7CA" poderiamos
associa-lo a qualquer outra base numérica (base octadecimal por
exemplo!)...
 O processo inverso, hexa → decimal, é mais simples... basta
escrever o númer, multiplicando cada digito pela potência correta,
levando-se em conta a equivalencia das letras com a base decimal:
 ┌────────────────────────────────────────────────────────────────┐
 │ 7CAh = (7 ∙ 16²) + (C ∙ 16¹) + (A ∙ 16 ) = │⁰
 │ (7 ∙ 16²) + (12 ∙ 16¹) + (10 ∙ 16 ) = │⁰
 │ 1792 + 192 + 10 = 1994 │
 └────────────────────────────────────────────────────────────────┘
 As mesmas regras podem ser aplicadas para a base binária (que
tem apenas dois digitos: 0 e 1). Por exemplo, o número 12 em
binário fica:
 ┌────────────────────────────────────────────────────────────────┐
 │ 12 ÷ 2 Quociente = 6, Resto = 0 │⇒
 │ 6 ÷ 2 Quociente = 3, Resto = 0 │⇒
 │ 3 ÷ 2 Quociente = 1, Resto = 1 │⇒
 │ 1 ÷ 2 Quociente = 0, Resto = 1 │⇒
 │ │
 │ 12 = 1100b │
 └────────────────────────────────────────────────────────────────┘
 Cada digito na base binária é conhecido como BIT (Binary digIT -
ou digito binário, em inglês). Note o 'b' no fim do número
convertido...
 Faça o processo inverso... Converta 10100110b para decimal.
 A vantagem de usarmos um número em base hexadecimal é que cada
digito hexadecimal equivale a exatamente quatro digitos binários!
Faça as contas: Quatro bits podem conter apenas 16 números (de 0 a
15), que é exatamente a quantidade de digitos na base hexadecimal.
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 02 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
Por: Frederico Pissarra
 Mais alguns conceitos são necessários para que o pretenso
programador ASSEMBLY saiba o que está fazendo. Em eletrônica
digital estuda-se a algebra booleana e aritimética com números
binários. Aqui esses conceitos também são importantes... Vamos
começar pela aritimética binária:
 A primeira operação básica - a soma - não tem muitos
mistérios... basta recorrer ao equivalente decimal. Quando somamos
dois números decimais, efetuamos a soma de cada algarismo em
separado, prestando atenção aos "vai um" que ocorrem entre um
algarismo e outro. Em binário fazemos o mesmo:
 ┌───────────────────────────────────────────────────────────────┐
 │ 1010b + 0110b = ? │
 │ │
 │ 111 ← "Vai uns" │
 │ 1010b │
 │ + 0110b │
 │ ───────── │
 │ 10000b │
 └───────────────────────────────────────────────────────────────┘
 Ora, na base decimal, quando se soma - por exemplo - 9 e 2, fica
1 e "vai um"... Tomemos o exemplo do odômetro (aquele indicador de
quilometragem do carro): 09 → 10 → 11
 Enquanto na base decimal existem 10 algarismos (0 até 9), na
base binária temos 2 (0 e 1). O odômetro ficaria assim:
00b → 01b → 10b → 11b
 Portanto, 1b + 1b = 10b ou, ainda, 0b e"vai um".
 A subtração é mais complicada de entender... Na base decimal
existem os números negativos... em binário não! (Veremos depois
como "representar" um número negativo em binário!). Assim, 1b - 1b
= 0b (lógico), 1b - 0b = 1b (outra vez, evidente!), 0b - 0b = 0b
(hehe... você deve estar achando que eu estou te sacaneando, né?),
mas e 0b - 1b = ?????
 A solução é a seguinte: Na base decimal quando subtraimos um
algarismo menor de outro maior costumamos "tomar um emprestado" para
que a conta fique correta. Em binário a coisa funciona do mesmo
jeito, mas se não tivermos de onde "tomar um emprestado" devemos
indicar que foi tomado um de qualquer forma:
 ┌───────────────────────────────────────────────────────────────┐
 │ 0b - 1b = ? │
 │ │
 │ 1 ← Tomamos esse um emprestado de algum lugar! │
 │ 0b (não importa de onde!) │
 │ - 1b │
 │ ────── │
 │ 1b │
 └───────────────────────────────────────────────────────────────┘
 Esse "1" que apareceu por mágica é conhecido como BORROW. Em um
número binário maior basta usar o mesmo artificio:
 ┌───────────────────────────────────────────────────────────────┐
 │ 1010b - 0101b = ? │
 │ │
 │ 1 1 ← Os "1"s que foram tomados emprestados são │
 │ 1010b subtraídos no proximo digito. │
 │ - 0101b │
 │ ───────── │
 │ 0101b │
 └───────────────────────────────────────────────────────────────┘
 Faça a conta: 0000b - 0001b, vai acontecer uma coisa
interessante! Faça a mesma conta usando um programa, ou calculadora
cientifica, que manipule números binários... O resultado vai ser
ligairamente diferente por causa da limitação dos digitos suportados
pelo software (ou calculadora). Deixo a conclusão do "por que"
desta diferença para você... (Uma dica, faça a conta com os "n"
digitos suportados pela calculadora e terá a explicação!).
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 Representando números negativos em binário ┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 Um artificio da algebra booleana para representar um número
interiro negativo é usar o último bit como indicador do sinal do
número. Mas, esse artificio gera uma segunda complicação...
 Limitemos esse estudo ao tamanho de um byte (8 bits)... Se o
bit 7 (a contagem começa pelo bit 0 - mais a direita) for 0 o número
representado é positivo, se for 1, é negativo. Essa é a diferença
entre um "char" e um "unsigned char" na linguagem C - ou um "char" e
um "byte" em PASCAL (Note que um "unsigned char" pode variar de 0
até 255 - 00000000b até 11111111b - e um "signed char" pode variar
de -128 até 127 - exatamenta a mesma faixa, porém um tem sinal e o
outro não!).
 A complicação que falei acima é com relação à representação dos
números negativos. Quando um número não é nagativo, basta
convertê-lo para base decimal que você saberá qual é esse número, no
entanto, números negativos precisam ser "complementados" para que
saibamos o número que está sendo representado. A coisa N─O funciona
da seguinte forma:
 ┌────────────────────────────────────────────────────────────────┐
 │ 00001010b = 10 │
 │ 10001010b = -10 (ERRADO) │
 └────────────────────────────────────────────────────────────────┘
 Não basta "esquecermos" o bit 7 e lermos o restante do byte. O
procedimento correto para sabermos que número está sendo
representado negativamente no segundo exemplo é:
 Inverte-se todos os bits e,➠
 Soma-se 1 ao resultado➠
 ┌───────────────────────────────────────────────────────────────┐
 │ 10001010b → 01110101b + 00000001b → 01110110b │
 │ 01110110b = 118 │
 │ Logo: │
 │ 10001010b = -118 │
 └───────────────────────────────────────────────────────────────┘
 Com isso podemos explicar a diferença entre os extremos da faixa
de um "signed char":
 Os números positivos contam de 00000000b até 01111111b, isto➠
 é, de 0 até 127.
 Os números negativos contam de 10000000b até 11111111b, isto➠
 é, de -128 até -1.
 Em "C" (ou PASCAL), a mesma lógica pode ser aplicada aos "int" e
"long" (ou INTEGER e LONGINT), só que a quantidade de bits será
maior ("int" tem 32 ou 16 bits de tamanho, de acordo com a 
arquitetura, e "long" tem 32).
 Não se preocupe MUITO com a representação de números negativos
em binário... A CPU toma conta de tudo isso sozinha... mas, as
vezes, você tem que saber que resultado poderá ser obtido de uma
operação aritimética em seus programas, ok?
 As outras duas operações matemáticas básicas (multiplicação e
divisão) tanbém estão presentes nos processadores 80x86... Mas, não
necessitamos ver como o processo é feito a nível binário. Confie na
CPU! :)
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 03 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
Por: Frederico Pissarra
 Comecemos a dar uma olhadela na arquitetura dos
microprocessadores da família INTEL 80x86... Vamos aos
registradores!
 Entenda os registradores como se fossem variáveis que o
microprocessador disponibiliza ao sistema. TODOS os registradores
têm 16 bits de tamanho e aqui vai a descrição deles:
 ┌──────┐
 │ AX │←─┐
 ├──────┤ │
 │ BX │←─┤
 ├──────┤ ├── Registradores de uso geral
 │ CX │←─┤
 ├──────┤ │
 │ DX │←─┘
 └──────┘
 ┌──────┐
 │ SI │←─── índice FONTE (Source Index)
 ├──────┤
 │ DI │←─── índice DESTINO (Destination Index)
 └──────┘
 ┌──────┐
 │ SP │←─── Apontador de pilha (Stack Pointer)
 ├──────┤
 │ BP │←─── Apontador de base (Base Pointer)
 └──────┘
 ┌──────┐
 │ CS │←─── Segmento de Cógido (Code Segment)
 ├──────┤
 │ DS │←─── Segmento de Dados (Data Segment)
 ├──────┤
 │ ES │←─── Segmento de dados Extra (Extra data Segment)
 ├──────┤
 │ SS │←─── Segmento de Pilha (Stack Segment)
 └──────┘
 ┌──────┐
 │ IP │←─── Apontador de instrução (Instruction Pointer)
 └──────┘
 ┌──────┐
 │Flags │←─── Sinalizadores
 └──────┘
 Por enquanto vamos nos deter na descrição dos registradores uso
geral... Eles podem ser subdivididos em dois registradore de oito
bits cada:
 AX (16 bits) BX (16 bits)
 ┌─────────────────┐ ┌─────────────────┐
 ┌────────┬────────┐ ┌────────┬────────┐
 │ AH │ AL │ │ BH │ BL │
 └────────┴────────┘ └────────┴────────┘
 15 8 7 0 15 8 7 0
 CX (16 bits) DX (16 bits)
 ┌─────────────────┐ ┌─────────────────┐
 ┌────────┬────────┐ ┌────────┬────────┐
 │ CH │ CL │ │ DH │ DL │
 └────────┴────────┘ └────────┴────────┘15 8 7 0 15 8 7 0
 AH é o byte mais significativo do registrador AX, enquanto que
AL é o menos significativo. Se alterarmos o conteúdo de AL,
estaremos alterando o byte menos significativo de AX ao mesmo
tempo... Não existem registradores de oito bits em separado...
tudo é uma coisa só. Portanto, ao manipularmos AH, estaremos
manipulando AX ao mesmo tempo!
 O nome de cada registrador tem o seu sentido de ser... "A" de
AX quer dizer que este registrador é um "acumulador" (usado por
default em algumas operações matematicas!), por exemplo...
 AX → Acumulador
 BX → Base
 CX → Contador
 DX → Dados
 O "X" de AX significa "eXtended". "H" de AH significa "High
byte".
 Embora estes registradores possam ser usados sem restrições, é
interessante atribuir uma função para cada um deles nos nossos
programas sempre que possível... Isto facilita a leitura do código
e nos educa a seguirmos uma linha de raciocínio mais concisa...
Mas, se for de sua preferência não seguir qualquer padrão no uso
desses registradores, não se preocupe... não haverá qualquer
desvantagem nisso (Well... depende do código, as vezes somos
obrigados a usar determinado registrador!).
 Alguns pontos importantes quanto a esses nomes serão observados
no decorrer do curso... Por exemplo, certas instruções usam AX (ou
AL, ou AH) e somente ele, não permitindo o uso de nenhum outro
registrador... Outras, usam CX para contar, etc... essas
instruções específicas serão vistas em outra oportunidade.
 Os registradores SI e DI são usados como índices para tabelas.
Em particular, SI é usado para leitura de uma tabela e DI para
escrita (fonte e destino... lembra algum procedimento de cópia,
nao?). No entanto, esses registradores podem ser usados com outras
finalidades... Podemos incluí-los no grupo de "registradores de uso
geral", mas assim como alguns registradores de uso geral, eles têm
aplicação exclusiva em algumas instruções, SI e DI são usados
especificamente como índices em instruções que manipulam blocos
(também veremos isso mais tarde!).
 Os registradores CS, DS, ES e SS armazenam os segmentos onde
estão o código (programa sendo executado), os dados, os dados
extras, e a pilha, respectivamente. Lembre-se que a memória é
segmentada em blocos de 64kbytes (dê uma olhada na primeira mensagem
dessa série).
 Quando nos referimos, através de alguma instrução, a um endereço
de memória, estaremos nos referindo ao OFFSET dentro de um segmento.
O registrador de segmento usado para localizar o dado no offset
especificado vai depender da própria instrução... Um exemplo em
assembly:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV AL,[1D4Ah] │
 └────────────────────────────────────────────────────────────────┘
 O número hexadecimal entre os colchetes é a indicação de um
offset em um segmento... Por default, a maioria das instruções usa
o segmento de dados (valor em DS). A instrução acima é equivalente
a:
 ┌────────────────────────────────────────────────────────────────┐
 │ AL = DS:[1D4Ah] │
 └────────────────────────────────────────────────────────────────┘
 Isto é, em AL será colocado o byte que está armazenado no offset
1D4Ah do segmento de dados (valor em DS). Veremos mais sobre os
segmentos e as instruções mais tarde :)
 Se quisessemos localizar o byte desejado em outro segmento (mas
no mesmo offset) devemos especificar o registrador de segmento na
instrução:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV AL,ES:[1D4Ah] │
 └────────────────────────────────────────────────────────────────┘
 Aqui o valor de ES será usado.
 O registrador IP (Instruction Pointer) é o offset do segmento de
código que contém a próxima instrução a ser execuatda. Este
registrador não é acessível por qualquer instrução (pelo menos não
pelas documentadas pela Intel)... é de uso interno do
microprocessador. No entanto existem alguns macetes para
conseguirmos obter o seu conteúdo (o que na maioria das aplicações
não é necessario... Para que conhecer o endereço da próxima
instrução se ela var ser executada de qualquer jeito?).
 O registrador SP é o offset do segmento SS (segmento de pilha)
onde o próximo dado vai ser empilhado. A pilha serve para armazenar
dados que posteriormente podem ser recuperados sem que tenhamos que
usar um dos registradores para esse fim. Também é usada para
armazenar o endereço de retorno das sub-rotinas. A pilha "cresce"
de cima para baixo, isto é, SP é decrementado cada vez que um novo
dado é colocado na pilha. Note também que existe um registrador de
segmento exclusivo para a pilha... SP sempre está relacionado a esse
segmento (SS), como foi dito antes.
 Para ilustrar o funcionamento da pilha, no gráfico abaixo
simularemos o empilhamento do conteúdo do registrador AX através da
instrução:
 ┌─────────────────────────────────────────────────────────────────┐
 │ PUSH AX │
 └─────────────────────────────────────────────────────────────────┘
 ┌─────────────────────────────────────────────────────────────────┐
 │ AX = A527h (Valor em AX) │
 │ │
 │ ┌───────┐ ┌───────┐ │
 │ │ ????h │←──── SP = n │ ????h │ │
 │ ├───────┤ ├───────┤ │
 │ │ │ │ A527h │←──── SP = n - 2 │
 │ └───────┘ └───────┘ │
 │ │
 │ (antes de PUSH AX) (depois de PUSH AX) │
 └─────────────────────────────────────────────────────────────────┘
 Observe que SP sempre aponta para o último dado empilhado.
 Na realidade SP é decrementado de duas posições ao invés de
apenas uma... mas, esse detalhe deixo para mais tarde.
 O registrador BP pode ser usado como apontador para a base da
pilha (já que, por default, está relacionado a SS) ou como um
registrador de uso geral... depende do seu programa. Veremos isso
detalhadamente mais tarde.
 Um dos registradores mais importantes de qualquer
microprocessador é o de "Flags". Eis uma descrição dos bits deste
registrador (a descrição abaixo aplica-se ao 8086. Normalmente não
acessamos diretamente o registrador de flags - embora possamos
fazê-lo - por isso não é conveniente assumirmos que os bits estão
sempre no mesmo lugar para qualquer microprocessador da família
80x86!):
 ┌────────────────────────────────────────────────────────────────┐
 │ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ │
 │ │ │ │ │ │O│D│I│T│S│Z│ │A│ │P│ │C│ │
 │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ │
 │ 15 0 │
 │ │
 │ C = Carry │
 │ P = Parity │
 │ A = Auxiliar Carry │
 │ Z = Zero │
 │ S = Signal │
 │ T = Trap│
 │ I = Interrupt Enable Flag │
 │ D = Direction │
 │ O = OverFlow │
 └────────────────────────────────────────────────────────────────┘
 Carry:➠
 Esse flag é setado sempre quando houver "vai um" depois de
 uma adição ou quando há BORROW depois de uma subtração. Ou
 quando, numa operação de deslocamento de bits, o bit mais ao
 extremo for deslocado para fora do dado (suponha um byte... se
 todos os bits forem deslocados em uma posição para a direita, o
 que acontece com o bit 0?... Resposta: Vai para o carry!)
 Parity:➠
 Depois de uma instrução aritimética ou lógica este bit
 informa se o resultado tem um número par de "1"s ou não.
 Auxiliar Carry:➠
 Igual ao carry, mas indica o "vai um" no meio de um dado (no
 caso de um byte, se houve "vai um" do bit 3 para o bit 4!).
 Zero:➠
 Depois de uma operação aritimética ou lógica, esse flag
 indica se o resultado é zero ou não.
 Signal:➠
 Depois de uma instrução aritimética ou lógica, este flag é
 uma cópia do bit de mais alta ordem do resultado, isto é, seu
 sinal (dê uma olhada na "representação de números negativos em
 binário" no texto anterior!).
 Trap:➠
 Quando setado (1) executa instruções passo-a-passo... Não
 nos interessa estudar esse bit por causa das diferenças de
 implementação deste flag em toda a família 80x86.
 Interrupt Enable Flag➠
 Habilita/Desabilita o reconhecimento de interrupções
 mascaráveis pela CPU. Sobre interrupções, veremos mais tarde!
 Direction:➠
 Quando usamos instruções de manipulação de blocos,
 precisamos especificar a direção que usaremos (do inicio para o
 fim ou do fim para o inicio).
 Quando D=0 a direção é a do início para o fim... D=1, então
 a direção é contrária!
 OverFlow:➠
 Depois de uma instrução aritimética ou lógica, este bit
 indica se houve mudança no bit mais significativo, ou seja, no
 sinal. Por exemplo, se somarmos FFFFh + 0001h obteremos 00h. O
 bit mais significativo variou de 1 para 0 (o counteúdo inicial
 de um registrador era FFFFh e depois da soma foi para 0000h),
 indicando que o resultado saiu da faixa (overflow) - ora, FFFFh
 + 0001h = 10000h, porém um registrador tem 16 bits de tamanho e
 o resultado cabe em 17 bits. Neste exemplo, o bit de carry
 também será setado pois houve "vai um" do bit 15 para o
 inexistente bit 16, mas não confunda o flag de overflow com o
 carry!
 Quando aos demais bits, não se pode prever seus estados lógicos
(1 ou 0).
 Na próxima "aula" começaremos a ver algumas instruções do
microprocessador 8086. Ainda não escreveremos nenhum programa, a
intenção é familiarizá-lo com a arquitetura do microprocessador
antes de começarmos a colocar a mão na massa... tenha um pouco de
paciência! :)
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 04 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
Por: Frederico Pissarra
 Começaremos a ver algumas instruções do microprocessador 8086
agora. Existem os seguintes tipos de instruções:
 Instruções Aritiméticas➠
 Instruções Lógicas➠
 Instruções de Controle de Fluxo de Programa➠
 Instruções de manipulação de flags➠
 Instruções de manipulação da pilha➠
 Instruções de manipulação de blocos➠
 Instruções de manipulação de registradores/dados➠
 Instruções de Entrada/Saída➠
 Vamos começar com as instruções de manipulação de
registradores/dados por serem estas as mais fáceis de entender.
┏━━━━━━━━━━━━━━━┓
 Instrução MOV ┃ ┃
┗━━━━━━━━━━━━━━━┛
 MOV tem a finalidade de MOVimentar um dado de um lugar para
 outro. Por exemplo, para carregar um registrador com um determinado
 valor. Isto é feito com MOV:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV AX,0001h │
 └────────────────────────────────────────────────────────────────┘
 É a mesma coisa que dizer: "AX = 1". Na verdade, movimentamos o
 valor 1 para dentro do registrador AX.
 Podemos mover o conteúdo de um registrador para outro:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV BH,CL │
 └────────────────────────────────────────────────────────────────┘
 É a mesma coisa que "BH = CL"!
 Os registradores de segmento não podem ser inicializados com MOV
 tomando um parametro imediato (numérico). Esses registradores são
 inicializados indiretamente:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV DS,0 ; ERRADO!!! │
 │ │
 │ MOV AX,0 │
 │ MOV DS,AX ; CORRETO! │
 └────────────────────────────────────────────────────────────────┘
 Carregar um registrador com o conteúdo (byte ou word, depende da
 instrução!) armazenado em um segmento é simples, basta especificar o
 offset do dado entre colchetes. Atenção que o segmento de dados
 (DS) é assumido por default com algumas excessões:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV AL,[0FFFFh] │
 └────────────────────────────────────────────────────────────────┘
 A instrução acima, pega o byte armazenado no endereço DS:FFFFh e
 coloca-o em AL. Sabemos que um byte vai ser lido do offset
 especificado porque AL tem 8 bits de tamanho.
 Ao invés de usarmos um offset imediato podemos usar um
 registrador:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV BX,0FFFFh │
 │ MOV CH,[BX] │
 └────────────────────────────────────────────────────────────────┘
 Neste caso, BX contém o offset e o byte no endereço DS:BX é
 armazenado em CH. Note que o registrador usado como indice
 obrigatoriamente deve ser de 16 bits.
 Uma observação quanto a essa modalidade: Dependendo do
 registrador usado como offset, o segmento default poderá ser DS ou
 SS. Se ao invés de BX usassemos BP, o segmento default seria SS e
 não DS - de uma olhada no diagrama de distribuição dos registradores
 no texto anterior. BP foi colocado no mesmo bloco de SP, indicando
 que ambos estão relacionados com SS (Segmento de pilha) - Eis uma
 tabela das modalidades e dos segmentos default que podem ser usados
 como offset:
 ┌─────────────────────────────┬─────────────────────────────────┐
 │ Offset usando registros │ Segmento default │
 ├─────────────────────────────┼─────────────────────────────────┤
 │ [SI + deslocamento] │ DS │
 │ [DI + deslocamento] │ DS │
 │ [BP + deslocamento] │ SS │
 │ [BX + deslocamento] │ DS │
 │ [BX + SI + deslocamento] │ DS │
 │ [BX + DI + deslocamento] │ DS │
 │ [BP + SI + deslocamento] │ SS │
 │ [BP + DI + deslocamento] │ SS│
 └─────────────────────────────┴─────────────────────────────────┘
 O "deslocamento" pode ser suprimido se for 0.
 Você pode evitar o segmento default explicitando um registrador
 de segmento na instrução:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV DH,ES:[BX] ;Usa ES ao invés de DS │
 │ MOV AL,CS:[SI + 4] ;Usa CS ao invés de DS │
 └────────────────────────────────────────────────────────────────┘
 Repare que tenho usado os registradores de 8 bits para armazenar
 os dados... Pode-se usar os de 16 bits também:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV ES:[BX],AX ; Poe o valor de AX para ES:BX │
 └────────────────────────────────────────────────────────────────┘
 Só que neste caso serão armazenados 2 bytes no endereço ES:BX.
 O primeiro byte é o menos significativo e o segundo o mais
 signigicativo. Essa instrução equivale-se a:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV ES:[BX],AL ; Instruçõess que fazem a mesma │
 │ MOV ES:[BX + 1],AH ;coisa que MOV ES:[BX],AX │
 └────────────────────────────────────────────────────────────────┘
 Repare também que não é possível mover o conteúdo de uma posição
 da memória para outra, diretamente, usando MOV. Existe outra
 instrução que faz isso: MOVSB ou MOVSW. Veremos essas instruções
 mais tarde.
 Regra geral: Um dos operandos TEM que ser um registrador! Salvo
 no caso da movimentação de um imediato para uma posição de memória:
 ┌───────────────────────────────────────────────────────────────┐
 │ MOV [DI],[SI] ; ERRO! │
 │ MOV [BX],0 ; OK! │
 └───────────────────────────────────────────────────────────────┘
 Para ilustrar o uso da instrução MOV, eis um pedaço do código
 usado pela ROM-BIOS do IBM PS/2 Modelo 50Z para verificar a
 integridade dos registradores da CPU:
 ┌────────────────────────────────────────────────────────────────┐
 │ ... │
 │ MOV AX,0FFFFh ;Poe 0FFFFh em AX │
 │ MOV DS,AX │
 │ MOV BX,DS │
 │ MOV ES,BX │
 │ MOV CX,ES │
 │ MOV SS,CX │
 │ MOV DX,SS │
 │ MOV SI,DX │
 │ MOV DI,SI │
 │ MOV BP,DI │
 │ MOV SP,BP │
 │ ... │
 └────────────────────────────────────────────────────────────────┘
 Se o conteúdo de BP não for 0FFFFh então a CPU está com algum
 problema e o computador não pode funcionar! Os flags são testados
 de uma outra forma... :)
 ┏━━━━━━┓
 XCHG ┃ ┃
 ┗━━━━━━┛
 Esta instrução serve para trocarmos o conteúdo de um registrador
 pelo outro. Por exemplo:
 ┌────────────────────────────────────────────────────────────────┐
 │ XCHG AH,AL │
 └────────────────────────────────────────────────────────────────┘
 Se AH=1Ah e AL=6Dh, após esta instrução AH=6Dh e AL=1Ah por
 causa da troca...
 Pode-se usar uma referência à memória assim como em MOV... com
 a mesma restrição de que um dos operandos TEM que ser um
 registrador. Não há possibilidade de usar um operando imediato.
 ┏━━━━━━━━━━━━━━━┓
 MOVSB e MOUSW ┃ ┃
 ┗━━━━━━━━━━━━━━━┛
 Essas instruções suprem a deficiência de MOV quanto a
 movimentação de dados de uma posição de memória para outra
 diretamente. Antes de ser chamada os seguintes registradores tem
 que ser inicializados:
 ┌───────────────────────────────────────────────────────────────┐
 │ DS:SI ← DS e SI têm o endereço fonte │
 │ ES:DI ← ES e DI têm o endereço destino │
 └───────────────────────────────────────────────────────────────┘
 Dai podemos executar MOVSB ou MOVSW.
 MOVSB move um byte, enquanto MOVSW move um word (16 bits).
 Os registradores SI e DI sao incrementados ou decrementados de
 acordo com o flag D (Direction) - Veja discussão sobre os flags na
 mensagem anterior. No caso de MOVSW, SI e DI serao incrementados
 (ou decrementados) de 2 posições de forma que DS:SI e ES:DI apontem
 sempre para a próxima word.
 ┏━━━━━━━━━━━━━━━┓
 STOSB e STOSW ┃ ┃
 ┗━━━━━━━━━━━━━━━┛
 Essas instruções servem para armazenar um valor que está em AX
 ou AL (dependendo da instrução usada) no endereço apontado por
 ES:DI. Então, antes de ser chamada, os seguintes registradores
 devem ser inicializados:
 ┌────────────────────────────────────────────────────────────────┐
 │ AX → Valor a ser armazenado se usarmos STOSW │
 │ AL → Valor a ser armazenado se usarmos STOSB │
 │ ES:DI → Endereço onde o dado será armazenado │
 └────────────────────────────────────────────────────────────────┘
 Depois da execução da instrução o registrador DI será
 incrementado ou decrementado de acordo com o flag D (Direction). DI
 será incrementado de 2 no case de usarmos STOSW, isto garante que
 ES:DI aponte para a proxima word.
 ┏━━━━━━━━━━━━━━━┓
 LODSB e LODSW ┃ ┃
 ┗━━━━━━━━━━━━━━━┛
 Essas instruções servem para ler um valor que está no endereço
 apontado por DS:SI e armazená-lo em AX ou AL (dependendo da
 instrução usada). Então, antes de ser chamada, os seguintes
 registradores devem ser inicializados:
 ┌────────────────────────────────────────────────────────────────┐
 │ DS:SI → Endereço de onde o dado será lido │
 └────────────────────────────────────────────────────────────────┘
 Depois da execução da instrução o registrador SI será
 incrementado ou decrementado de acordo com o flag D (Direction). No
 caso de usarmos LODSW, SI será incrementado de 2 para garantir que
 DS:SI aponte para a próxima word.
 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 Outras instruções de manipulação de registros/dados ┃ ┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 Existem ainda as instruções LEA, LES e LDS.
 LEA:➠
 LEA é, basicamente, igual a instrução MOV, com apenas uma
 diferença: o operando "fonte" é um endereço (precisamente: um
 "offset"). LEA simplesmente calcula o endereço e transfere para o
 operando "destino", de forma que as instruções abaixo sao
 equivalentes:
 ┌────────────────────────────────────────────────────────────────┐
 │ MOV BX,100h │
 │ LEA BX,[100h] │
 └────────────────────────────────────────────────────────────────┘
 Porém, a instrução:
 ┌────────────────────────────────────────────────────────────────┐
 │ LEA DX,[BX + SI + 10h] │
 └────────────────────────────────────────────────────────────────┘
 Equivale a:
 ┌────────────────────────────────────────────────────────────────┐│ MOV DX,BX │
 │ ADD DX,SI ; DX = DX + SI │
 │ ADD DX,10h ; DX = DX + 10h │
 └────────────────────────────────────────────────────────────────┘
 Repare que apenas uma instrução faz o serviço de três!! Nos
 processadores 286 e 386 a diferença é significativa, pois, no
 exemplo acima, LEA gastará 3 (nos 286) ou 2 (nos 386) ciclos de
 máquina enquando o equivalente gastará 11 (nos 286) ou 6 (nos 386)
 ciclos de máquina! Nos processadores 8088/8086 a diferença não é
 tao grande...
 Obs:
 Consideremos cada ciclo de máquina seria, aproximadamente,
 num 386DX/40, algo em torno de 300ns - ou 0,0000003s. É uma
 medida empirica e não expressa a grandeza real (depende de
 uma série de fatores não considerados aqui!).
 O operando "destino" é sempre um registrador. O operando
 "fonte" é sempre um endereço.
 LDS e LES➠
 Existe uma forma de carregar um par de registradores
 (segmento:offset) de uma só vez. Se quisermos carregar DS:DX basta
 usar a instrução LDS, caso o alvo seja ES, usa-se LES.
 Suponhamos que numa posição da memória tenhamos um double word
 (número de 32 bits) armazenado. A word mais significativa
 correspondendo a um segmento e a menos signigicativa a um offset
 (esse é o caso da tabela dos vetores de interrupção, que descreverei
 com poucos detalhes em uma outra oportunidade!). Se usamos:
 ┌────────────────────────────────────────────────────────────────┐
 │ LES BX,[SI] │
 └────────────────────────────────────────────────────────────────┘
 O par ES:BX será carregado com o double word armazenado no
 endereço apontado por DS:SI (repare no segmento default que
 discutimos em um texto anterior!). A instrução acima é equivalente
 a:
 ┌───────────────────────────────────────────────────────────────┐
 │ MOV BX,[SI+2] │
 │ MOV ES,BX │
 │ MOV BX,[SI] │
 └───────────────────────────────────────────────────────────────┘
 De novo, uma instrução substitui três!
 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 Manipulando blocos... parte I ┃ ┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 As instruções MOVSB, MOVSW, STOSB, STOSW, LODSB e LODSW podem
 ser usadas para lidar com blocos de dados. Para isto, basta indicar
 no registrador CX a quantidade de dados a serem manipulados e
 acrescentar REP na frente da instruçao. Eis um trecho de uma
 pequena rotina que apaga o video em modo texto (80 x 25 colorido):
 ┌───────────────────────────────────────────────────────────────┐
 │ MOV AX,0B800h │
 │ MOV ES,AX ; Poe em ES o segmento do vídeo │
 │ MOV DI,0 ; Começa no Offset 0 │
 │ MOV AH,7 ; AH = atributo do caracter │
 │ ; 7 = cinza com fundo preto │
 │ MOV AL,' ' ; AL = caracter usado para apagar │
 │ MOV CX,2000 ; CX = contador (4000 bytes ou │
 │ ; 2000 words). │
 │ REP STOSW ; Preenche os 2000 words com AX │
 └───────────────────────────────────────────────────────────────┘
 O modificador REP diz a instrução que esta deve ser executada CX
 vezes. Note que a cada execução de STOSW o registrador DI apontará
 para a proxima word.
 Suponha que queiramos mover 4000 bytes de alguma posição da
 memória para o video, preenchendo a tela com esses 4000 bytes:
 ┌───────────────────────────────────────────────────────────────┐
 │ MOV AX,0B800h │
 │ MOV ES,AX ; Poe em ES o segmento do vídeo │
 │ MOV AX,SEG TABELA │
 │ MOV DS,AX ; Poe em DS o segmento da tabela │
 │ MOV SI,OFFSET TABELA ; Começa no offset inicial da tabela │
 │ MOV DI,0 ; Começa no Offset 0 │
 │ MOV CX,4000 ; CX = contador (4000 bytes) │
 │ REP MOVSB ; Copia 4000 bytes de DS:SI para ES:DI │
 └───────────────────────────────────────────────────────────────┘
 Nota: O modificador REP só pode ser preceder as seguintes
instruções: MOVSB, MOVSW, STOSB, STOSW, LODSB, LODSW, CMPSB, CMPSW,
SCASB, SCASW, OUTSB, OUTSW, INSB, INSW. As instruções nao vistas no
texto acima serão detalhadas mais tarde...
 Existem mais algumas instruções de manipulação de
registradores/dados, bem como mais algumas de manipulação de blocos.
Que ficarão para uma próxima mensagem.
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 05 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 Depois de algumas instruções de movimentação de dados vou
mostrar a mecânica da lógica booleana, bem como algumas instruções.
 A lógica booleana baseia-se nas seguintes operações: AND, OR,
NOT. Para simplificar a minha digitação vou usar a notação
simplificada: & (AND), | (OR) e ~ (NOT). Essa notação é usada na
linguagem C e em muitos manuais relacionados a hardware da IBM.
 Operação AND:➠
 A operação AND funciona de acordo com a seguinte tabela-verdade:
 ┌───────────┐
 │ S = A & B │
 ├───┬───┬───┤
 │ A │ B │ S │
 ├───┼───┼───┤
 │ 0 │ 0 │ 0 │
 │ 0 │ 1 │ 0 │
 │ 1 │ 0 │ 0 │
 │ 1 │ 1 │ 1 │
 └───┴───┴───┘
 Note que o resultado (S) será 1 apenas se A "E" B forem 1.
 Aplicando esta lógica bit a bit em operações envolvendo dois
bytes obteremos um terceiro byte que será o primeiro AND o segundo:
 ┌────────────────────────────────────────────────────────────────┐
 │ A = 01010111b B = 00001111b │
 │ │
 │ S = A & B → 01010111b │
 │ & 00001111b │
 │ ───────────── │
 │ 00000111b │
 └────────────────────────────────────────────────────────────────┘
 Uma das utilidades de AND é resetar um determinado bit sem
afetar os demais. Suponha que queira resetar o bit 3 de um
determinado byte. Para tanto basta efetuar um AND do byte a ser
trabalhado com o valor 11110111b (Apenas o bit 3 resetado).
 Eis a sintaxe da instrução AND:
 ┌───────────────────────────────────────────────────────────────┐
 │ AND AL,11110111b │
 │ AND BX,8000h │
 │ AND DL,CL │
 │ AND [DI],AH │
 └───────────────────────────────────────────────────────────────┘
 Lembrando que o operando destino (o mais a esquerda) deve sempre
ser um registrador ou uma referenciaa memória. o operando a
direita (fonte) pode ser um registrador, uma referência a memória ou
um valor imediato, com a restrição de que não podemos usar
referências a memória nos dois operandos.
 A instrução AND afeta os FLAGS Z, S e P e zera os flags Cy
(Carry) e O (veja os flags em alguma mensagem anterior a esta).
 Operação OR:➠
 ┌───────────┐
 │ S = A | B │
 ├───┬───┬───┤
 │ A │ B │ S │
 ├───┼───┼───┤
 │ 0 │ 0 │ 0 │
 │ 0 │ 1 │ 1 │
 │ 1 │ 0 │ 1 │
 │ 1 │ 1 │ 1 │
 └───┴───┴───┘
 Note que S será 1 se A "OU" B forem 1.
 Da mesma forma que AND, aplicamos essa lógica bit a bit
envolvendo um byte ou word através de uma instrução em assembly.
Vejamos um exemplo da utilidade de OR:
 ┌────────────────────────────────────────────────────────────────┐
 │ A = 01010111b B = 10000000b │
 │ │
 │ S = A | B → 01010111b │
 │ | 10000000b │
 │ ───────────── │
 │ 11010111b │
 └────────────────────────────────────────────────────────────────┘
 A operação OR é ideal para setarmos um determinado bit sem
afetar os demais. No exemplo acima B tem apenas o bit 7 setado...
depois da operação OR com A o resultado final foi A com o bit 7
setado! :)
 A sintaxe de OR é a mesma que a de AND (obviamente trocando-se
AND por OR). Os flags afetados são os mesmos da instrução AND!
 Operação NOT:➠
 NOT simplesmente inverte todos os bits de um byte ou word:
 ┌───────────┐
 │ S = ~A │
 ├─────┬─────┤
 │ A │ S │
 ├─────┼─────┤
 │ 0 │ 1 │
 │ 1 │ 0 │
 └─────┴─────┘
 A sintaxe da instrução em assembly é a seguinte:
 ┌───────────────────────────────────────────────────────────────┐
 │ NOT AL │
 │ NOT DX │
 │ NOT [SI] │
 └───────────────────────────────────────────────────────────────┘
 Operação XOR:➠
 A operação XOR é derivada das três acima. A equação booleana
que descreve XOR é:
 ┌────────────────────────────────────────────────────────────────┐
 │ S = (A AND ~B) OR (~A AND B) = A ^ B │
 └────────────────────────────────────────────────────────────────┘
 Que na tabela-verdade fica:
 ┌───────────┐
 │ S = A ^ B │
 ├───┬───┬───┤
 │ A │ B │ S │
 ├───┼───┼───┤
 │ 0 │ 0 │ 0 │
 │ 0 │ 1 │ 1 │
 │ 1 │ 0 │ 1 │
 │ 1 │ 1 │ 0 │
 └───┴───┴───┘
 Uso o simbolo ^ para o XOR aqui. XOR funciona da mesma forma
que OR, só que o resultado será 1 se APENAS A ou APENAS B for 1,
melhor dizendo: Se ambos forem diferentes.
 XOR é muito útil quando se quer inverter um determinado bit de
um byte ou word sem afetar os outros:
 ┌────────────────────────────────────────────────────────────────┐
 │ A = 01010111b B = 00001111b │
 │ │
 │ S = A ^ B → 01010111b │
 │ ^ 00001111b │
 │ ───────────── │
 │ 01011000b │
 └────────────────────────────────────────────────────────────────┘
 No exemplo acima invertemos apenas os quatro bits menos
significativos de A.
 A sintaxe e os flags afetados são os mesmos que AND e OR.
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 06 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 Instruções aritiméticas são o tópico de hoje. Já discuti,
brevemente, os flags e os sistemas de numeração. Aqui vai uma
aplicação prática:
 Soma:➠
 A soma é feita através das instruções ADD e ADC. A diferença
entre elas é que uma faz a soma normalmente e a outra faz a mesma
coisa acrecentando o conteúdo do flag CARRY. Eis a sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ ADD AL,10h │
 │ ADC AH,22h │
 │ │
 │ ADD AX,2210h │
 └─────────────────────────────────────────────────────────────────┘
 As duas primeiras instruções fazem exatamente a mesma coisa que
a terceira. Note que na primeiria somamos AL com 10h e o resultado
ficará em AL (se ocorrer "vai um" nesta soma o flag CARRY será
setado). A segunda instrução soma AH com 22h MAIS o carry
resultante da primeira instrução e o resultado ficará em AH
(novamente setando o flag carry se houver outro "vai um"!). A
terceira instrução faz a mesma coisa porque soma 2210h a AX, ficando
o resultado em AX e o possível "vai um" no carry.
 Todos os flags são afetados após a execução de uma das
instruções de soma, exceto: I, D e Trap.
 Subtração➠
 Semelhante as instruções de soma, existem duas instruções de
subtração: SUB e SBB. A primeira faz a subtração simples e a
segunda faz a mesma coisa subtraindo também o conteúdo prévio do
flag CARRY (como é uma subtração o CARRY é conhecido como BORROW!).
 A sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ SUB AL,1 │
 │ SBB AH,0 │
 │ │
 │ SUB AX,1 │
 └─────────────────────────────────────────────────────────────────┘
 Como no exemplo anterior, as duas primeiras instruções fazem
exatamente o que a terceira faz... Os flags afetados seguem a mesma
regra das instruções de soma!
 Incremento e decremento:➠
 As instruções INC e DEC são usadas no lugar de ADD e SUB se
quisermos incrementar ou decrementar o conteúdo de algum registrador
(ou de uma posição de memória) de uma unidade. A sintaxe é simples:
 ┌───────────────────────────────────────────────────────────────-┐
 │ DEC AX │
 │ INC BL │
 └───────────────────────────────────────────────────────────────-┘
 Os flags afetados seguem a mesma regra de uma instrução de soma
ou uma de subtração!
 Multiplicação:➠
 Os processadores da família 80x86 possuem instruções de
multiplicação e divisão inteiras (ponto flutuante fica pro 8087).
Alguns cuidados devem ser tomados quando usarmos uma instrução de
divisão (que será vista mais adiante!).Uma coisa interessante com a multiplicação é que se
multiplicarmos dois registradores de 16 bits obteremos o resultado
necessariamente em 32 bits. O par de registradores DX e AX são
usados para armazenar esse número de 32 bits da seguinte forma: DX
será a word mais significativa e AX a menos significativa.
 Por exemplo, se multiplicarmos 0FFFFh por 0FFFFh obteremos:
0FFFE0001h (DX = 0FFFEh e AX = 0001h).
 Eis a regra para descobrir o tamanho do restultado de uma
operação de multiplicação:
 ┌─────────────────────────────────┐
 │ A * B = M │
 ├──────────┬───────────┬──────────┤
 │ A │ B │ M │
 ├──────────┼───────────┼──────────┤
 │ 8 bits │ 8 bits │ 16 bits │
 │ │ │ │
 │ 16 bits │ 16 bits │ 32 bits │
 └──────────┴───────────┴──────────┘
 A multiplicação sempre ocorrerá entre o acumulador (AL ou AX) e
um outro operando. Eis a sintaxe das instruções:
 ┌───────────────────────────────────────────────────────────────-┐
 │ MUL BL ; AX = AL * BL │
 │ IMUL CX ; DX:AX = AX * CX │
 └───────────────────────────────────────────────────────────────-┘
 A primeira instrução (MUL) não considera o sinal dos operandos.
Neste caso, como BL é de 8 bits, a multiplicação se dará entre BL e
AL e o resultado será armazenado em AX.
 A segunda instrução leva em consideração o sinal dos operandos
e, como CX é de 16 bits, a multiplicação se dará entre CX e AX e o
restultado será armazenado em DX e AX. Lembrando que o sinal de um
número inteiro depende do seu bit mais significativo!
 Divisão:➠
 Precisamos tomar cuidado com a divisão pelo seguinte motivo: Se
o resultado não couber no registrador destino, um erro de "Division
by zero" ocorrerá (isto não está perfeitamente documentado nos
diversos manuais que li enquanto estudava assembly 80x86... Vim a
descobrir este 'macete' numa antiga edição da revista PC MAGAZINE
americana). Outro cuidado é com o divisor... se for 0 o mesmo erro
ocorrerá!
 A divisão pode ser feita entre um número de 32 bits e um de 16
ou entre um de 16 e um de 8, veja a tabela:
 ┌────────────────────────────────┐
 │ A ÷ B = Q e resto │
 ├──────────┬───────────┬─────────┤
 │ A │ B │Q e resto│
 ├──────────┼───────────┼─────────┤
 │ 32 bits │ 16 bits │ 16 bits │
 │ │ │ │
 │ 16 bits │ 8 bits │ 8 bits │
 └──────────┴───────────┴─────────┘
 Assim como na multiplicação o número (dividendo) de 32 bits é
armazenado em DX e AX.
 Depois da divisão o quociente é armazenado em AL e o resto em AH
(no caso de divisão 16/8 bits) ou o quociente fica em AX e o resto
em DX (no caso de divisão 32/8 bits).
 Exemplo da sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ DIV CX ; AX = DX:AX ÷ CX, DX = resto │
 │ IDIV BL ; AL = AX ÷ BL, AH = resto │
 └─────────────────────────────────────────────────────────────────┘
 O primeiro caso é uma divisão sem sinal e o segundo com sinal.
Note os divisores (CX e BL no nosso exemplo).
 Na divisão 16/8 bits o dividendo é armazenado em AX antes da
divisão... No caso de 32/8 bits DX e AX são usados...
 Mais um detalhe: Os flags, depois de uma multiplicação ou
divisão não devem ser considerados.
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 07 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 Algumas instruções afetam somente aos flags. Dentre elas, as
mais utilizadas são as instruções de comparação entre dois dados.
 Comparações aritiméticas:➠
 A instrução CMP é usada quando se quer comparar dois dados,
afetando somente aos flags. Eis a sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ CMP AL,1Fh │
 │ CMP ES:[DI],1 │
 │ CMP AX,[SI] │
 └─────────────────────────────────────────────────────────────────┘
 Esta instrução faz a subtração entre o operando mais a esquerda
e o mais a direita, afetando somente os flags. Por exemplo, se os
dois operandos tiverem valores iguais a subtração dará valores
iguais e o flag de ZERO será 1. Eis a mecânica de CMP:
 ┌─────────────────────────────────────────────────────────────────┐
 │ CMP AL,1Fh ; AL - 1Fh, afetando somente os Flags │
 └─────────────────────────────────────────────────────────────────┘
 Comparações lógicas:➠
 A instrução TEST é usada quando se quer comparar o estado de
determinados bits de um operando. Eis a sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ TEST AL,10000000b │
 │ TEST [BX],00001000b │
 └─────────────────────────────────────────────────────────────────┘
 Esta instrução faz um AND com os dois operados, afetando apenas
os flags. Os flags Z, S e P são afetados, os flags O e C serão
zerados.
 Instruções que mudam o estado dos flags diretamente:➠
 CLC - Abreviação de CLear Carry (Zera o flag Carry).
 CLD - Abreviação de CLear Direction (Ajusta flag de direção em
 zero, especificando o sentido correto para instruções de
 bloco).
 CLI - Abreviação de CLear Interrupt (Mascara flag de
 interrupção, não permitindo que a CPU reconheça as
 interrupções mascaráveis).
 CMC - Abreviação de CoMplement Carry (Inverte o flag de carry).
 STC - Abreviação de SeT Carry (Faz carry = 1).
 STD - Abreviação de SeT Direction (flag de direção setado -
 indica que as instruções de bloco incrementarão os seus
 pointers no sentido contrário - de cima para baixo).
 STI - Abreviação de SeT Interrupt (Faz com que a CPU volte a
 reconhecer as interrupções mascaráveis).
 Uma interrupção é um "desvio" feito pela CPU quando um
dispositivo requer a atenção da mesma. Por exemplo, quando você
digita uma tecla, o circuito do teclado requisita a atenção da CPU,
que por sua vez, para o que está fazendo e executa uma rotina
correspondente à requisição feita pelo dispositivo (ou seja, a
rotina da interrupção). Ao final da rotina, a CPU retorna à tarefa
que estava desempenhando antes da interrupção. Nos PCs, TODAS as
interrupções são mascaráveis (podem ser ativadas e desativadas
quando quisermos), com a única excessão da interrupçao de checagem
do sistema (o famoso MEMORY PARITY ERROR é um exeplo!).
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 08 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 Veremos agora as instruções de controle de fluxo de programa.
 A CPU sempre executa instruções em sequência, a não ser que
encontre instruções que "saltem" para outra posição na memória.
 Existem diversas formas de "saltar" para um determinado
endereço:
 Salto incondicional:➠
 A instrução JMP simplesmente salta para onde se quer. Antes de
apresentar a sintaxe, um detalhe sobrecodificaçao: O operando da
instrução JMP é um endereço na memória, mas, como usaremos sempre um
compilador assembly, necessitamos criar um "rotulo" ou "label" para
onde o salto será efetuado... O compilador trata de calcular o
endereço pra gente.
 Eis a sintaxe de JMP:
 ┌─────────────────────────────────────────────────────────────────┐
 │ JMP Aqui2 │
 │ Aqui1: │
 │ JMP Aqui3 │
 │ Aqui2: │
 │ JMP Aqui1 │
 │ Aqui3: │
 └─────────────────────────────────────────────────────────────────┘
 Os "labels" são sempre seguidos de dois-pontos. Note, no pedaço
de código acima, a quebra da sequência de execução.
 Salto incondicional:➠
 Diferente de JMP, temos instruções que realizam um salto somente
se uma condição for satisfeita. Para isso, usa-se os flags. A
sintaxe dessas instruções depende da condição do flag que se quer
testar. Eis a listagem dessas instruções:
 - JZ "label" → Salta se flag Z=1
 - JNZ "label" → Salta se flag Z=0
 - JC "label" → Salta se flag C=1
 - JNC "label" → Salta se flag C=0
 - JO "label" → Salta se flag O=1
 - JNO "label" → Salta se flag O=0
 - JPO "label" → Salta se flag P=0 (paridade impar)
 - JPE "label" → Salta se flag P=1 (paridade par)
 - JS "label" → Salta se flag S=1
 - JNS "label" → Salta se flag S=0
 Existem ainda mais saltos condicionais para facilitar a vida do
programador:
 - JE "label" → Jump if Equal (mesmo que JZ)
 - JNE "label" → Jump if Not Equal (mesmo que JNZ)
 - JA "label" → Jump if Above (salta se acima)
 - JB "label" → Jump if Below (salta se abaixo)
 - JAE "label" → Jump if Above or Equal (salta se acima ou =)
 - JBE "label" → Jump if Below of Equal (salta se abaixo ou =)
 - JG "label" → Jump if Greater than (salta se >)
 - JL "label" → Jump if Less than (salta se <)
 - JGE "label" → Jump if Greater than or Equal (salta se >=)
 - JLE "label" → Jump if Less or Equal (salta se <=)
 A diferença entre JG e JA, JL e JB é:
 - JA e JB são relativos a comparações sem sinal.
 - JG e JL são relativos a comparações com sinal.
 Os saltos condicionais têm uma desvantagem com relação aos
saltos incondicionais: O deslocamento é relativo a posição corrente,
isto é, embora no nosso código o salto se dê na posição do "label" o
assembler traduz esse salto para uma posição "x" bytes para frente
ou para tras em relação a posição da instrução de salto... e esse
número "x" está na faixa de -128 a 127 (traduzindo isso tudo pra
quem não entendeu: Não é possível saltos muito longos com instruções
de salto condicionais... salvo em casos especiais que explicarei
mais tarde!).
 Existe ainda a instrução JCXZ. Essa instrução salta se o
registrador CX for 0.
 Mais uma instrução: LOOP
 A instrução LOOP salta para um determinado endereço se o
registrador CX for diferente de zero e, antes de saltar, decrementa
CX. Um exemplo do uso desta instrução:
 ┌─────────────────────────────────────────────────────────────────┐
 │ SUB AL,AL ;AL = 0 │
 │ SUB DI,DI ;DI = 0 │
 │ MOV CX,1000 ;CX = 1000 │
 │ Loop1: │
 │ MOV BYTE PTR ES:[DI],0 ;Poe 0 em ES:DI │
 │ INC DI ;Incrementa o offset (DI) │
 │ LOOP Loop1 ;Repete ate' que CX seja 0 │
 └─────────────────────────────────────────────────────────────────┘
 Essa rotina preenche os 1000 bytes a partir de ES:0 com 0. O
modificador "BYTE PTR" na frente de ES:[DI] resolve uma ambiguidade:
Como podemos saber se a instrução "MOV ES:[DI],0" escreverá um byte
ou um word? Por default, o compilador assume word, por isso temos
que usar o modificador indicando que queremos byte.
 Repare que o pedaço entre "Loop1" e o final da rotina equivale a
uma instrução "REP STOSB".
 Podemos também especificar uma instrução LOOP condicional, basta
acrescentar 'Z' ou 'NZ' (ou os equivalentes 'E' ou 'NE') no fim.
Isto quer dizer: Salte ENQUANTO CX for ZERO (Z) ou N─O for ZERO
(NZ). A instrução LOOP sem condição é a mesma coisa que LOOPNZ ou
LOOPNE!
 Chamadas a sub-rotinas:➠
 A instrução CALL funciona como se fosse a instrução GOSUB do
velho BASIC. Ela salta para a posição especificada e quando a
instrução RET for encontrada na sub-rotina a CPU salta de volta para
a próxima instrução que segue o CALL. A sintaxe:
 ┌─────────────────────────────────────────────────────────────────┐
 │ CALL "label" │
 └─────────────────────────────────────────────────────────────────┘
 Eis um exemplo:
 ┌─────────────────────────────────────────────────────────────────┐
 │ MOV AL,9 ;Poe numero em AL │
 │ CALL ShowNumber ;Salta para a subrotina │
 │ ... │
 │ │
 │ ShowNumber: │
 │ ... │
 │ RET ;Retorna │
 └─────────────────────────────────────────────────────────────────┘
 ┏━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┓
 RBT │ Curso de Assembly │ Aula Nº 09 ┃ ┃
 ┗━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┛
 Por: Frederico Pissarra
 O assunto de hoje é INTERRUPÇÕES. Como já disse antes, uma
interrupção é uma requisição da atenção da CPU por um dispositivo
(por exemplo o teclado, quando apertamos uma tecla!). A CPU
INTERROMPE o processamento normal e salta para a rotina que "serve"
a interrupção requisitada, retornando ao ponto em que estava ANTES
da interrupção quando finalizar a rotina de interrupção. Assim
funciona a nível de hardware.
 A novidade nos processadores INTEL da série 80x86 é que existem
instruções assembly que EMULAM a requisição de uma interrução.
Essas instruções nada mais são que um "CALL", mas ao invés de
usarmos um endereço para uma subrotina, informamos o índice (ou o
código) da interrupção requisitada e a CPU se comportará como se um
dispositivo tivesse requisitado a interrupção...
 As rotinas do DOS e da BIOS são chamadas por essas instruções.
Na realidade, este artificio da família INTEL facilita muito o
trabalho dos programadores porque não precisamos saber onde se
encontram as rotinas da BIOS e do DOS na memória... Precisamos
saber apenas o índice da interrupção de cada uma das rotinas... o
endereço a CPU calcula para nós!
 Eis a sintaxe da instrução:
 ┌──────────────────────────────────────────────────────────────────┐
 │ INT 21h │
 │ INT 10h │
 └──────────────────────────────────────────────────────────────────┘
 Onde 21h e 10h são índices.
 A CPU sabe para onde saltar porque no inicio da memória de todo
PC tem uma tabela conhecida como "Tabela dos vetores de
interrupção". A CPU, de posse do índice na instrução INT, "pega" o
endereço correspondente a esse índice nessa tabela e efetua um CALL
diferente (porque o fim

Outros materiais

Outros materiais