             ͸
              RBT      Curso de Assembly      Aula N 01 
             ;

Por: Frederico Pissarra

͸
 ASSEMBLY I 
;

    A linguagem ASSEMBLY (e no  assemblER!) d medo em muita gente!
S no sei porque!  As liguagens ditas de  "alto  nvel"  so  MUITO
mais  complexas  que  o  assembly!   O  programador assembly tem que
saber, antes de mais nada, como est organizada a memria da mquina
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 mquina oferece.

    Uma  grande  desvantagem  do  assembly  com  relao  as  outras
linguagens  que  no  existe  tipagem  de  dados como, por exemplo,
ponto-flutuante...  O  programador  ter  que  desenvolver  as  suas
prprias rotinas ou lanar mao do co-processador matemtico (o TURBO
ASSEMBLER,   da   Borland,   fornece   uma   maneira   de  emular  o
co-processador).  No existem funes de entrada-sada como PRINT do
BASIC  ou  o  Write() do PASCAL...  No existem rotinas que imprimam
dados numricos ou strings na  tela...  Enfim...  no existe nada de
til!  (Ser?!  hehehe)

    Pra que serve o  assembly  ento?   A  resposta : Para que voc
possa desenvolver as suas prprias rotinas, sem ter  que  topar  com
bugs  ou  limitaes  de rotinas j existentes na ROM-BIOS ou no seu
compilador  "C",  "PASCAL"  ou  qualquer  outro...   Cabe  aqui  uma
considerao  interessante:    muito  mais  produtivo  usarmos  uma
liguagem de alto nvel juntamente com nossas rotinas em  assembly...
Evita-se a "reinveno da roda" e no temos que desenvolver TODAS as
rotinas  necessrias  para  os  nossos  programas.  Em particular, o
assembly  muito til quando  queremos criar rotinas que no existem
na liguagem de alto-nvel  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  comear  a  entender  alguma  coisa de
assembly  entender como a CPU organiza a memria.   Como  no  nosso
caso a idia  entender os microprocessadores da  famlia  80x86  da
Intel (presentes em qualquer PC-Compatvel), vamos dar uma  olhadela
no modelamento de memria usado pelos PCs, funcionando sob o  MS-DOS
(Windows,  OS/2,  UNIX,  etc...   usam  outro tipo de modelamento...
MUITO MAIS COMPLICADO!).

͸
 Modelamento REAL da memria - A segmentao 
;

    A memria de qualquer PC  dividida em segmentos.  Cada segmento
tem 64k bytes  de  tamanho  (65536  bytes)  e  por mais estranho que
parea  os  segmentos  no  so  organizados  de  forma   sequencial
(o  segmento seguinte no comea logo aps o anterior!).  Existe uma
sobreposiao.  De uma olhada:

                               64k
    Ŀ
    Ŀ
                                                         
                                                             
                                                         
    
    0      1      2 <- Numero do segmento
    
       16     16
     bytes   bytes

    O  segundo  segmento  comea   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,  ninguem  sabe  porque  a  Intel resolveu
utilizar essa coisa esquisita.  Mas, pacincia,  assim que a  coisa
funciona!

    Para  encontrarmos  um  determinado  byte  dentro de um segmento
precisamos  fornecer  o  OFFSET (deslocamento, em ingls) deste byte
relativo ao inicio  do  segmento.   Assim,  se  queremos localizar o
dcimo-quinto byte do segmento 0, basta especificar 0:15,  ou  seja,
segmento 0 e offset 15.  Esta notao  usada no restante deste e de
outros artigos.

    Na  realidade  a  CPU  faz  o  seguinte clculo para encontrar o
"endereo fsico" ou "endereo efetivo" na memria:

 Ŀ
          ENDEREO-EFETIVO = (SEGMENTO * 16) + OFFSET             
 

    Ilustrando  a  complexidade   deste  esquema  de  endereamento,
podemos provar que existem  diversas  formas  de  especificarmos  um
nico "endereo  efetivo"  da  memria...   Por  exemplo, o endereo
0:13Ah pode ser tambm 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 daro
o   mesmo  resultado:  o  endereo-efetivo  0013Ah.   Generalizando,
existem, no mximo,  16  formas  de  especificarmos o mesmo endereo
fsico!  As nicas faixas de endereos que no tem equivalentes e s
podem  ser  especificados  de  uma  nica  forma  so  os  desesseis
primeiros bytes do segmento  0  e  os  ltimos  desesseis  bytes  do
segmento 0FFFFh.

    Normalmente o programador no tem que se preocupar com esse tipo
de coisa.  O compilador toma conta da melhor forma de endereamento.
Mas, como a toda regra existe uma excesso, a informao acima  pode
ser til algum dia.

Ŀ
 A BASE NUMRICA HEXADECIMAL E BINARIA (para os novatos...)        


    Alguns  talvez  no  tenham  conhecimento  sobre as demais bases
numricas usadas na rea informata.    muito comum dizermos "cdigo
hexadecimal", mas o que significa?

     bastante lgico que usemos o sistema decimal  como  base  para
todos  os  clculos  matemticos  do  dia-a-dia pelo simples fato de
temos DEZ dedos nas mos...  fica  facil  contar  nos  dedos  quando
precisamos (hehe).

    Computadores usam o sistema binrio por um outro motimo simples:
Existem apenas dois nveis de tenso presentes em todos os circuitos
lgicos:  nveis  baixo  e  alto  (que  so  chamados  de  0 e 1 por
convenincia...  para podermos medi-los  sem  ter  que recorrer a um
multmetro!).   O  sistema  hexadecimal  tambm tem o seu lugar:  a
forma mais abreviada de escrever um conjunto de bits.

    Em decimal, o nmero 1994, por exemplo, pode ser escrito como:

       1994 = (1 * 10^3) + (9 * 10^2) + (9 * 10^1) + (4 * 10^0)

    Note a base 10  nas  potncias.   Fao  agora uma pergunta: Como
representariamos o mesmo nmer se tivessemos 16 dedos nas mos?

     Primeiro teriamos que obter mais digitos...  0 at 9  no  so
      suficientes.   Pegaremos mais 6 letras do alfabeto para suprir
      esta deficiencia.

     Segundo,  Tomemos  como  inspirao  um  odmetro (equipamento
      disponvel  em  qualquer  automvel   -      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 prximo  incrementado,  formando  o  10.   No
      caso  do sistema hexadecimal, isto s acontece quando o ltimo
      algarismo alcana 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 prximo algarismo,
      certo?

    Como contar em base diferente  de  dez   uma situao no muito
intuitiva, vejamos a regra de converso de bases.  Comearemos  pela
base  decimal  para  a  hexadecimal.   Tomemos  o  nmero  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 nmer convertido para hexadecimal:

  Ŀ
   1994 / 16     -> Q=124, R=10      -> 10=A                     
   124 / 16      -> Q=7, R=12        -> 12=C                     
   7 / 16        -> Q=0, R=7         ->  7=7                     
  

    Toma-se ento os restos de baixo para cima, formando o nmero em
hexadecimal. Neste caso, 1994=7CAh

    Acrescente um 'h' no fim do nmero para sabermos que se trata da
base  16,  do  contrrio,  se  olharmos  um  nmero "7CA" poderiamos
associa-lo a  qualquer  outra  base  numrica  (base octadecimal por
exemplo!)...

    O processo inverso,  hexa->decimal,    mais  simples...   basta
escrever  o nmer, multiplicando cada digito pela potncia correta,
levando-se em conta a equivalencia das letras com a base decimal:

 Ŀ
   7CAh = (7 * 16^2) + (C * 16^1) + (A * 16^0) =                  
          (7 * 16^2) + (12 * 16^1) + (10 * 16^0) =                
          1792 + 192 + 10 = 1994                                  
 

    As mesmas regras podem  ser  aplicadas  para a base binria (que
tem apenas dois digitos: 0 e  1).   Por  exemplo,  o  nmero  12  em
binrio fica:

 Ŀ
   12 / 2      -> Q=6, R=0                                        
   6 / 2       -> Q=3, R=0                                        
   3 / 2       -> Q=1, R=1                                        
   1 / 2       -> Q=0, R=1                                        
                                                                  
   12 = 1100b                                                     
 

    Cada digito na base binria  conhecido como BIT (Binary digIT -
ou  digito  binrio,  em  ingls)!   Note  o  'b'  no  fim do nmero
convertido...

    Faa o processo inverso... Converta 10100110b para decimal.

    A vantagem de usarmos um  nmero  em base hexadecimal  que cada
digito hexadecimal equivale a exatamente  quatro  digitos  binrios!
Faa  as  contas: Quatro bits podem conter apenas 16 nmeros (de 0 a
15), que  exatamente a quantidade de digitos na base hexadecimal.

             ͸
              RBT      Curso de Assembly      Aula N 02 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY II 
;

    Mais  alguns  conceitos  so  necessrios  para  que  o pretenso
programador ASSEMBLY  saiba  o  que  est  fazendo.   Em  eletrnica
digital  estuda-se  a  algebra  booleana  e  aritimtica com nmeros
binrios.  Aqui esses conceitos  tambm  so  importantes...   Vamos
comear pela aritimtica binria:

    A  primeira  operao  bsica  -   a   soma  -  no  tem  muitos
mistrios...  basta recorrer ao equivalente decimal.  Quando somamos
dois nmeros  decimais,  efetuamos  a  soma  de  cada  algarismo  em
separado,  prestando  ateno  aos  "vai  um"  que  ocorrem entre um
algarismo e outro. Em binrio 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 odmetro (aquele indicador de
quilometragem do carro!): 09 -> 10 -> 11

    Enquanto  na  base  decimal  existem 10 algarismos (0 at 9), na
base binria temos 2 (0 e 1).  O odmetro ficaria assim:
00b -> 01b -> 10b -> 11b

    Portanto, 1b + 1b = 10b ou, ainda, 0b e "vai um".

    A  subtrao    mais complicada de entender...  Na base decimal
existem os nmeros  negativos...   em  binrio nao!  (Veremos depois
como  "representar" um nmero negativo em binrio!).  Assim, 1b - 1b
= 0b (lgico), 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 soluo  a  seguinte:  Na  base  decimal quando subtraimos um
algarismo menor de outro maior costumamos "tomar um emprestado" para
que a conta fique correta.  Em binrio a  coisa  funciona  do  mesmo
jeito,  mas  se  no  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                            (no importa de onde!)     
     -  1b                                                       
                                                           
        1b                                                       
  

    Esse "1" que apareceu por mgica  conhecido como BORROW.  Em um
nmero binrio maior basta usar o mesmo artificio:

  Ŀ
   1010b - 0101b = ?                                             
                                                                 
        1 1         <- Os "1"s que foram tomados emprestados so 
        1010b          subtrados no proximo digito.             
      - 0101b                                                    
                                                        
        0101b                                                    
  

    Faa  a  conta:  0000b   -   0001b,   vai  acontecer  uma  coisa
interessante!  Faa a mesma conta usando um programa, ou calculadora
cientifica,  que  manipule  nmeros binrios...  O resultado vai ser
ligairamente diferente por causa da limitao dos digitos suportados
pelo software (ou calculadora).  Deixo  a  concluso  do  "por  que"
desta diferena para voc...   (Uma  dica,  faa  a conta com os "n"
digitos suportados pela calculadora e ter a explicao!).

͸
 Representando nmeros negativos em binrio                       
;

    Um artificio da algebra  booleana  para  representar  um  nmero
interiro  negativo    usar  o ltimo bit como indicador do sinal do
nmero.  Mas, esse  artificio  gera  uma segunda complicao...

    Limitemos esse estudo ao tamanho  de  um  byte (8 bits)...  Se o
bit 7 (a contagem comea pelo bit 0 - mais a direita) for 0 o nmero
representado  positivo, se for 1,  negativo.  Essa   a  diferena
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, porm um tem sinal  e  o
outro no!).

    A complicao que falei acima   com relao  representao dos
nmeros  negativos.   Quando  um  nmero  no     nagativo,   basta
convert-lo para base decimal que voc saber qual  esse nmero, no
entanto,  nmeros  negativos  precisam ser "complementados" para que
saibamos o nmero que est sendo representado.  A coisa NO funciona
da seguinte forma:

 Ŀ
   00001010b   =   10                                            
   10001010b   =  -10     (ERRADO)                               
 

    No basta "esquecermos" o bit 7  e lermos o restante do byte.  O
procedimento  correto  para   sabermos   que   nmero   est   sendo
representado negativamente no segundo exemplo :

     Inverte-se todos os bits
     Soma-se 1 ao resultado

  Ŀ
   10001010b   ->  01110101b + 00000001b   ->  01110110b         
   01110110b   =   118                                           
   Logo:                                                         
   10001010b   =  -118                                           
  

    Com isso podemos explicar a diferena entre os extremos da faixa
de um "signed char":

     Os  nmeros positivos contam  de 00000000b at 01111111b, isto
      , de 0 at 127.
     Os nmeros negativos  contam  de 10000000b at 11111111b, isto
      , de -128 at -1.

    Em "C" (ou PASCAL), a mesma lgica pode ser aplicada aos "int" e
"long" (ou INTEGER e  LONGINT),  s  que  a  quantidade de bits ser
maior ("int" tem 16 bits de tamanho e "long" tem 32).

    No se preocupe MUITO com a representao de  nmeros  negativos
em binrio...  A CPU toma conta de tudo  isso  sozinha...   mas,  as
vezes,  voc  tem  que  saber que resultado poder ser obtido de uma
operao aritimtica em seus programas, ok?

    As outras duas operaes matemticas  bsicas  (multiplicao  e
diviso) tanbm esto presentes nos processadores 80x86...  Mas, no
necessitamos ver como o processo  feito a nvel binrio.  Confie na
CPU!  :)
             ͸
              RBT      Curso de Assembly      Aula N 03 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY III 
;

    Comecemos   a   dar    uma    olhadela    na   arquitetura   dos
microprocessadores   da   famlia    INTEL   80x86...    Vamos   aos
registradores!

    Entenda  os  registradores  como  se  fossem  variveis  que   o
microprocessador  disponibiliza  ao sistema.  TODOS os registradores
tm 16 bits de tamanho e aqui vai a descrio 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 Cgido (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 instruo (Instruction Pointer)
     
     Ŀ
     Flags < Sinalizadores
     

    Por  enquanto vamos nos deter na descrio 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  contedo de AL,
estaremos alterando  o  byte  menos  significativo  de  AX  ao mesmo
tempo...  No 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 operaes 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  restries,  
interessante  atribuir  uma  funo  para  cada  um deles nos nossos
programas sempre que possvel...  Isto  facilita a leitura do cdigo
e nos educa a seguirmos uma  linha  de  raciocnio  mais  concisa...
Mas,  se  for  de  sua preferncia no seguir qualquer padro no uso
desses  registradores,  no  se  preocupe...   no  haver  qualquer
desvantagem nisso  (Well...   depende  do  cdigo,  as  vezes  somos
obrigados a usar determinado registrador!).

    Alguns pontos importantes quanto  a esses nomes sero observados
no decorrer do curso...  Por exemplo, certas instrues usam AX  (ou
AL, ou AH) e  somente  ele,  no  permitindo  o  uso de nenhum outro
registrador...   Outras,  usam  CX  para   contar,   etc...    essas
instrues especficas sero vistas em outra oportunidade.

    Os registradores SI e DI  so  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 cpia,
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 tm
aplicao exclusiva  em  algumas  instrues,  SI  e  DI  so usados
especificamente  como  ndices  em  instrues  que manipulam blocos
(tambm veremos isso mais tarde!).

    Os registradores CS, DS,  ES  e  SS  armazenam os segmentos onde
esto o cdigo  (programa  sendo  executado),  os  dados,  os  dados
extras,  e  a  pilha,  respectivamente.   Lembre-se  que a memria 
segmentada em blocos de 64kbytes (d uma olhada na primeira mensagem
dessa srie).

    Quando nos referimos, atravs de alguma instruo, a um endereo
de memria, estaremos nos referindo ao OFFSET dentro de um segmento.
O registrador de  segmento  usado  para  localizar  o dado no offset
especificado vai depender da prpria  instruo...   Um  exemplo  em
assembly:

 Ŀ
       MOV     AL,[1D4Ah]                                        
 

    O  nmero  hexadecimal  entre  os  colchetes  a indicao de um
offset em um segmento...  Por  default, a maioria das instrues usa
o segmento de dados (valor em  DS).  A instruo 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 instrues mais tarde :)

    Se quisessemos localizar o byte  desejado em outro segmento (mas
no mesmo offset) devemos especificar o registrador  de  segmento  na
instruo:

 Ŀ
       MOV     AL,ES:[1D4Ah]                                     
 

    Aqui o valor de ES ser usado.

    O registrador IP (Instruction Pointer)  o offset do segmento de
cdigo  que  contm  a  prxima  instruo  a  ser  execuatda.  Este
registrador no  acessvel por qualquer instruo (pelo  menos  no
pelas   documentadas   pela   Intel)...       de   uso  interno  do
microprocessador.    No   entanto   existem   alguns   macetes  para
conseguirmos obter o seu contedo (o que na maioria  das  aplicaes
no    necessario...   Para  que  conhecer  o  endereo  da prxima
instruo se ela var ser executada de qualquer jeito?).

    O registrador SP  o offset do segmento SS (segmento  de  pilha)
onde o prximo 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.   Tambm   usada para
armazenar o endereo 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  tambm 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  grfico abaixo
simularemos o empilhamento do contedo do registrador AX atravs  da
instruo:

 Ŀ
       PUSH    AX                                                 
 

 Ŀ
   AX = A527h (Valor em AX)                                       
                                                                  
    Ŀ                       Ŀ                     
     ????h < SP = n           A527h                      
    Ĵ                       Ĵ                     
                                         < SP = n - 1    
                                                
                                                                  
  (antes de PUSH AX)              (depois de PUSH AX)             
 

    Observe que SP sempre aponta para um espao vago na pilha.

    Na  realidade  SP    decrementado  de duas posies ao invs 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 descrio dos  bits  deste
registrador  (a descrio abaixo aplica-se ao 8086.  Normalmente no
acessamos diretamente  o  registrador  de  flags  -  embora possamos
faz-lo - por isso no  conveniente assumirmos que  os  bits  esto
sempre  no  mesmo  lugar  para  qualquer microprocessador da famlia
80x86!):

 Ŀ
             Ŀ                   
                 ODITSZ 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  adio  ou  quando  h  BORROW depois de uma subtrao.  Ou
    quando, numa operao 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 posio para a direita, o
    que acontece com o bit 0?...  Resposta: Vai para o carry!)

     Parity:

        Depois  de  uma  instruo  aritimtica  ou  lgica este bit
    informa se o resultado tem um nmero par de "1"s ou no.

     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  operao  aritimtica  ou  lgica, esse flag
    indica se o resultado  zero ou no.

     Signal:

        Depois de uma instruo aritimtica ou lgica, este  flag  
    uma  cpia  do  bit de mais alta ordem do resultado, isto , seu
    sinal (d uma olhada  na  "representao de nmeros negativos em
    binrio" no texto anterior!).

     Trap:

        Quando setado (1)  executa  instrues passo-a-passo...  No
    nos interessa estudar esse  bit  por  causa  das  diferenas  de
    implementao deste flag em toda a famlia 80x86.

     Interrupt Enable Flag

        Habilita/Desabilita   o   reconhecimento   de   interrupes
    mascarveis pela CPU. Sobre interrupes, veremos mais tarde!

     Direction:

        Quando   usamos   instrues   de   manipulao  de  blocos,
    precisamos especificar a direo que  usaremos (do inicio para o
    fim ou do fim para o inicio).
        Quando D=0 a direo  a do incio para o fim...  D=1, ento
    a direo  contrria!

     OverFlow:

        Depois de uma instruo  aritimtica  ou  lgica,  este  bit
    indica se houve mudana 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 countedo 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, porm um registrador tem 16 bits de tamanho e
    o resultado cabe em  17  bits.   Neste  exemplo,  o bit de carry
    tambm ser setado  pois  houve  "vai  um"  do  bit  15  para  o
    inexistente  bit  16,  mas no confunda o flag de overflow com o
    carry!

    Quando aos demais bits, no  se pode prever seus estados lgicos
(1 ou 0).

    Na prxima  mensagem  comearemos  a  ver  algumas instrues do
microprocessador 8086.  Ainda no escreveremos  nenhum  programa,  a
inteno    familiariz-lo  com  a  arquitetura do microprocessador
antes de comearmos a colocar a  mo  na massa...  tenha um pouco de
pacincia! :)

             ͸
              RBT      Curso de Assembly      Aula N 04 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY IV 
;

    Comearemos a ver algumas instrues  do  microprocessador  8086
agora.  Existem os seguintes tipos de instrues:

     Instrues Aritimticas
     Instrues Lgicas
     Instrues de Controle de Fluxo de Programa
     Instrues de manipulao de flags
     Instrues de manipulao da pilha
     Instrues de manipulao de blocos
     Instrues de manipulao de registradores/dados
     Instrues de Entrada/Sada

    Vamos   comear   com   as   instrues   de   manipulao    de
registradores/dados por serem estas as mais fceis de entender.

͸
 Instruo 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 contedo de um registrador para outro:

 Ŀ
   MOV BH,CL                                                     
 

     a mesma coisa que "BH = CL"!

    Os registradores de segmento no podem ser inicializados com MOV
tomando um parametro imediato (numrico).  Esses  registradores  so
inicializados indiretamente:

 Ŀ
   MOV DS,0    ; ERRADO!!!                                       
                                                                 
   MOV AX,0                                                      
   MOV DS,AX   ; CORRETO!                                        
 

    Carregar um registrador com o contedo (byte ou word, depende da
instruo!) armazenado em um segmento  simples, basta especificar o
offset  do  dado  entre  colchetes.  Ateno que o segmento de dados
(DS)  assumido por default com algumas excesses:

 Ŀ
   MOV AL,[0FFFFh]                                               
 

    A instruo acima, pega o byte armazenado no endereo 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  invs  de  usarmos  um  offset  imediato  podemos  usar   um
registrador:

 Ŀ
   MOV BX,0FFFFh                                                 
   MOV CH,[BX]                                                   
 

    Neste caso, BX contm o offset e o  byte  no  endereo  DS:BX  
armazenado  em  CH.   Note  que  o  registrador  usado  como  indice
obrigatoriamente deve ser de 16 bits.

    Uma   observao   quanto   a  essa  modalidade:  Dependendo  do
registrador usado como offset, o segmento default poder ser  DS  ou
SS.   Se  ao invs de BX usassemos BP, o segmento default seria SS e
no DS - de uma olhada no diagrama de distribuio dos registradores
no texto anterior.  BP foi colocado  no mesmo bloco de SP, indicando
que ambos esto 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 instruo:

 Ŀ
   MOV DH,ES:[BX]      ;Usa ES ao invs de DS                    
   MOV AL,CS:[SI + 4]  ;Usa CS ao invs de DS                    
 

    Repare que tenho usado os registradores de 8 bits para armazenar
os dados... Pode-se usar os de 16 bits tambm:

 Ŀ
   MOV ES:[BX],AX         ; Poe o valor de AX para ES:BX         
 

    S que neste caso sero armazenados 2 bytes no  endereo  ES:BX.
O  primeiro  byte    o  menos  significativo  e  o  segundo  o mais
signigicativo.  Essa instruo equivale-se a:

 Ŀ
   MOV ES:[BX],AL            ; Instruess que fazem a mesma     
   MOV ES:[BX + 1],AH        ;coisa que MOV ES:[BX],AX           
 

    Repare tambm que no  possvel mover o contedo de uma posio
da  memria  para  outra,  diretamente,  usando  MOV.   Existe outra
instruo que faz isso: MOVSB ou MOVSW.   Veremos  essas  instrues
mais tarde.

    Regra geral: Um dos operandos TEM que ser um registrador!  Salvo
no caso da movimentao de um imediato para uma posio de memria:

 Ŀ
   MOV [DI],[SI]       ; ERRO!                                  
   MOV [BX],0          ; OK!                                    
 

    Para ilustrar o uso da instruo MOV, eis um  pedao  do  cdigo
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 contedo de BP no  for  0FFFFh  ento a CPU est com algum
problema e o computador no pode funcionar!  Os flags  so  testados
de uma outra forma...  :)


͸
 XCHG 
;

    Esta instruo serve para trocarmos o contedo de um registrador
pelo outro. Por exemplo:

 Ŀ
   XCHG    AH,AL                                                 
 

    Se  AH=1Ah  e  AL=6Dh,  aps  esta instruo AH=6Dh e AL=1Ah por
causa da troca...

    Pode-se  usar uma referncia  memria assim como em MOV...  com
a  mesma  restrio  de  que  um   dos  operandos  TEM  que  ser  um
registrador.   No  h  possibilidade  de usar um operando imediato.


͸
 MOVSB e MOUSW 
;

    Essas  instrues  suprem   a   deficincia   de  MOV  quanto  a
movimentao  de  dados  de  uma   posio  de  memria  para  outra
diretamente.  Antes de ser chamada os  seguintes  registradores  tem
que ser inicializados:

 Ŀ
   DS:SI   <- DS e SI tm o endereo fonte                      
   ES:DI   <- ES e DI tm o endereo 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 discusso sobre os  flags  na
mensagem  anterior.   No  caso de MOVSW, SI e DI serao incrementados
(ou decrementados) de 2 posies de  forma que DS:SI e ES:DI apontem
sempre para a prxima word.


͸
 STOSB e STOSW 
;

    Essas  instrues  servem para armazenar um valor que est em AX
ou AL  (dependendo  da  instruo  usada)  no  endereo apontado por
ES:DI.  Ento, 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   -> Endereo onde o dado ser armazenado               
 

    Depois   da   execuo   da  instruo  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  instrues  servem para ler um valor que est no endereo
apontado  por  DS:SI  e  armazen-lo  em  AX  ou  AL  (dependendo da
instruo  usada).   Ento,  antes  de  ser  chamada,  os  seguintes
registradores devem ser inicializados:

 Ŀ
   DS:SI   -> Endereo de onde o dado ser lido                  
 

    Depois   da   execuo   da  instruo  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 prxima word.

͸
 Outras instrues de manipulao de registros/dados              
;

    Existem ainda as instrues LEA, LES e LDS.

  LEA:

    LEA ,  basicamente,  igual  a  instruo  MOV,  com  apenas uma
diferena: o  operando  "fonte"    um  endereo  (precisamente:  um
"offset").   LEA  simplesmente calcula o endereo e transfere para o
operando  "destino",  de   forma   que   as  instrues  abaixo  sao
equivalentes:

 Ŀ
   MOV     BX,100h                                               
   LEA     BX,[100h]                                             
 

    Porm, a instruo:

 Ŀ
   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  instruo  faz  o  servio de trs!!  Nos
processadores 286 e  386  a  diferena    significativa,  pois,  no
exemplo acima, LEA gastar  3  (nos  286)  ou  2 (nos 386) ciclos de
mquina enquando o equivalente gastar 11 (nos 286) ou 6  (nos  386)
ciclos  de  mquina!   Nos processadores 8088/8086 a diferena no 
tao grande...

    Obs:
        Consideremos cada ciclo  de  mquina seria, aproximadamente,
        num 386DX/40, algo em torno de 300ns - ou 0,0000003s.   uma
        medida empirica e no expressa a grandeza real  (depende  de
        uma srie de fatores no considerados aqui!).

    O  operando  "destino"    sempre  um  registrador.   O operando
"fonte"  sempre um endereo.

  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 instruo LDS, caso o alvo seja ES, usa-se LES.

    Suponhamos que numa posio  da  memria tenhamos um double word
(nmero  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 interrupo, 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
endereo  apontado  por  DS:SI  (repare  no  segmento  default   que
discutimos  em um texto anterior!).  A instruo acima  equivalente
a:

 Ŀ
   MOV     BX,[SI+2]                                            
   MOV     ES,BX                                                
   MOV     BX,[SI]                                              
 

    De novo, uma instruo substitui trs!

͸
 Manipulando blocos... parte I                                   
;

    As instrues 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  instruao.   Eis  um trecho de uma
pequena rotina que apaga o video em modo texto (80 x 25 colorido):

 Ŀ
   MOV AX,0B800h                                                
   MOD ES,AX           ; Poe em ES o segmento do vdeo          
   MOV DI,0            ; Comea 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 instruo que esta deve ser executada CX
vezes.  Note que a cada execuo de STOSW o registrador DI  apontar
para a proxima word.

    Suponha que queiramos  mover  4000  bytes  de  alguma posio da
memria para o video, preenchendo a tela com esses 4000 bytes:

 Ŀ
   MOV AX,0B800h                                                
   MOD ES,AX           ; Poe em ES o segmento do vdeo          
   MOV AX,SEG TABELA                                            
   MOV DS,AX           ; Poe em DS o segmento da tabela         
   MOV SI,OFFSET TABELA ; Comea no offset inicial da tabela    
   MOV DI,0            ; Comea 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
instrues: MOVSB, MOVSW, STOSB,  STOSW, LODSB, LODSW, CMPSB, CMPSW,
SCASB, SCASW, OUTSB, OUTSW, INSB, INSW.  As instrues nao vistas no
texto acima sero detalhadas mais tarde...

    Existem   mais    algumas    instrues    de   manipulao   de
registradores/dados, bem como mais algumas de manipulao de blocos.
Que ficaro para uma prxima mensagem.

             ͸
              RBT      Curso de Assembly      Aula N 05 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY V 
;

    Depois  de  algumas  instrues  de  movimentao  de  dados vou
mostrar a mecnica da lgica booleana, bem como algumas instrues.

    A lgica booleana  baseia-se  nas  seguintes operaes: AND, OR,
NOT.   Para  simplificar  a  minha  digitao  vou  usar  a  notao
simplificada: & (AND), | (OR) e ~ (NOT).  Essa notao    usada  na
linguagem C e em muitos manuais relacionados a hardware da IBM.


  Operao AND:

    A operao 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 lgica bit a bit  em  operaes  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 instruo 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  referencia  a  memria.   o operando a
direita (fonte) pode ser um registrador, uma referncia a memria ou
um  valor  imediato,  com  a  restrio  de  que  no  podemos  usar
referncias a memria nos dois operandos.

    A  instruo  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).


  Operao 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  lgica  bit  a bit
envolvendo  um  byte  ou  word atravs de uma instruo em assembly.
Vejamos um exemplo da utilidade de OR:

 Ŀ
               A = 01010111b       B = 10000000b                 
                                                                 
               S = A | B ->    01010111b                         
                             | 10000000b                         
                                                    
                               11010111b                         
 

    A  operao  OR    ideal  para  setarmos um determinado bit sem
afetar os demais.  No exemplo acima  B  tem apenas o bit 7 setado...
depois da operao 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 so os mesmos da instruo AND!


  Operao NOT:

    NOT simplesmente inverte todos os bits de um byte ou word:

                           Ŀ
                              S = ~A  
                           ͵
                             A    S  
                           Ĵ
                             0    1  
                             1    0  
                           

    A sintaxe da instruo em assembly  a seguinte:

  Ŀ
       NOT AL                                                    
       NOT DX                                                    
       NOT [SI]                                                  
  


  Operao XOR:

    A operao XOR  derivada das trs acima.   A  equao  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 so os mesmos que AND e OR.

             ͸
              RBT      Curso de Assembly      Aula N 06 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY VI 
;

    Instrues aritimticas  so  o  tpico  de  hoje.   J discuti,
brevemente,  os  flags  e  os  sistemas  de numerao.  Aqui vai uma
aplicao prtica:

  Soma:

    A  soma    feita atravs das instrues ADD e ADC.  A diferena
entre elas  que uma faz  a  soma  normalmente e a outra faz a mesma
coisa acrecentando o contedo do flag CARRY. Eis a sintaxe:

 Ŀ
   ADD AL,10h                                                     
   ADC AH,22h                                                     
                                                                  
   ADD AX,2210h                                                   
 

    As duas primeiras instrues 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  instruo  soma  AH  com  22h  MAIS  o  carry
resultante  da  primeira  instruo  e  o  resultado  ficar  em  AH
(novamente  setando  o  flag  carry  se  houver outro "vai um"!).  A
terceira instruo faz a mesma coisa porque soma 2210h a AX, ficando
o resultado em AX e o possvel "vai um" no carry.

    Todos  os  flags  so  afetados  aps  a  execuo  de  uma  das
instrues de soma, exceto: I, D e Trap.


  Subtrao

    Semelhante as instrues  de  soma,  existem  duas instrues de
subtrao: SUB e SBB.  A  primeira  faz  a  subtrao  simples  e  a
segunda  faz  a  mesma  coisa subtraindo tambm o contedo prvio do
flag CARRY (como  uma subtrao o CARRY  conhecido como BORROW!).

    A sintaxe:

 Ŀ
   SUB AL,1                                                       
   SBB AH,0                                                       
                                                                  
   SUB AX,1                                                       
 

    Como no exemplo  anterior,  as  duas  primeiras instrues fazem
exatamente o que a terceira faz...  Os flags afetados seguem a mesma
regra das instrues de soma!


  Incremento e decremento:

    As instrues INC e DEC so usadas no lugar  de  ADD  e  SUB  se
quisermos incrementar ou decrementar o contedo de algum registrador
(ou de uma posio de memria) de uma unidade. A sintaxe  simples:

 Ŀ
   DEC AX                                                        
   INC BL                                                        
 

    Os  flags afetados seguem a mesma regra de uma instruo de soma
ou uma de subtrao!


  Multiplicao:

    Os  processadores  da   famlia   80x86  possuem  instrues  de
multiplicao e diviso  inteiras  (ponto  flutuante fica pro 8087).
Alguns cuidados devem ser tomados quando usarmos  uma  instruo  de
diviso (que ser vista mais adiante!).

    Uma   coisa   interessante   com   a   multiplicao    que  se
multiplicarmos dois registradores de  16  bits obteremos o resultado
necessariamente em 32 bits.   O  par  de  registradores  DX e AX so
usados para armazenar esse nmero 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
operao de multiplicao:

                Ŀ
                             A * B = M           
                Ĵ
                    A          B          M    
                Ĵ
                  8 bits     8 bits    16 bits 
                                               
                 16 bits    16 bits    32 bits 
                

    A multiplicao sempre ocorrer entre  o acumulador (AL ou AX) e
um outro operando. Eis a sintaxe das instrues:

 Ŀ
   MUL BL      ; AX = AL * BL                                    
   IMUL CX     ; DX:AX = AX * CX                                 
 

    A primeira instruo (MUL) no considera o sinal dos  operandos.
Neste  caso, como BL  de 8 bits, a multiplicao se dar entre BL e
AL e o resultado ser armazenado em AX.

    A segunda instruo leva  em  considerao o sinal dos operandos
e, como CX  de 16 bits, a multiplicao se dar entre CX e AX  e  o
restultado  ser armazenado em DX e AX.  Lembrando que o sinal de um
nmero inteiro depende do seu bit mais significativo!


  Diviso:

    Precisamos tomar cuidado com a  diviso pelo seguinte motivo: Se
o resultado no couber no  registrador destino, um erro de "Division
by zero" ocorrer  (isto  no  est  perfeitamente  documentado  nos
diversos  manuais  que li enquanto estudava assembly 80x86...  Vim a
descobrir este 'macete' numa  antiga  edio  da revista PC MAGAZINE
americana).  Outro cuidado  com o divisor...  se for 0 o mesmo erro
ocorrer!

    A  diviso  pode ser feita entre um nmero 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 multiplicao  o  nmero  (dividendo) de 32 bits 
armazenado em DX e AX.

    Depois da diviso o quociente  armazenado em AL e o resto em AH
(no caso de diviso 16/8 bits) ou o quociente fica em AX e  o  resto
em DX (no caso de diviso 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 diviso sem sinal e o segundo com sinal.
Note os divisores (CX e BL no nosso exemplo).

    Na diviso 16/8 bits o dividendo  armazenado  em  AX  antes  da
diviso... No caso de 32/8 bits DX e AX so usados...

    Mais  um  detalhe:  Os  flags,  depois  de  uma multiplicao ou
diviso no devem ser considerados.

             ͸
              RBT      Curso de Assembly      Aula N 07 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY VII 
;

    Algumas instrues afetam  somente  aos  flags.  Dentre elas, as
mais utilizadas so as instrues de comparao entre dois dados.

  Comparaes aritimticas:

    A  instruo  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  instruo faz a subtrao 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  subtrao  dar valores
iguais e o flag de ZERO ser 1. Eis a mecnica de CMP:

 Ŀ
   CMP AL,1Fh   ; AL - 1Fh, afetando somente os Flags             
 

  Comparaes lgicas:

    A instruo TEST  usada quando se quer  comparar  o  estado  de
determinados bits de um operando. Eis a sintaxe:

 Ŀ
   TEST AL,10000000b                                              
   TEST [BX],00001000b                                            
 

    Esta  instruo faz um AND com os dois operados, afetando apenas
os flags.  Os flags Z, S  e  P  so  afetados,  os flags O e C sero
zerados.

  Instrues que mudam o estado dos flags diretamente:

    CLC - Abreviao de CLear Carry (Zera o flag Carry).
    CLD - Abreviao de CLear Direction (Ajusta flag de  direo  em
          zero,  especificando  o sentido correto para instrues de
          bloco).
    CLI - Abreviao   de  CLear   Interrupt   (Mascara   flag    de
          interrupo,   no  permitindo  que  a  CPU  reconhea  as
          interrupes mascarveis).
    CMC - Abreviao de CoMplement Carry (Inverte o flag de carry).
    STC - Abreviao de SeT Carry (Faz carry = 1).
    STD - Abreviao  de  SeT  Direction  (flag  de direo setado -
          indica que as instrues de bloco  incrementaro  os  seus
          pointers no sentido contrrio - de cima para baixo).
    STI - Abreviao  de  SeT Interrupt (Faz com que a CPU volte a
          reconhecer as interrupes mascarveis).

    Uma  interrupo    um  "desvio"  feito  pela  CPU  quando   um
dispositivo  requer  a  ateno  da mesma.  Por exemplo, quando voc
digita uma tecla, o circuito do  teclado requisita a ateno da CPU,
que  por  sua  vez,  para  o  que  est fazendo e executa uma rotina
correspondente   requisio  feita  pelo  dispositivo  (ou  seja, a
rotina da interrupo).  Ao final da rotina, a CPU retorna   tarefa
que  estava  desempenhando  antes da interrupo.  Nos PCs, TODAS as
interrupes  so  mascarveis  (podem  ser  ativadas  e desativadas
quando  quisermos),  com a nica excesso da interrupao de checagem
do sistema (o famoso MEMORY PARITY ERROR  um exeplo!).

             ͸
              RBT      Curso de Assembly      Aula N 08 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY VIII 
;

    Veremos agora as instrues de controle de fluxo de programa.

    A CPU sempre executa instrues em  sequncia,  a  no  ser  que
encontre instrues que "saltem" para outra posio na memria.

    Existem  diversas  formas   de   "saltar"  para  um  determinado
endereo:

  Salto incondicional:

    A instruo JMP simplesmente salta  para onde se quer.  Antes de
apresentar a sintaxe, um  detalhe  sobre  codificaao: O operando da
instruo JMP  um endereo na memria, 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
endereo pra gente.

    Eis a sintaxe de JMP:

 Ŀ
       JMP Aqui2                                                  
  Aqui1:                                                          
       JMP Aqui3                                                  
  Aqui2:                                                          
       JMP Aqui1                                                  
  Aqui3:                                                          
 

    Os "labels" so sempre seguidos de dois-pontos.  Note, no pedao
de cdigo acima, a quebra da sequncia de execuo.


  Salto incondicional:

    Diferente de JMP, temos instrues que realizam um salto somente
se uma condio  for  satisfeita.   Para  isso,  usa-se os flags.  A
sintaxe dessas instrues depende da condio do flag  que  se  quer
testar. Eis a listagem dessas instrues:

    - 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 diferena entre JG e JA, JL e JB :

    - JA e JB so relativos a comparaes sem sinal.
    - JG e JL so relativos a comparaes com sinal.

    Os  saltos  condicionais  tm  uma  desvantagem  com relao aos
saltos incondicionais: O deslocamento  relativo a posio corrente,
isto , embora no nosso cdigo o salto se d na posio do "label" o
assembler traduz esse salto para  uma  posio "x" bytes para frente
ou para tras em relao a posio da instruo de salto...   e  esse
nmero  "x"  est  na  faixa de -128 a 127 (traduzindo isso tudo pra
quem no entendeu: No  possvel saltos muito longos com instrues
de salto condicionais...   salvo  em  casos especiais que explicarei
mais tarde!).

    Existe ainda a  instruo  JCXZ.   Essa  instruo  salta  se  o
registrador CX for 0.

    Mais uma instruo: LOOP

    A  instruo  LOOP  salta  para  um  determinado  endereo  se o
registrador CX for diferente de zero e, antes de saltar,  decrementa
CX. Um exemplo do uso desta instruo:

 Ŀ
       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  instruo "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 pedao entre "Loop1" e o final da rotina equivale a
uma instruo "REP STOSB".

    Podemos tambm especificar uma instruo 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 NO for ZERO
(NZ).  A instruo LOOP sem condio  a mesma coisa que  LOOPNZ  ou
LOOPNE!


  Chamadas a sub-rotinas:

    A instruo CALL funciona como se fosse  a  instruo  GOSUB  do
velho BASIC.  Ela  salta  para  a  posio  especificada  e quando a
instruo RET for encontrada na sub-rotina a CPU salta de volta para
a prxima instruo 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


͸
 ASSEMBLY IX 
;

    O assunto de  hoje    INTERRUPES.   Como  j disse antes, uma
interrupo  uma requisio da ateno 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 interrupo requisitada, retornando ao ponto em que  estava  ANTES
da  interrupo  quando  finalizar  a  rotina de interrupo.  Assim
funciona a nvel de hardware.

    A novidade nos processadores INTEL  da srie 80x86  que existem
instrues assembly que  EMULAM  a  requisio  de  uma  interruo.
Essas  instrues  nada  mais  so  que  um  "CALL", mas ao invs de
usarmos um endereo para  uma  subrotina,  informamos o ndice (ou o
cdigo) da interrupo requisitada e a CPU se comportar como se  um
dispositivo tivesse requisitado a interrupo...

    As  rotinas  do DOS e da BIOS so chamadas por essas instrues.
Na realidade,  este  artificio  da  famlia  INTEL  facilita muito o
trabalho dos programadores  porque  no  precisamos  saber  onde  se
encontram  as  rotinas  da  BIOS  e do DOS na memria...  Precisamos
saber apenas o ndice da  interrupo  de cada uma das rotinas...  o
endereo a CPU calcula para ns!

    Eis a sintaxe da instruo:

 Ŀ
   INT 21h                                                         
   INT 10h                                                         
 

    Onde 21h e 10h so ndices.

    A CPU sabe para onde saltar porque no inicio da memria de  todo
PC   tem   uma   tabela   conhecida  como  "Tabela  dos  vetores  de
interrupo".  A CPU, de posse do ndice na instruo INT, "pega"  o
endereo  correspondente a esse ndice nessa tabela e efetua um CALL
diferente (porque  o  fim  de  uma  rotina  de  interrupo  tem que
terminar em IRET e  no  em  RET  -  IRET    o  RET  da  rotina  de
interrupo - Interrupt RETurn).

    Por exemplo...  Se precisamos abrir um  arquivo,  o  trabalho  
enviado  ao  DOS pela interrupao de indice 21h.  Se queremos ler um
setor do disco, usamos a interrupao de indice 13h, etc...  Mas, nao
use a instruao INT  sem  saber  exatamente  o que est fazendo, ok?
Pode ter resultados desastrosos!

    Uma  descrio  da  maioria   das   interrupes   de   software
disponveis  nos  PCs  compatveis est disponvel no livro "Guia do
programador para PC e PS/2"  de  Peter Norton (recomendo a aquisio
deste livro!  De preferencia a  versao americana!).  Ou, se preferir
"literatura eletronica" recomendo  o  arquivo  HELPPC21.ZIP  (v2.1),
disponivel  em qualquer bom BBS...  Ainda assim pedirei para o RC do
ES (RBT) para disponibiliza-lo para  FREQ aos Sysops interessados em
adquiri-lo.

    Quanto as interrues  de  hardware  (as  famosas  IRQs!)...   
assunto  meio  complexo  no  momento e requer um bom conhecimento de
eletronica digital  e  do  funcionamento  do  micrprocessador...  no
futuro (prximo, espero!) abordarei esse assunto.
             ͸
              RBT      Curso de Assembly      Aula N 10 
             ;

Por: Frederico Pissarra


͸
 ASSEMBLY X 
;

    Mais  instrues  lgicas...   Falta-nos  ver  as  intrues  de
deslocamento de bits: SHL, SHR, SAL, SAR, ROL, ROR, RCL e RCR.

    A ltima letra  nas  instrues  acima  especifica  o sentido de
rotao (R = Right -> direita, L = Left -> esquerda).

    Para exemplificar a mecnica do funcionamento dessas  instrues
recorrerei a graficos (fica mais fcil assim).

    ķ
     SHL e SHR 
    Ľ

        SHL:
      Ŀ     Ŀ
      Carry<ĳ                  < 0
           
                 msb                lsb


        SHR:
                  Ŀ      Ŀ
           0 >                  >Carry
                        
                 msb                lsb

    SHR e SHL fazem o deslocamento dos bits em direo ao flag Carry
e  acrescentam  0  no  lugar  do ltimo bit que foi deslocado.  Essa
operao tem o mesmo efeito  de  multiplicar  por 2 (SHL) ou dividir
por 2 (SHR) um valor.  Com a vantagem  de  no  gastar  tanto  tempo
quanto as instrues DIV e MUL.

    SHR  a abreviao de  SHift  Right,  enquando  SHL  a de SHift
Left.


    ķ
     SAL e SAR 
    Ľ

    SAL funciona da mesma maneira que SHL.

        SAR: Ŀ
                 Ŀ      Ŀ
             >                  >Carry
                        
                 msb                lsb

    SAR  desloca todos os bits para a direita (o lsb vai para o flag
carry) e repete o contedo do antigo ltimo bit (que foi deslocado).

    SAR    a  abreviao  de  SHift  Arithmetic  Right.   Sendo  um
deslocamento aritimtico, no  poderia  de  desconsiderar o sinal do
dado deslocado (dai o motivo de repetir o bit mais significativo!).


    ķ
     RCL e RCR 
    Ľ

        RCL:
           Ŀ
                                                   
               Ŀ     Ŀ    
           ĴCarry<ĳ                  <
                     
                           msb                lsb


        RCR:
            Ŀ
                                                  
               Ŀ      Ŀ  
            >                  >Carry
                      
               msb                lsb

    RCR  e  RCL  rotacionam  o  dado  "passando  pelo  carry".  Isto
significa que o bit  menos  significativo  (no  caso  de  ROR)  ser
colocado  no  flag  de carry e que o contedo antigo deste flag ser
colocado no bit mais significativo do dado.


    ķ
     ROL e ROR 
    Ľ

        ROL:
                          Ŀ
                                                   
                Ŀ    Ŀ    
                Carry<ĳ                  <
                     
                           msb                lsb


        ROR:
            Ŀ
                                     
               Ŀ     Ŀ
            >                  >Carry
                      
               msb                lsb

    Aqui a rotaao e' feita  da  maneira correta...  o flag de carry
apenas indica o ultimo bit que "saiu" e foi para o outro lado...

    A sintaxe dessas instrues  a seguinte:

 Ŀ
   SHL AX,1                                                       
   SHR BL,1                                                       
   RCL DX,CL                                                      
   ROL ES:[DI],CL                                                 
 

    Note que o segundo operando   um contador do nmero de rotaes
ou shifts sero efetuadas.  Nos microprocessadores 80286  em  diante
pode-se usar um valor diferente de 1, no 8088/8086 no pode!

    Repare  tambm  que  podemos  usar  APENAS o registrador CL como
operando da direita se quisermos usar algum registrador!
             ͸
              RBT      Curso de Assembly      Aula N 11 
             ;

Por: Frederico Pissarra



͸
 ASSEMBLY XI 
;

    Mais instrues de comparao...

  CMPSB e CMPSW

    Essas instrues comparam (da mesma forma que CMP) o contedo da
memria apontada  por  DS:SI  com  o  contedo  apontado  por ES:DI,
afetando os flags.  Com isso, soluciona-se a limitao da  instruo
CMP com relao aos dois operandos como referncias  memria!

    Lembre-se que DS:SI  o operando implicito FONTE, enquanto ES:DI
  o  destino.   A comparao  feita de ES:DI para DS:SI.  A rotina
abaixo  equivalente  a  CMPSB:

 Ŀ
   MOV AL,ES:[DI]                                                 
   CMP AL,[SI]                                                    
   INC SI                                                         
   INC DI                                                         
 

    Existe um pequenino erro de lgica na rotina  acima,  mas  serve
aos nossos propsitos de ilustrar o que ocorre em CMPSB.

    SI  e  DI sero incrementados (ou decrementados, depende do flag
de direo)  depois  da  operao,  e  o  incremento (ou decremento)
depender da instruo...  Lembre-se que CMPSB compara Bytes e CMPSW
compara Words.


  SCASB e SCASW

    Essas  instrues servem para comparar (da mesma forma que CMP o
faz) o contedo da memria  apontado  por DS:SI com o registrador AL
(no caso de SCASB) ou AX (no caso de SCASW).  Os flags so  afetados
e SI  incrementado (ou decrementado)  de  acordo  com  a  instruo
usada.


Ŀ
Comparando blocos de memria:                                     


    Podemos usar CMPS?  e SCAS?  (onde ?  e' B ou W) em conjunto com
REP  para  compararmos blocos (CMPS?) ou procurar por um determinado
dado num bloco (SCAS?).  A diferena aqui  que podemos fornecer uma
condio de comparao ou busca.

    Acrescentando o modigicador REP,  precisamos  dizer  uma dessas
instrues a quantidades de dados que queremos manipular...  fazemos
isso atravs do registrador CX (assim  como  fizemos  com  LODS?   e
STOS?):

 Ŀ
   ;Certifica-se do sentido crescente!                           
   CLD                                                           
                                                                 
   ;Obtm o segmento da linha de comando e coloca em DS          
   MOV   AX,SEG LINHA_DE_COMANDO                                 
   MOV   DS,AX                                                   
                                                                 
   ;Obtm o offset inicial da linha de comando                   
   MOV   SI,OFFSET LINHA_DE_COMANDO                              
                                                                 
   ;Procura, no mximo por 128 bytes                             
   MOV   CX,128                                                  
                                                                 
   ;Procuraremos por um espao.                                  
   MOV   AL,' '                                                  
                                                                 
   REPNE SCASB                                                   
 

    Esse  fragmento  de cdigo ilustra o uso de SCASB com blocos.  O
modificador  REPNE  significa  (REPete  while  Not  Equal  -  Repete
enquanto no for igual).  REPNE garante que o byte vai ser procurado
por  toda  a  linha  de  comando  at  que  o  primeiro  espao seja
encontrado.  Se no houver espaos na linha, ento,  depois  de  128
bytes  de  procura,  o  registrador  CX  estar  zerado  (j  que  
decrementado a cada byte comparado).

    Esta   outra caracterstica das instrues que manipulam blocos
(as que  so  precedidas  de  REP,  REPNE  ou  REPE):  O  contador 
decrementado a cada operao da instruo associada (no  nosso  caso
SCASB), bem como os demais operandos implicitos (SI no caso acima) 
incrementado a cada passo.

    Se  quisermos  encontrar  o primeiro byte DIFERENTE de espao na
rotina acima,  basta  trocar  REPNE  por  REPE  (Repete enquanto for
IGUAL).

    REPE e REPNE no foram mencionados antes  porque  no  funcionam
com LODS? e STOS?.



             ͸
              RBT      Curso de Assembly      Aula N 12 
             ;

Por: Frederico Pissarra



ͻ
 ASSEMBLY XII 
ͼ

    A  partir  de  agora  veremos,  resumidamente,  como desenvolver
funes/procedures em assembly no mesmo cdigo PASCAL.

    O  TURBO  PASCAL  (a  partir  da  versao  6.0)  fornece  algumas
palavras-chave dedicadas   construo  de  rotinas assembly in-line
(esse recurso  chamado de BASM nos manuais do TURBO PASCAL - BASM 
a abreviao de Borland ASseMbler).

    Antes de comearmos a  ver  o  nosso primeiro cdigo em assembly
vale a pena ressaltar  alguns  cuidados  em relao a codificao de
rotinas assembly em TURBO PASCAL...  As nossas rotinas devem:

     Preservar sempre o contedo dos registradores DS, BP e SP.
     Nunca modificar, diretamente, o contedo dos registradores CS,
      IP e SS.

    O motivo dessas restries  que os registradores BP,  SP  e  SS
so  usados  na  obteno  dos  valores  passados  como parametros 
funo/procedure e na localizao  das variaveis globais na memria.
O registrador DS  usado por todo o cdigo PASCAL  e  aponta  sempre
para  o  segmento  de  dados  corrente  (o  qual no sabemos onde se
encontra...  deixe que o cdigo PASCAL tome conta disso!).

    Com relao ao contedo de CS  e  IP, no  uma boa prtica (nem
mesmo em cdigos assembly puros) alterar o seus valores.  Deixe  que
as  instrues  de  salto  e  chamada  de  subrotinas faam isso por
voc!).

    Os  demais  registradores  podem  ser  alterados a vontade.

    A funo HexByte()  abaixo    um  exemplo  de funo totalmente
escrita em assembly...  Ela toma  um  valor  de 8 bits e devolve uma
string de 2 bytes contendo o valor hexadecimal desse parametro:

 Ŀ
   FUNCTION    HexByte(Data : Byte) : String; ASSEMBLER;         
   ASM                                                           
       LES     DI,@Result   { Aponta para o inicio da string. }  
                                                                 
       MOV     AL,2         { Ajusta tamanho da string em 2.  }  
       STOSB                                                     
                                                                 
       MOV     AL,Data      { Pega o dado a ser convertido.   }  
                                                                 
       MOV     BL,AL        { Salva-o em BL.                  }  
       SHR     AL,1         { Para manter compatibilidade com }  
       SHR     AL,1         { os microprocessadores 8088/8086 }  
       SHR     AL,1         { nao  prudente usar SHR AL,4.   }  
       SHR     AL,1                                              
       ADD     AL,'0'       { Soma com ASCII '0'.             }  
       CMP     AL,'9'       { Maior que ASCII '9'?            }  
       JBE     @NoAdd_1     { ... Nao , ento nao soma 7.    }  
       ADD     AL,7         { ... , ento soma 7.            }  
   @NoAdd_1:                                                     
       MOV     AH,AL        { Salva AL em AH.                 }  
                                                                 
       MOV     AL,BL        { Pega o valor antigo de AL em BL.}  
       AND     AL,1111B     { Zera os 4 bits superiores de AL.}  
       ADD     AL,'0'       { Soma com ASCII '0'.             }  
       CMP     AL,'9'       { Maior que ASCII '9'?            }  
       JBE     @NoAdd_2     { ... Nao , ento nao soma 7.    }  
       ADD     AL,7         { ... , ento soma 7.            }  
   @NoAdd_2:                                                     
                                                                 
       XCHG    AH,AL        { Trocar AH com AL para gravar na }  
       STOSW                { ordem correta.                  }  
   END;                                                          
 

    A primeira linha  a declarao da funo  seguida  da  diretiva
ASSEMBLER  (informando  que a funo TODA foi escrita em assembly!).
A seguir a palavra-chave ASM  indica  o inicio do bloco assembly at
que END; marque o fim da funo...

    A primeira linha do cdigo assembly :

 Ŀ
       LES     DI,@Result                                        
 

    Quando retornamos uma string  numa  funo precisamos conhecer o
endereo do  inicio  dessa  string.   A  varivel  @Result contm um
pointer que aponta para o inicio da string que ser  devolvida  numa
funo.  Esse endereo  sempre um endereo FAR (ou seja, no formato
SEGMENTO:OFFSET).

    A seguir inicializamos o tamanho da string em 2 caracteres:

 Ŀ
       MOV     AL,2                                              
       STOSB                                                     
 

    Note  que STOSB vai gravar o contedo de AL no endereo apontado
por ES:DI, ou seja, o endereo  apontado por @Result, e logo aps DI
 incrementado, apontando para a primeira posio valida da string.

    O mtodo  que  usei  para  gerar  uma  string  hexadecimal    o
seguinte:

    - Pegamos o parametro 'Data' e colocamos em AL.
    - Salva-se o contedo de AL em BL para que possamos obter  os  4
      bits menos significativos sem termos que ler 'Data' novamente!
    - Com AL fazemos:
        - Desloca-se AL 4  posioes  para  a direita, colocando os 4
          bits mais significativos  nos  4  menos  significativos  e
          preenchendo os 4 mais significativos com 0B.
     (a)- Soma-se o valor do ASCII '0' a AL.
     (b)- Verifica-se se o  resultado  maior que o ASCII '9'.
            - Se for, somamos 7.
        - Salvamos o contedo  de  AL  em  AH.
    - Recuperamos o valor antigo de AL que estava em BL.
    - Com AL fazemos:
        - Zeramos os 4 bits mais significativos para obtermos apenas
          os 4 menos significativos em AL.
        - Repetimos (a) e (b)
    - Trocamos AL com AH e gravamos AX  com STOSB

    A primeira pergunta : Porque somar 7 quando o resultado da soma
com o ASCII '0' for  maior  que  o  ASCII  '9'?  A resposta pode ser
vista no pedao da tabela ASCII abaixo:

 Ŀ
          0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F          
                                                    
                            E esses 7 bytes ?                    
 

    Observe  que  depois  do ASCII '9' segue o ASCII ':' ao invs do
ASCII 'A', como  desejado...  Entao,  se  o resultado da soma dos 4
bits menos signficativos (que varia de 0000B at 1111B - ou de  0  a
15) com o ASCII '0' for maior que o ASCII '9' precisamos compensar a
existencia dos 7 caracteres indesejveis!

    Imagine que AL seja 0. Somando o  ASCII  '0'  (que  equivale  ao
nmero 30h) a AL obteriamos:

 Ŀ
   AL = 0010B = 2h                                                
   AL = 2h + '0'                                                  
   AL = 2h + 30h                                                  
   AL = 32h = '2'                                                 
 

    Imagine  agora  que  AL  seja  1011B.   Fazendo as mesmas contas
obteriamos AL = 3Bh  (que    a  mesma  coisa  que  o ASCII ';'.  No
entando, 3Bh  maior que o ASCII '9' (ou seja, 39h)... Ento:

 Ŀ
   AL = ';' = 3Bh                                                 
   AL = 3Bh + 7h                                                  
   AL = 42h = 'B'                                                 
 

    A outra coisa que voc poderia me perguntar  o porque eu usei a
instruo  XCHG  AH,AL  no final do cdigo.  A resposta  simples...
Os microprocessadores da INTEL  gravam  words na memria da seguinte
maneira:

 Ŀ
   Word = FAFBh                                                   
   Na memria: FBh FAh                                            
 

    No  importa  se o seu computador seja um Pentium ou um XT...  A
memria    sempre dividida em BYTES.  A CPU apenas "le" um conjunto
maior de bytes de acordo com  a  quantidade de bits da sua CPU.  Por
exemplo, os microprocessadores 8086 e 80286 so CPUs de  16  bits  e
por isso conseguem ler 2 bytes (8 bits + 8 bits = 16 bits) de uma s
vez...  As CPUs 386 e 486 so de 32 bits e podem ler de uma s vez 4
bytes!

    Esse  conjunto  de  bytes  que  a  CPU  pode  enxergar   sempre
armazenado da forma contrria  do  que  os  olhos humanos leem...  O
byte menos significativo SEMPRE vem ANTES do mais significativo.  No
caso de um DOUBLEWORD (ou numero de 32 bits de tamanho) o formato  
o mesmo... Exemplo:

 Ŀ
   Nmero = FAFBFCFDFEh                                           
   Na memria: FE FD FB FA                                        
 

    Analizando  a rotina HexByte() a gente ve que AH tem o byte mais
significativo  e   AL   o   menos   significativo.    Como  o  menos
significativo vem sempre antes do mais significativo fiz a troca  de
AH com AL para que o nmero HEXA seja armazenado de forma correta na
memria  (string).  Um exemplo: Suponha que o voc passe o valor 236
 funo HexByte():

 Ŀ
   Valor = 236 ou ECh                                             
   At antes de XCHG AH,AL:    AH = ASCII 'E'                     
                               AL = ASCII 'C'                     
 

    Se  no  tivessemos  a   instruo  XCHG  AH,AL  e  simplesmente
usassemos o STOSW (como est no cdigo!) AH seria precedido de AL na
memria (ou na string!),  ficariamos  com  uma  string 'CE'!  No me
lembro  se  j  falei  que  o  L  de  AL  significa  LOW  (ou  menos
significativo!) e H de AH significa HIGH  (ou  mais  significativo),
portanto  AL  e  AH  so,  respectivamente,  os  bytes  menos e mais
significativos de AX!

    No se importe em coloca um RET ao fim da funo, o TURBO PASCAL
coloca isso sozinho...

    Voc deve estar se perguntando porque no fiz a rotina de  forma
tal  que  a troca de AH por AL no fosse necessria...  Well...  Fiz
isso pra ilustrar a  forma  como  os  dados so gravados na memria!
Retire XCHG AH,AL do cdigo e veja o que  acontece!   Um  outro  bom
exerccio    tentar  otimizar  a  rotina  para que a troca no seja
necessria...

    E...  para fechar  a  rotina,  podemos aproveitar HexByte() para
construir HexWord():

 Ŀ
   Function HexWord(Data : Word) : String;                        
   Var H, L : String;                                             
   Begin                                                          
       H := HexByte(HIGH(Data));                                  
       L := HexByte(LOW(Data));                                   
       HexWord := H + L;                                          
   End;                                                           
 

    HexDoubleWord() eu deixo por sua conta :)

    Aguardo as suas duvidas...



             ͸
              RBT      Curso de Assembly      Aula N 13 
             ;

Por: Frederico Pissarra



ͻ
 ASSEMBLY XIII 
ͼ

    Algumas pessoas,  depois  de  verem  o  cdigo-exemplo  do texto
anterior, desenvolvido  para  ser  compilado  em  TURBO  PASCAL,  me
perguntaram:  "E quanto ao C?!".  Well...  aqui vo algumas tcnicas
para codificao mixta em C...

    Antes de comearmos a dar uma olhada nas tcnicas, quero  avisar
que  meu  compilador preferido  o BORLAND C++ 3.1.  Ele tem algumas
caractersticas que no esto presentes  do  MicroSoft C++ 7.0 ou no
MS Visual C++!  Por  exemplo,  O  MSC++  ou  o MS-Visual C++ no tem
"pseudo"-registradores (que ajudam um  bocado  na mixagem de cdigo,
evitando os "avisos" do compilador).

    Mesmo com algumas diferenas, voc poder usar as tcnicas  aqui
descritas...  As regras podem ser usadas  para  qualquer  compilador
que no gere aplicaes em modo protegido para o MS-DOS.


  Regras para a boa codigificao assembly em C

    Assim como no TURBO PASCAL, devemos:

        * Nunca alterar CS, DS, SS, SP, BP e IP.
        * Podemos alterar com muito cuidado ES, SI e DI
        * Podemos alterar sempre que quisermos AX, BX, CX, DX

    O registrador DS  sempre  aponta  para  o  segmento  de dados do
programa...  Se a sua funo assembly acessa alguma varivel global,
e voc tiver alterado DS, a  varivel  que  voc  pretendia  acessar
no estar disponvel!  Os registradores SS, SP e BP so usados pela
linguagem  para  empilhar  e  desempilhar  os parametros e variveis
locais da funo na  pilha...   altera-los pode causar problemas!  O
par de registradores CS:IP nao deve ser  alterado  porque  indica  a
prxima  posio  da  memria  que contm uma instruo assembly que
ser executada...  Em qualquer  programa "normal" esses ltimos dois
registradores so deixados em paz.

    No caso dos registradores ES, SI e DI, o compilador  os  usa  na
manipulao  de  pointers  e  quando precisa manter uma varivel num
registrador  (quando  se  usa   a  palavra-reservada  "register"  na
declarao de uma varivel, por exemplo!).   Dentro  de  uma  funo
escrita puramente em assembly, SI e DI podem ser alterados a vontade
porque  o compilador trata de salva-las na pilha (via PUSH SI e PUSH
DI) e, ao trmino da funo, as  restaura  (via POP DI e POP SI).  A
melhor  forma  de  se  saber  se  podemos  ou  no  usar  um  desses
registradores em um cdigo mixto  compilando o programa  e  gerando
uma listagem assembly (no BORLAND C++ isso  feito usando-se a chave
-S  na  linha  de comando!)...  faa a anlize da funo e veja se o
uso  desses  registradores  vai  prejudicar  alguma  outra  parte do
cdigo!

    Se voc no quer ter essa dor de cabea,  simplesmente  salve-os
antes de usar e restaure-os depois que os usou!


  Modelamento de memria:

    O  mais  chato  dos  compiladores   C/C++  para  o  MS-DOS    o
modelamento de memria, coisa que no existe no TURBO PASCAL!   Digo
"chato"  porque esse recurso, QUE  MUITO UTIL, nos d algumas dores
de cabea de vez em quando...

    Os modelos COMPACT, LARGE e  HUGE usam, por default, pointers do
tipo  FAR  (segmento:offset).  Os modelos TINY, SMALL e MEDIUM usam,
por default, pointers do  tipo  NEAR  (apenas  offset, o segmento de
dados  assumido!).

    A "chatisse" est  em  criarmos  cdigos  que  compilem  bem  em
qualquer  modelo  de  memria.  Felizmente isso  possvel graas ao
pre-processador:

Ŀ
#if defined(__TYNY__) || defined(__SMALL__) || defined(__MEDIUM__)
/* Processamento de pointers NEAR */                              
#else                                                             
/* Processamento dos mesmos pointers... mas, FAR! */              
#endif                                                            


    Concorda comigo que   meio  chato  ficar enchendo a listagem de
diretivas do pr-processador?... C'est la vie!

  C + ASM

    Os compiladores da BORLAND possuem a  palavra  reservada  "asm".
Ela  diz  ao compilador que o que a segue deve ser interpretado como
uma instruo  assembly.   Os  compiladores  da  MicroSoft possuem o
"_asm" ou o "__asm".  A  BORLAND  ainda  tem  uma  diretiva  para  o
pr-processador  que  usada para indicar ao compilador que o cdigo
deve ser montado pelo TURBO ASSEMBLER ao invs do compilador C/C++:

Ŀ
 #pragma inline                                                   


    Voc pode usar isto ou ento  a  chave -B da linha de comando do
BCC...  funciona da mesma forma!  Voc  deve  estar  se  perguntando
porque  usar  o  TURBO  ASSEMBLER se o prprio compilador C/C++ pode
compilar o cdigo...   Ahhhhh,  por  motivos de COMPATIBILIDADE!  Se
voc pretende que o seu cdigo seja compilvel no TURBO C  2.0,  por
exemplo,  deve  incluir a diretiva acima!!  Alm do mais, o TASM faz
uma checagem mais detalhada do cdigo assembly do que o BCC...

    Eis um exemplo de uma funozinha escrita em assembly:

 Ŀ
   int f(int X)                                                  
   {                                                             
       asm mov     ax,X    /* AX = parametro X */                
       asm add     ax,ax   /* AX = 2 * AX */                     
       return _AX;         /* retorna AX */                      
   }                                                             
 

    Aqui segue mais uma regra:

     Se a sua funo pretende  devolver  um valor do tipo "char" ou
      "unsigned char", coloque o valor  no  registrador  AL  e  (nos
      compiladores da BORLAND) use "return _AL;"

      Se  a  sua funo pretende devolver um valor do tipo "int" ou
      "unsigned int", coloque o  valor  no  registrador AX e (tambm
      nos compiladores da BORLAND) use "return _AX;"

    A ltima linha da funo acima ("return _AX;") no  necessria,
mas  se  no  a colocarmos teremos um aviso do compilador, indicando
que "a funo precisa retornar  um  'int'".  Se voc omitir a ltima
linha   (  o  caso  dos  compiladores  da  MicroSoft  que  no  tem
pseudo-registradores) e no ligar  pros  avisos, a coisa funciona do
mesmo jeito.

    Agora voc deve estar querendo  saber  como  devolver  os  tipos
"long", "double", "float", etc...  O tipo "long" (bem como "unsigned
long")  simples:

     Se a sua funo pretende  devolver  um valor do tipo "long" ou
      "unsigned  long", coloque os 16 bits mais significativos em DX
      e os 16 menos significativos em AX.

    No existe uma forma de devolvermos  DX  e  AX  ao  mesmo  tempo
usando  os pseudo-registradores da Borland, ento prepare-se para um
"aviso" do compilador...

    Os demais tipos no so  inteiros...   so  de  ponto-flutuante,
portanto, deixe que o compilador tome conta deles.


  Trabalhando com pointers e vetores:

    D uma olhada na listagem abaixo:

Ŀ
   unsigned ArraySize(char *str)                                   
   {                                                               
#if defined(__TYNY__) || defined(__SMALL__) || defined(__MEDIUM__) 
       asm mov     si,str  /* STR = OFFSET apenas */               
#else                                                              
       asm push    ds                                              
       asm lds     si,str  /* STR = SEGMENTO:OFFSET */             
#endif                                                             
                                                                   
       asm mov     cx,-1                                           
ContinuaProcurando:                                                
       asm inc     cx                                              
       asm lodsb                                                   
       asm or      al,al                                           
       asm jnz     ContinuaProcurando                              
       asm mov     ax,cx                                           
                                                                   
#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)
       asm pop     ds          /* Restaura DS */                   
#endif                                                             
                                                                   
       return _AX;                                                 
   }                                                               


    A rotina acima  equivalente a funo strlen() de <string.h>.

    Como disse antes, nos modelos COMPACT, LARGE e HUGE  um  pointer
tem o formato SEGMENTO:OFFSET  que    armazenado  na memria em uma
grande varivel de 32 bits (os 16 mais significativos so o SEGMENTO
e os 16 menos significativos so o OFFSET).  Nos modelos TINY, SMALL
e MEDIUM apenas o OFFSET  fornecido no pointer  (ele  tem  16  bits
neste  caso),  o SEGMENTO  o assumido em DS (no devemos alter-lo,
neste caso!).

    Se voc compilar  essa  listagem  nos  modelos COMPACT, LARGE ou
HUGE o cdigo coloca em DS:SI o pointer (lembre-se: pointer  s  um
outro  nome  para "endereo de memria!").  Seno, precisamos apenas
colocar em SI o OFFSET (DS j est certo!).

    Ao sair da funo, DS deve  ser  o  mesmo de antes da funo ser
chamada...  Portanto, nos modelos "LARGOS" (hehe) precisamos  salvar
DS ANTES de us-lo e restaura-lo  DEPOIS de usado!  O compilador no
faz isso automaticamente!

    No se preocupe com SI  (neste caso!)...  este sim, o compilador
salva sozinho...

    Um macete com o  uso  de  vetores  pode ser mostrado no seguinte
cdigo exemplo:

 Ŀ
   char a[3];                                                     
   int b[3], c[3];                                                
   long d[3];                                                     
                                                                  
   void init(void)                                                
   {                                                              
       int i;                                                     
                                                                  
       for (i = 0; i < 3; i++)                                    
           a[i] = b[i] = c[i] = d[i] = 0;                         
   }                                                              
 

    O compilador gera a seguinte funo equivalente em assembly:

 Ŀ
   void init(void)                                                
   {                                                              
       asm xor     si,si           /* SI = i */                   
       asm jmp     short @1@98                                    
   @1@50:                                                         
       asm mov     bx,si           /* BX = i */                   
       asm shl     bx,1                                           
       asm shl     bx,1            /* BX = BX * 4 */              
       asm xor     ax,ax                                          
       asm mov     word ptr [d+bx+2],0 /* ?! */                   
       asm mov     word ptr [d+bx],ax                             
                                                                  
       asm mov     bx,si                                          
       asm shl     bx,1                                           
       asm mov     [c+bx],ax                                      
                                                                  
       asm mov     bx,si       /* ?! */                           
       asm shl     bx,1        /* ?! */                           
       asm mov     [b+bx],ax                                      
                                                                  
       asm mov     [a+si],al                                      
       asm inc     si                                             
   @1@98:                                                         
       asm cmp     si,3                                           
       asm jl      short @1@50                                    
   }                                                              
 

    Quando poderiamos ter:

 Ŀ
   void init(void)                                                
   {                                                              
       asm xor     si,si           /* SI = i = 0 */               
       asm jmp     short @1@98                                    
   @1@50:                                                         
       asm mov     bx,si           /* BX = i */                   
       asm shl     bx,1                                           
       asm shl     bx,1            /* BX = BX * 4 */              
       asm xor     ax,ax           /* AX = 0 */                   
       asm mov     word ptr [d+bx+2],ax /* modificado! */         
       asm mov     word ptr [d+bx],ax                             
                                                                  
       asm shr     bx,1            /* BX = BX / 2 */              
       asm mov     [c+bx],ax                                      
       asm mov     [b+bx],ax                                      
                                                                  
       asm mov     [a+si],al                                      
       asm inc     si                                             
   @1@98:                                                         
       asm cmp     si,3                                           
       asm jl      short @1@50                                    
   }                                                              
 

    Note  que  economizamos  3  instrues  em  assembly   e   ainda
aceleramos um tiquinho, retirando o movimento de um  valor  imediato
para memria (o 0 de  "mov  word  ptr [d+bx+2],0"), colocando em seu
lugar o registrador AX, que foi zerado previamente.

    Isso  parece  besteira neste cdigo, e eu concordo...  mas, e se
tivessemos:

 Ŀ
   void init(void)                                                
   {                                                              
       for (i = 0; i < 32000; i++)                                
           a[i] = b[i] = c[i] = d[i] =                            
           e[i] = f[i] = g[i] = h[i] =                            
           I[i] = j[i] = k[i] = l[i] =                            
           m[i] = n[i] = o[i] = p[i] =                            
           r[i] = s[i] = t[i] = u[i] =                            
           v[i] = x[i] = y[i] = z[i] =                            
           /* ... mais um monte de membros de vetores... */       
           = _XYZ[i] = 0;                                         
   }                                                              
 

    A perda de eficincia  e  o  ganho  de  tamanho do cdigo seriam
enormes por causa da quantidade de  vezes  que  o  loop    exeutado
(32000)  e  por  causa  do numero de movimentos de valores imediatos
para memria, "SHL"s  e  "MOV  BX,SI"  que  teriamos!  Concluso: Em
alguns casos  mais conveniente manipular VARIOS vetores com funes
escritas em assembly...

   EXEMPLO de codificao: ** O swap() aditivado :)

    Alguns  cdigos  em  C  que  precisam  trocar  o contedo de uma
varivel pelo de outra usam o seguinte macro:

 Ŀ
   #define swap(a,b) { int t; t = a; a = b; b = t; }              
 

    Bem...  a macro acima funciona  perfeitamente bem, mas vamos dar
uma olhada no cdigo  assembly  gerado  pelo compilador pro seguinte
programinha usando o macro swap():

 Ŀ
   #define swap(a,b) { int t; t = a; a = b; b = t; }              
                                                                  
   int x = 1, y = 2;                                              
                                                                  
   void main(void)                                                
   { swap(x,y); }                                                 
 

    O cdigo equivalente, aps ser pre-processado, ficaria:

 Ŀ
   int x = 2, y = 1;                                              
   void main(void) {                                              
       int t;                                                     
                                                                  
       asm mov ax,x                                               
       asm mov t,ax                                               
       asm mov ax,y                                               
       asm mov x,ax                                               
       asm mov ax,t                                               
       asm mov y,ax                                               
   }                                                              
 

    No mximo, o compilador usa o registrador SI ou DI como variavel
't'... Poderiamos fazer:

 Ŀ
   int x = 2, y = 1;                                              
   void main(void)                                                
   {                                                              
       asm mov     ax,x                                           
       asm mov     bx,y                                           
       asm xchg    ax,bx                                          
       asm mov     x,ax                                           
       asm mov     y,ax                                           
   }                                                              
 

    Repare que eliminamos  uma  instruo  em  assembly,  eliminando
tambm  um acesso  memria e uma varivel local...  T bom...  pode
me chamar de chato, mas  eu  ADORO  diminuir  o tamanho e aumentar a
velocidade de meus programas usando esse tipo de artifcio! :)


ͻ
 ASSEMBLY XIV 
ͼ

    Aqui  estou  eu   novamente!!!    Nos  textos  de  "SoundBlaster
Programming" a gente vai precisar  entender  um  pouquinho  sobre  o
TURBO ASSEMBLER, ento  disso que vou tratar aqui, ok?

    Well...     O   TURBO   ASSEMBLER   'compila'   arquivos   .ASM,
transformando-os em  .OBJ  (sorry  "C"zeiros,  mas os "PASCAL"zeiros
talvez no estejam familiarizados  com  isso!).   Os  arquivos  .OBJ
devem  ser linkados com os demais mdulos para formar o arquivo .EXE
final.   Precisamos  ento conhecer como criar um .OBJ que possa ser
linkado com cdigos em "C" e  "PASCAL".  Eis um exemplo de um mdulo
em ASSEMBLY compatvel com as duas linguagens:

 Ŀ
   IDEAL               ; Poe TASM no modo IDEAL                  
   MODEL LARGE,PASCAL  ; Modelo de memria...                    
   LOCALS                                                        
   JUMPS                                                         
                                                                 
   GLOBAL  ZeraAX : PROC   ; ZeraAX  pblico aos outros mdulos 
                                                                 
   CODESEG     ; Inicio do (segmento de) cdigo                  
                                                                 
   PROC    ZeraAX          ; Inicio de um PROCedimento.          
           sub     ax,ax                                         
           ret                                                   
   ENDP                    ; Fim do PROCedimento.                
                                                                 
   END     ; Fim do mdulo .ASM                                  
 

    As duas linhas iniciais  informam  ao  TURBO ASSEMBLER o modo de
operao (IDEAL), o modelamento de memria (LARGE -  veja  discusso
abaixo!) e o  mtodo  de  passagem  de  parametros  para  uma funo
(PASCAL).

    O modo IDEAL   um  dos  estilos  de  programao  que  o  TURBO
ASSEMBLER suporta (o outro  o  modo  MASM), e  o meu preferido por
um certo nmero de razes.  O modelo LARGE e a parametrizao PASCAL
tambm so minhas preferidas  porque  no  modelo  LARGE    possvel
termos  mais  de  um  segmento  de  dados e de cdigo (podemos criar
programas  realmente  GRANDES   e   com   MUITA   informao  a  ser
manipulada!).   PASCAL  deixa  o  cdigo  mais  limpo com relao ao
contedo dos registradores  aps  o  retorno  de  uma funo (alguns
compiladores C, em algumas circunstancias, tm a mania de  modificar
o  contedo  de  CX  no  retorno!).  Fora isso PASCAL tambm limpa a
pilha ANTES do retorno da  procedure/funo.  Mas, isso tudo tem uma
pequena desvantagem: Usando-se PASCAL, no podemos passar um  nmero
varivel  de  parametros pela pilha (os trs pontos da declarao de
uma funo C: void f(char *, ...); )!

    Ahhh...   Voc  deve  estar  se  perguntando  o que  o LOCALS e
JUMPS.  LOCALS diz ao compilador  que qualquer label comeado por @@
 local ao PROC  atual  (no    visivel em outros PROCs!)...  Assim
podemos usar labels com mesmo  nome  dentro  de  vrias  PROCs,  sem
causar nenhuma confuso:

 Ŀ
   ; modelamento, modo, etc...                                   
   LOCALS                                                        
                                                                 
   PROC    F1                                                    
           mov cx,1000                                           
   @@Loop1:                                                      
           dec cx                                                
           jnz @@Loop1                                           
           ret                                                   
   ENDP                                                          
                                                                 
   PROC    F2                                                    
           mov cx,3000                                           
   @@Loop1:                                                      
           dec cx                                                
           jnz @@Loop1                                           
           ret                                                   
   ENDP                                                          
   ;... O resto...                                               
 

    Repare  que  F1 e F2 usam o mesmo label (@@Loop1), mas o fato da
diretiva LOCALS estar  presente  informa  ao  assembler que elas so
diferentes!

    J   JUMPS   resolve   alguns  problemas  para  ns:  Os  saltos
condicionais  (JZ, JNZ, JC, JS, etc..) so relativos a posio atual
(tipo: salte para frente tantas posies a partir de onde  est!)...
Em  alguns  casos  isso  pode causar alguns erros de compilao pelo
fato do salto no poder  ser  efetuado  na faixa que queremos...  ai
entra  o  JUMPS...   Ele resolve isso alterando o cdigo para que um
salto incondicional seja efetuado.   Em  exmplo: Suponha que o label
@@Loop2 esteja muito longe do ponto atual e o salto abaixo no possa
ser efetuado:

 Ŀ
       JNZ     @@Loop2                                           
 

    O assembler substitui, caso JUMPS esteja presente, por:

 Ŀ
       JZ      @@P1                                              
       JMP     @@Loop2     ; Salto absoluto se NZ!               
   @@P1:                                                         
 

    A linha seguinte do exemplo inicial informa ao assembler  que  o
PROCedimento  ZeraAX    pblico, ou GLOBAL (visvel por qualquer um
dos mdulos que o queira!).  Logo aps, a diretiva CODESEG informa o
inicio de um segmento de cdigo.

    Entre  as  diretivas  PROC  e  ENDP vem o corpo de uma rotina em
assembly.  PROC precisa apenas do  nome da funo (ou PROCedimento).
Mais detalhes sobre PROC abaixo.

    Finalizamos a listagem com END, marcando  o  fim  do  mdulo  em
.ASM.

    Simples, n?!  Suponha agora que voc queira passar um parametro
para um PROC. Por exemplo:

 Ŀ
   ; Equivalente a:                                              
   ;   void pascal SetAX(unsigned v) { _AX = v; }                
   ;   PROCEDURE SetAX(V:WORD) BEGIN regAX := V; END;            
   IDEAL                                                         
   MODEL LARGE,PASCAL                                            
   LOCALS                                                        
   JUMPS                                                         
                                                                 
   GLOBAL SetAX : PROC                                           
                                                                 
   PROC    SetAX                                                 
   ARG     V : WORD                                              
           mov     ax,[V]                                        
           ret                                                   
   ENDP                                                          
                                                                 
   END                                                           
 

    Hummmm...   Surgiu uma diretiva nova.  ARG especifica a lista de
parametros que dever  estar  na  pilha  aps  a  chamada  de  SetAX
(ARGumentos de SetAX).  Note que V est entre colchetes na instruo
'mov'...   isso porque V , na verdade, uma referncia  memria (na
pilha!)  e  toda  referncia    memria  precisa  ser  cercada  com
colchetes (seno  d  um  baita  erro  de  sintaxe  no modo IDEAL!).
Depois da compilao o assembler substitui V pela referncia certa.

    Os  tipos,  bsicos,  vlidos  para o assembler so: BYTE, WORD,
DWORD...  No existe INTEGER,  CHAR  como  em PASCAL (INTEGER = WORD
com sinal; assim como CHAR = BYTE com sinal!).

    Para  finalizar:  Em um nico mdulo podem existir vrios PROCs:

 Ŀ
   IDEAL               ; modo IDEAL do TASM                      
   MODEL LARGE, PASCAL ; modelamento de memria...               
   LOCALS                                                        
   JUMPS                                                         
                                                                 
   ; ... aqui entra os GLOBALS para os PROCs que vc queira que   
   ;     sejam pblicos!                                         
                                                                 
   CODESEG     ; Comeo do segmento de cdigo...                 
                                                                 
   PROC    P1                                                    
       ; ... Corpo do PROC P1                                    
   ENDP                                                          
                                                                 
   PROC    P2                                                    
       ; ... Corpo do PROC P2                                    
   ENDP                                                          
                                                                 
   ;... outros PROCs...                                          
                                                                 
   END     ; Fim da listagem                                     
 

    Existem MUITOS outros detalhes  com  relao do TASM...  mas meu
objetivo no curso de ASM   a  mixagem  de  cdigo...   pls,  alguma
dvida, mandem mensagem para c ou via netmail p/ mim em 12:2270/1.

ͻ
 ASSEMBLY XV 
ͼ

    Continuando o  papo  sobre  o  TASM,  precisaremos aprender como
manipular tipos de dados mais complexos do que WORD, BYTE ou  DWORD.
Eis a descrio das estruturas!

    Uma  estrutura   o agrupamento de tipos de dados simples em uma
nica classe de armazenamento, por exemplo:

 Ŀ
   STRUC   MyType                                                 
       A   DB  ?                                                  
       B   DW  ?                                                  
   ENDS                                                           
 

    A estrutura MyType acima,  delimitada pelas palavras-chava STRUC
e ENDS, foi construida com dois tipos de dados simples (BYTE e WORD)
com os nomes de A e B. Note que as linhas acima  apenas  declaram  a
estrutura,  sem  alocar  espao  na  memria  para  ela.   Criar uma
'instancia' dessa estrutura  to  simples quanto criar uma varivel
de tipo simples:

 Ŀ
   MyVar   MyType  <0,0>                                          
 

    A sintaxe    basicamente  a  mesma  de  qualquer  declarao de
varivel em assembly, com a diferena de que o 'tipo' do  dado    o
nome  (ou  TAG)  da  estrutura  -  MyType  - e os dados iniciais dos
elementos da estrutura esto localizados entre os simbolos < e >. Na
linha acima criamos a  varivel  MyVar,  cujos  elementos so 0 e 0.
Vamos a um exemplo de uso desse novo tipo:

 Ŀ
   ;... Aqui entra o modelamento,...                             
                                                                 
   DATASEG                                                       
                                                                 
   MyVar   MyType  <0,0>                                         
                                                                 
   CODESEG                                                       
                                                                 
   PROC    SetA        ; Poe valor em A na estrutura.            
   ARG     V : Byte                                              
           mov     al,[V]                                        
           mov     [MyVar.A],al                                  
           ret                                                   
   ENDP                                                          
                                                                 
   PROC    SetB        ; Poe valor em B na estrutura.            
   ARG     V : Word                                              
           mov     ax,[V]                                        
           mov     [MyVar.B],ax                                  
           ret                                                   
   ENDP                                                          
                                                                 
   ;... Aqui entra o fim do cdigo...                            
 

    Simples, no?

    Mas, e se quisermos trabalhar  com  um  vetor  do  tipo  MyType?
Vetores de tipos mais simples  facil:

 Ŀ
   DATASEG                                                       
                                                                 
   MyVar1  dw  10 DUP (0)                                        
                                                                 
   CODESEG                                                       
                                                                 
   PROC    Fill1                                                 
       mov     cx,10                                             
       sub     bx,bx                                             
   @@FillType1:                                                  
       mov     [bx+MyVar1],0FFh                                  
       add     bx,2                                              
       dec     cx                                                
       jnz     @@FillType1                                       
       ret                                                       
   ENDP                                                          
 

    Aqui fiz da  maneira  mais  dificil  apenas para exemplificar um
mtodo de preenchimento  de  vetores.   No  caso,  BX  contm o item
desejado do vetor.  MyVar1  o  deslocamento  do  primeiro  item  do
vetor  na  memria  e  CX  a quantidade de itens do vetor.  Note que
temos um vetor de WORDS  e  precisaremos  adicionar 2 (tamnho de uma
WORD) para cara item do vetor.  No caso da estrutura, isso  fica  um
pouco mais complicado porque ela pode ter um tamanho no mltiplo de
2  (o que complica o clculo.  Por exemplo, MyType (a estrutura) tem
3 bytes de  tamanho.   Eis  a  implementao  (no otimizada) para a
rotina FillType para preenchimento de um  vetor  de  MyType  com  10
itens:

 Ŀ
   DATASEG                                                        
   MyVar   MyType  10 dup (<0,0>)                                 
                                                                  
   CODESEG                                                        
   PROC    FillType                                               
           mov     cx,10                                          
           sub     bx,bx   ; indice para localizar itens.         
   @@FillLoop:                                                    
           mov     [bx+MyVar.A],0FFh   ; * Instruo destacada... 
           mov     [bx+MyVar.B],0FFFFh                            
           add     bx,3                                           
           dec     cx                                             
           jnz     @@FillLoop                                     
           ret                                                    
   ENDP                                                           
 

    Essa rotina merece ser observada mais de perto:

    Vejamos a  instruo  destacada  na  listagem  acima...  MyVar.A
fornece o deslocamento de A, do primeiro item do vetor, na  memria,
enquanto isso BX fornece o indice do item desejado no vetor.  Assim,
BX+MyVar.A  fornecer  o  offset  do elemento A do item da estrutura
desejado.

    Well...  isso...

ͻ
 ASSEMBLY XVI 
ͼ

    Usando a memria Expandida (EMS).

    Muitos modplayers hoje  em  dia  usam  a  memria expandida para
armazenar os samples.  Neste texto veremos como funciona  a  memria
expandida e como us-la...

    A  maioria  dos  PC-ATs  com  mais de 1Mb de memria possui dois
tipos de memria:

     Convencional - Na faixa de 0 at 1Mb
     Extendida: de 1Mb em diante.

    A  memria extendida  facilmente manipulvel quando um programa
est em modo protegido e com  toda  a  memria mapeada em um ou mais
seletores.  Os 386s permitem que um seletor acesse  um  segmento  de
at  4Gb  de  tamanho...   Mas,  no   esse o nosso caso.  Temos um
pequeno programa rodando sob o MS-DOS, no modo real (modo nativo dos
processadores Intel), que tem acesso somente  memria convencional.
Podemos acessar a memria  extendida  atravs do driver HIMEM.SYS ou
usando  uma  funo  de  movimento  de  blocos  da  BIOS,  mas  isso
aumentaria em muito a complexidade do software (e, por conseguncia,
seu tamanho).

    A Lotus, Intel e Microsoft criaram a especificao EMS para esse
caso.  O  programa  EMM386.EXE,  ou  qualquer  outro  gerenciador de
memria como o QEMM, emula a memria expandida da  mesma  forma  que
uma  mquina  com  apenas  este  tipo  de  memria  faria (A memria
expandida por hardware no fez muito  sucesso nos EUA como a memria
extendida!).  A especificao EMS  simplesmente  usa  um  espao  da
memria  convencional  (chamado  de  "Page  Frame")  para  armazenar
"pginas" de 16kb da memria extendida.  Isto  ...   divida  a  sua
memria  extendida  em  diversos  blocos  de  16k e ter o nmero de
pginas (pages) que podero estar disponveis para  uso.

    O EMM (Expanded  Memory  Manager)  simplesmente  faz a cpia das
pginas  desejadas  para  o  "Page  Frame" para que o nosso software
posssa l-las e escrev-las,  copiando-as  de  volta para as pginas
corretas quando fizermos a troca de pginas  do  "Page  Frame".   No
"Page  Frame"  cabem,  normalmente, 4 pginas... fazendo um total de
64kb (ou seja, exatamente o  tamanho  de um segmento!).  Considere a
figura abaixo:


         Memria extendida          Memria extendida
                                         paginada

               Ŀ                       Ŀ
                                          Page 0
                                        Ĵ
                                          Page 1
                                        Ĵ
                                          Page 2
                                        Ĵ
                                          Page 3
                                        Ĵ
                                          Page 4
                                        Ĵ
               ...                    ...


    Ok...  a memria extendida foi dividida em 'n' pginas  de  16k.
O  "Page  Frame" fica na memria convencional.  Por exemplo, suponha
que o "Page Frame" esteja localizado no segmento 0C000h:


                         "Page Frame"

                     Ŀ0
     Pgina fisica 0                    
                     Ĵ16k
     Pgina fisica 1                    
                     Ĵ32k
     Pgina fisica 2                    
                     Ĵ48k
     Pgina fisica 3                    
                     64k


    Do offset 0 at 16k-1 fica a primeira pgina do "Page Frame", do
offset 16k at 32k-1 a segunda, e assim por diante.  A especificao
EMS nos permite colocar apenas 4  pginas no "Page Frame".  Assim, o
nosso programa escolhe cada uma das  quatro  "pginas  lgicas"  que
sero  copiadas  da  memria  extendida  para  cada  uma  das quatro
"pginas fisicas" do Page Frame.

    Vale a pena lembrar que o  Page Frame est sempre em algum lugar
da memria convencional, portanto  acessvel  aos  programas  feitos
para MS-DOS, que normalmente trabalham em modo real.

    A  interrupo  67h   a porta de entrada para as funes do EMM
(EMM386,  QEMM,  386MAX,  entre  outros).  Mas antes de comearmos a
futucar o EMM precisamos saber se ele est presente...  Eis a rotina
de deteco do EMM p/ os compiladores C da BORLAND:

---%<----------------------------------%<---------------------------
#include <io.h>
#include <fcntl.h>
#include <dos.h>

#define CARRY_BIT   (_FLAGS & 0x01)

/* Obtm a maior verso do EMM - definida em outro mdulo! */
extern int emm_majorVer(void);

/* Testa a presena do EMM
   Retorna 0 se EMM no presente ou verso < 3.xx
   Retorna 1 se tudo ok! */
int isEMMpresent(void)
{
    int handle;

    /* Tenta abrir o device driver EMMXXXX0 para leitura! */
    if ((handle = open("EMMXXXX0", O_BINARY | O_RDONLY)) == -1)
        return 0;   /* No tem EMM! */

    /* Verifica se  um arquivo ou dispositivo
       Usa IOCTL para isso! */
    _BX = handle;
    _AX = 0x4400;
    geninterrupt(0x21);
    if (!(_DX & 0x80))
        return 0;   /*  um arquivo!!! No  o EMM! */

    /* Verifica o dispositivo est ok */
    _BX = handle;
    _AX = 0x4407;
    geninterrupt(0x21);
    if (CARRY_BIT || !_AL) return 0; /* No est ok */

    /* Verifica a verso do EMM.
       Para nossos propsitos tem que ser >= que
       3.xx */
    if (emm_majorVer() < 3) return 0; /* No  ver >= 3.xx */

    /* Tudo ok... EMM presente */
    return 1;
}
---%<----------------------------------%<---------------------------

    No prximo texto mostrarei como usar o EMM.

    See ya...
ͻ
 ASSEMBLY XVII 
ͼ

    Eis  o  arquivo  .ASM com as rotinas para manipulao da memria
expandida:

 Ŀ
   IDEAL                                                         
   MODEL LARGE,PASCAL                                            
   LOCALS                                                        
   JUMPS                                                         
                                                                 
   GLOBAL  emmGetVersion : PROC                                  
   GLOBAL  emmGetPageFrameSegment : PROC                         
   GLOBAL  emmGetAvailablePages : PROC                           
   GLOBAL  emmAllocPages : PROC                                  
   GLOBAL  emmFreePages : PROC                                   
   GLOBAL  emmMapPage : PROC                                     
   GLOBAL  emmGetError : PROC                                    
                                                                 
   DATASEG                                                       
                                                                 
   emmVersion  dw  0                                             
   emmError    db  0       ; Nenhum erro ainda... :)             
                                                                 
   CODESEG                                                       
                                                                 
   ; Obtm a verso do EMM.                                      
   ; Devolve no formato 0x0X0Y (onde X  verso e Y reviso).    
   ; Prottipo em C:                                             
   ;   unsigned pascal emmGetVersion(void);                      
   PROC    emmGetVersion                                         
       mov     [emmError],0    ; Inicializa flag de erro...      
       mov     ah,46h                                            
       int     67h             ; Invoca o EMM                    
       or      ah,ah           ; Testa o sucesso da funo...    
       jz      @@no_error                                        
       mov     [emmError],ah   ; Poe erro no flag...             
       mov     ax,-1           ; ... e retorna != 0.             
       jmp     @@done                                            
       mov     ah,al           ; Prepara formato da verso.      
       and     ax,111100001111b ; A funo 46h do EMM devolve    
       mov     [emmVersion],ax  ; no formato BCD... por isso     
   @@done:                      ; precisamos formatar...         
       ret                                                       
   ENDP                                                          
                                                                 
   ; Funo: Obtm o segmento do Page Frame.                     
   ; Prottipo em C:                                             
   ;   unsigned pascal emmGetPageFrameSegment(void);             
   PROC    emmGetPageFrameSegment                                
       mov     ah,41h      ; Usa a funo 41h do EMM             
       int     67h         ; Chama o EMM                         
       mov     ax,bx       ; Poe o segmento em AX                
                           ; Funo 41h coloca o segmento do     
                           ; "Page Frame" em BX.                 
       ret                                                       
   ENDP                                                          
                                                                 
   ; Funo: Obtm o nmero de pginas disponveis na memria.   
   ; Prottipo em C:                                             
   ;   unsigned pascal emmGetAvailablePages(void);               
   ; Obs:                                                        
   ;   No verifica a ocorrencia de erros... modifique se quiser 
   PROC    emmGetAvailablePages                                  
       mov     ah,42h                                            
       int     67h     ; Invoca o EMM.                           
       mov     ax,bx   ; Poe pginas disponiveis em AX.          
       ret                                                       
   ENDP                                                          
                                                                 
   ; Aloca pginas e devolve handle.                             
   ; Prottipo em C:                                             
   ;   int pascal emmGetAvailablePages(unsigned Pages);          
   ; Obs: Devolve -1 se houve erro na alocaao e seta            
   ;      a varivel emmError.                                   
   PROC    emmAllocPages                                         
   ARG     Pages:WORD                                            
       mov     [emmError],0    ; Inicializa flag de erros...     
       mov     bx,[Pages]      ; BX = nmero de pginas a alocar 
       mov     ah,43h                                            
       int     67h             ; Invoca o EMM.                   
       or      ah,ah           ; Verifica erro do EMM.           
       jz      @@no_error                                        
       mov     [emmError],ah   ; Poe erro na varivel emmError   
       mov     dx,-1                                             
   @@no_error:                                                   
       mov     ax,dx           ; retorna cdigo de erro.         
                               ; ou o handle.                    
       ret                                                       
   ENDP                                                          
                                                                 
   ; Libera pginas alocadas.                                    
   ; Prottipo em C:                                             
   ;   void pascal emmFreePages(int handle);                     
   ; Obs: No verifica erros... modifique se quiser...           
   PROC    emmFreePages                                          
   ARG     handle:WORD                                           
       mov     dx,[handle]                                       
       mov     ah,45h                                            
       int     67h                                               
       ret                                                       
   ENDP                                                          
                                                                 
   ; Mapeia uma pgina no Page Frame.                            
   ; Prottipo em C:                                             
   ;   int pascal emmMapPage(int handle,                         
   ;                         unsigned char pfPage,               
   ;                         unsignec PageNbr);                  
   ; Onde: handle  o valor devolvido pela funo de alocao de 
   ;       pginas.                                              
   ;       pfPage  o nmero da pgina do Page Frame (0 at 3).  
   ;       PageNbr   o nmero da pgina a ser colocada no       
   ;       Page Frame (0 at mximo - 1).                        
   ; Devolve -1 se ocorreu erro e seta a varivel emmError.      
   PROC    emmMapPage                                            
   ARG     handle:WORD, pfPage:BYTE, PageNbr:WORD                
       mov     [emmError],0                                      
       mov     ah,44h                                            
       mov     al,[pfPage]                                       
       mov     bx,[PageNbr]                                      
       mov     dx,[handle]                                       
       int     67h                                               
       or      ah,ah                                             
       jz      @@no_error                                        
       mov     [emmError],ah                                     
       mov     ah,-1                                             
   @@no_error:                                                   
       mov     al,ah                                             
       ret                                                       
   ENDP                                                          
                                                                 
   ; Retorna com o erro do EMM.                                  
   ; Prottipo:                                                  
   ;   int pascal emmGetError(void);                             
   PROC    emmGetError                                           
       mov     ax,[emmError]                                     
       ret                                                       
   ENDP                                                          
                                                                 
   END                                                           
 

    Esta    uma   implementao   simplificada,   mas  para  nossos
propsitos serve muito bem.  Algumas consideraes:  A  alocao  de
memria  via  EMM no  feita da mesma maneira que a funo malloc()
de C ou GetMem() do  TURBO  PASCAL.  No  devolvido nenhum pointer.
Isto se torna bvio a partir do momento que entendemos como funciona
o EMM: Toda a manipulao de bancos de  memria    feita  de  forma
indireta pelo Page Frame.  A funo de alocao deve apenas devolver
um handle para que possamos  manipular as pginas alocadas.  Entenda
esse handle da mesma forma com que os  arquivos  so  manipulados...
Se  quisermos  usar um banco alocado precisamos informar ao EMM qual
dos bancos queremos usar, fazendo  isso  via o handle devolvido pelo
prprio EMM.

    Suponha que queiramos alocar  128kb  da memria expandida para o
nosso programa.  Precisamos alocar 8 pginas  lgicas  (8  *  16k  =
128k).  Chamariamos a funo emmAllocPages() em C da seguinte forma:

 Ŀ
   #include <conio.h>                                            
   #include <stdlib.h>                                           
                                                                 
   int emm_handle;                                               
                                                                 
   void f(void)                                                  
   {                                                             
       /* ... */                                                 
       if ((emm_handle = emmAllocPages(8)) == -1) {              
           cprintf("EMM ERROR #%d\r\n", emmGetError());          
           exit(1);                                              
       }                                                         
       /* ... */                                                 
   }                                                             
 

    Na  funo  emmAllocPages() optei por devolver -1 para indicar o
insucesso da funo...  Voc pode  arrumar um esquema diferente para
chegar isso (por  exemplo,  checando  a  varivel  emmError  aps  a
chamada a funo!).

    Well...  Temos 8 pginas lgicas disponveis.  E agora?...  As 8
pginas esto sempre numeradas de 0 at o mximo - 1.  No nosso caso
teremos as pginas 0 at 7 disponveis ao nosso programa.  Lembre-se
que cada uma tem apenas 16k de tamanho e que podem ser arranjadas de
qq  maneira  q  vc  queira  no  Page Frame.  Vamos usar as 4 pginas
iniciais como  exemplo...  para  isso  precisamos  mapea-las no Page
Frame usando a funo emmMapPage().

 Ŀ
   void f(void)                                                   
   {                                                              
       int i;                                                     
                                                                  
       /* ... */                                                  
       for (i = 0; i < 4; i++)                                    
           emmMapPage(emm_handle,i,i);                            
   }                                                              
 

    Depois  deste  pequeno  loop  sabemos  que qualquer alterao no
contedo do  Page  Frame  alterar  as  pginas  que  esto mapeadas
nele...:) Simples n?  S nos resta conhecer o endereo  inicial  do
Page Frame:

 Ŀ
   #include <dos.h>                                               
                                                                  
   void far *PageFrameAddr;                                       
                                                                  
   void f(void)                                                   
   {                                                              
       /* ... */                                                  
       PageFrameAddr = MK_FP(emmGetPageFrameSegment(), 0);        
       /* ... */                                                  
   }                                                              
 

    Ao  fim do uso da memria expandida precisamos dealocar o espao
previamente alocado...  C  e  C++  dealocam automaticamente qualquer
espao alocado por malloc(), calloc() e funes afins...   No    o
caso  de  nossas rotinas acima...  ento acostume-se a manter a casa
em ordem e usar  a  funo  emmFree()  quando  no precisar mais das
pginas alocadas.

    Isso  tudo  no funcionar se o EMM no estiver instalado...  No
texto anterior mostrei a rotina  para  determinar a presena do EMM.
E, no mesmo texto, apareceu a rotina emm_majorVer().  Eis  a  rotina
abaixo:

 Ŀ
   int emm_majorVer(void)                                        
   { return ((int)emmGetVersion() >> 8); }                       
 

    See ya l8tr
ͻ
 ASSEMBLY XVIII 
ͼ

    Hummmm...  Estamos na era dos  32 bits...  ento por que esperar
mais para discutirmos as novidades da linha 386  e  486?   Eles  no
diferem  muito do irmo menor: o 8086.  A no ser pelo fato de serem
"maiores". :)

    O 8086 e 80286 tm barramento de dados de  16  bits  de  tamanho
enquanto o 386 e o 486 tem  de 32 bits.  Nada mais justo que existam
modificaes nos registradores tambm:

           31              16 15                  0
           Ŀ
                               AH   AX   AL      EAX
           Ĵ
                               BH   BX   BL      EBX
           Ĵ
                               CH   CX   CL      ECX
           Ĵ
                               DH   DX   DL      EDX
           

    Os registradores de uso geral continuam os velhos conhecidos  de
sempre...  S que existem os registradores  de uso geral de 32 bits:
EAX, EBX, ECX e EDX, onde os 16 bits menos significativos destes so
AX, BX, CX e DX, respectivamente.

           31              16 15                  0
           Ŀ
                                    SI           ESI
           Ĵ
                                    DI           EDI
           Ĵ
                                    BP           EBP
           Ĵ
                                    SP           ESP
           

    Da mesma forma, os registradores SI, DI, BP  e  SP  ainda  esto
aqui...  bem como os seus equivalentes de 32 bits: ESI, EDI,  EBP  e
ESP.

    Os registradores  de  segmento  (chamados  de  SELETORES desde o
surgimento do 80286)  so  os  mesmos  e  no  mudaram  de  tamanho,
continuam com 16 bits: CS, DS, ES e SS.  Mas acrecentaram outros: FS
e  GS.  Isto ...  Agora existe um registrador de segmento de cdigo
(CS), um segmento de pilha (SS) e quatro segmentos de dados (DS, ES,
FS e GS).  Lembrando que DS    o segmento de dados default.  Repare
na ordem alfabtica dos registradores de segmento de dados...

    O registrador Instruction Pointer  tambm  continua  o  mesmo...
E tambm existe o seu irmo maior... EIP:

           31              16 15                  0
           Ŀ
                                    IP           EIP
           

    Da mesma forma os FLAGS tambm so os mesmos de sempre...  mas o
registrador FLAGS tambm foi expandido para 32  bits  e  chamado  de
EFLAGS.   Os sinalizadores extras so usados em aplicaes especiais
(como por exemplo,  chaveamento  para  modo protegido, modo virtual,
chaveamento de tarefas, etc...).

    Alguns outros registradores foram  adicionados ao conjunto: CR0,
CR1, CR3, TR4 a TR7.  DR0 a DR3, DR6 e DR7  (todos  de  32  bits  de
tamanho).  Esses novos registradores  so  usados no controle da CPU
(CR?), em testes (TR?) e DEBUG (DR?).  No tenho maiores informaes
sobre alguns deles e por isso no vou descrev-los aqui.

     Novas instrues foram criadas para o 386 e ainda  outras  mais
novas  para  o  486  (imagino  que  devam  existir outras instrues
especficas para o Pentium!). Eis algumas delas:

 BSF (Bit Scan Forward)

    Processador: 386 ou superior

    Sintaxe: BSF dest,src

    Descrio:

        Procura pelo  primeiro  bit  setado  no  operando "src".  Se
encontrar, coloca o numero do bit no operando "dest" e seta  o  flag
Zero.   Se  no  encontrar,  no altera o operando "dest" e reseta o
flag Zero.   BSF  procura  o  bit  setado  comeando  pelo  bit 0 do
operando "src".

    Exemplo:

        BSF     AX,BX

 BSR (Bit Scan Reverse)

    Processador: 386 ou superior

    Sintaxe: BSR dest,src

    Descrio:

        Faz a mesma coisa que BSF, porm a ordem de procura comea a
partir do bit mais significativo do operando "src".

    Exemplo:

        BSR     AX,BX

 BSWAP

    Processador: 486 ou superior

    Sintaxe: BSWAP reg32

    Descrio:

        Inverte a ordem das words de um registrador de 32 bits.

    Exemplo:

        BSWAP EAX

 BT (Bit Test)

    Processador: 386 ou superior

    Sintaxe: BT dest,src

    Descrio:

        Copia  o  contedo  do  bit do operando "dest" indicado pelo
operando "src" para o flag Carry.

    Exemplo:

        BT  AX,3

    Observaes:

      1- Aparentemente esta  instruo  no  aceita  operandos de 32
         bits.
      2- No exemplo acima o bit 3 de AX ser  copiado  para  o  flag
         Carry.

 BTC (Bit Test And Complement)

    Processador: 386 ou superior

    Sintaxe:  BTC dest,src

    Descrio:

        Instruo  identica   BT, porm complementa (inverte) o bit
do operando "dest".

 BTR e BTS

    Processador: 386 ou superior

    Sintaxe: BTR dest,src
             BTS dest,src

    Descrio:

        Instrues identicas a BT, porm  BTR zera o bit do operando
destino e BTS seta o bit do operando destino.

 CDQ (Convert DoubleWord to QuadWord)

    Processador: 386 ou superior

    Sintaxe: CDQ

    Descrio:

        Expande o contedo do registrador EAX para o par EDX e  EAX,
preenchendo com o bit 31 de EAX os bits de EDX (extenso de sinal).

 CWDE (Convert Word to DoubleWord Extended)

    Processador: 386 ou superior

    Sintaxe: CWDE

    Descrio:

        Esta  instruo  expande  o   registrador   AX   para   EAX,
considerando  o sinal.  Ela  equivalente a instruo CWD, porm no
usa o par DX:AX para isso.

 CMPXCHG

    Processador: 486 ou superior

    Sintaxe: CMPXCHG dest,src

    Descrio:

        Compara  o  acumulador  (AL,  AX  ou  EAX  -  dependendo dos
operandos) com o operando  "dest".   Se  forem iguais o acumulador 
carregado com o contedo de "dest", caso contrrio com o contedo de
"src".

    Exemplo:

        CMPXCHG BX,CX

 INVD (Invalidate Cache)

    Processador: 486 ou superior

    Sintaxe: INVD

    Descrio:

        Limpa o cache interno do processador.

 JECXZ

    Processador: 386 ou superior

    Observao:  identica a instruo  JCXZ,  porm o teste  feito
no registrador extendido ECX (32 bits).

 LGS e LFS

    Processador: 386 ou superior

    Observao: Essas instrues so identicas as instrues  LDS  e
LES, porm trabalham com os novos registradores de segmento.

 MOVSX e MOVZX

    Processador: 386 ou superior

    Sintaxe: MOVSX dest,src
             MOVZX dest,src

    Descrio:

        Instrues  teis  quando  queremos  lidar  com operandos de
tamanhos diferentes.  MOVZX move  o  contedo do operando "src" para
"dest" (sendo que "src" deve ser menor que "dest") zerando  os  bits
extras.   MOVSX  faz  a  mesma coisa, porm copiando o ltimo bit de
"src" nos bits extras de "dest" (converso com sinal).

    Exemplo:

        * Usando  instrues  do  8086,  para  copiar  AL  para  BX
          precisariamos fazer isto:

                MOV     BL,AL
                MOV     BH,0

        * Usando MOVZX podemos simplesmente fazer:

                MOVZX   BX,AL

 Instruo condicional SET

    Processador: 386 ou superior

    Sintaxe: SET? dest
             (Onde ?  a condio...)

    Descrio:

        Poe 1 no operando destino  se  a  condio  for  satisfeita.
        Caso contrrio poe 0.

    Exemplo:

        SETNZ AX
        SETS  EBX
        SETZ  CL

 SHRD e SHLD (Double Precision Shift)

    Processador: 386 ou superior

    Sintaxe: SHRD dest,src,count
             SHLD dest,src,count

    Descrio:

        Faz  o  shift  para  esquerda  (SHLD)  ou  direita (SHRD) do
operando "dest" "count" vezes, porm  os bits que seriam preenchidos
com zeros so preenchidos com o contudo dos bits do operando "src".
Eis um grfico exemplificando:

    SHRD
          src                 dest
    Ŀ    Ŀ
                  >Ĵ             > Carry
        
     n            0      n            0

    O operando "src" no  alterado no processo.  O  flag  de  Carry
contm o ltimo bit que "saiu" do operando "dest".

    Exemplo:

        SHLD    EAX,ECX,3
        SHRD    AX,BX,CL

 Instrues que manipulam blocos...

    CMPSD,  LODSD,  MOVSD, STOSD, INSD e OUTSD se comportam da mesma
forma que suas similares  de  8  ou  16  bits (CMPSB, CMPSW, etc..),
porm usam os registradores extendidos (ESI, EDI, ECX, EAX) e operam
com dados de 32 bits de tamanho (DoubleWords).

    Existem mais instrues...  Consulte algum manual da Intel ou  o
hipertexto  HELPPC21...  Pedirei aos Sysops do VixNET BBS (agora com
6 linhas hehehe) para  deixarem disponivel o arquivo 386INTEL.ZIP...
que  o guia tcnico para o processador 386.
--------------------------------------------------------------------

Dvidas a respeito dos novos recursos:

Q Os  segmentos  tem  mais  que  64k  no  modo  real,  j  que  os
registradores  extendidos  podem  ser  usados  neste   modo?    Como
funcionaria uma instruo do tipo:

    MOV     [ESI+3],EAX

R  No...   no  modo  real  os  segmentos  continuam  a ter 64k de
tamanho.  Os registradores extendidos podem  ser usados a vontade e,
quando  usados como offset em um segmento, os 16 bits superiores so
ignorados. A instruo apresentada funcionaria da mesma forma que:

    MOV     [SI+3],EAX

Q Onde e quando deve-se usar os novos registradores de segmentos?
R Onde e quando  voc  quiser.   Pense  neles  como se fosse novos
segmentos de dados extras.   Na  realidade  voc  apenas  conseguir
us-los  se  explicit-los  numa  instruo  que  faz  referncia  
memria, por exemplo:

    MOV FS:[BX],AL

Q Posso usar os registradores extendidos nas instrues normais ou
apenas nas novas instrues?
R  Pode  us-los  nas  instrues  "normais".   A  no  ser  que a
instruo no permita operandos de 32 bits...

    That's all for now, pals...
ͻ
 ASSEMBLY XIX 
ͼ

Oi povo...

    Estou retomando  o  desenvolvimento  do  curso  de  assembly aos
poucos  e na nova srie:  Otimizao de cdigo para programadores C.
Well... vo  algumas  das  rotinas  para  aumentar  a velocidade dos
programas C que lidam com strings:

Ŀ
   strlen() 


    A  rotina  strlen()    implementada  da  seguinte  maneira  nos
compiladores C mais famosos:

 Ŀ
   int strlen(const char *s)                                      
   {                                                              
       int i = 0;                                                 
       while (*s++) ++i;                                          
       return i;                                                  
   }                                                              
 

    Isso  gera  um  cdigo  aproximadamente  equivalente,  no modelo
small, a:

 Ŀ
   PROC    _strlen NEAR                                    
   ARG     s:PTR                                           
       push    si                   ; precisamos preservar 
       push    di                   ;  SI e DI.            
       xor     di,di                ; i = 0;               
       mov     si,s                                        
   @@_strlen_loop:                                         
       mov     al,[si]                                     
       or      al,al                ; *s == '\0'?          
       jz      @@_strlen_exit       ; sim... fim da rotina.
       inc     si                   ; s++;                 
       inc     di                   ; ++i;                 
       jmp     short @@_strlen_loop ; retorna ao loop.     
   @@_strlen_exit:                                         
       mov     ax,si                ; coloca i em ax.      
       pop     si                   ; recupara SI e DI.    
       pop     di                                          
       ret                                                 
   ENDP                                                    
 

    Eis uma implementao mais eficaz:

Ŀ
 #ifdef __TURBOC__                                                   
 #include <dos.h>      /* Inclui pseudo_registradores */             
 #define _asm  asm                                                   
 #endif                                                              
                                                                     
   int     Strlen(const char *s)                                     
   {                                                                 
       _asm push    es                                               
                                                                     
 #ifndef __TURBOC__                                                  
       _asm push    di                                               
 #endif                                                              
                                                                     
 #if defined(__LARGE__) || defined(__HUGE__) || defined(__COMPACT__) 
       _asm les     di,s                                             
 #else                                                               
       _asm mov     di,ds                                            
       _asm mov     es,di                                            
       _asm mov     di,s                                             
 #endif                                                              
                                                                     
       _asm mov     cx,-1                                            
       _asm sub     al,al                                            
       _asm repne   scasb                                            
                                                                     
       _asm not     cx                                               
       _asm dec     cx                                               
       _asm mov     ax,cx                                            
                                                                     
 #ifndef __TURBOC__                                                  
       _asm pop     di                                               
 #endif                                                              
                                                                     
       _asm pop     es                                               
                                                                     
 #ifdef __TURBOC__                                                   
       return _AX;                                                   
 #endif                                                              
   }                                                                 


    Essa nova Strlen() [Note que  Strlen() e no strlen(), para no
confundir com a funo que j existe na biblioteca padro!]  ,  com
certeza,  mais  rpida  que  strlen(),  pois  usa a instruo "repne
scasb" para varrer o vetor a  procura  de um caracter '\0', ao invs
de  recorrer  a  vrias instrues em um loop.  Inicialmente, CX tem
que ter o maior valor  possvel  (-1  no sinalizado = 65535).  Essa
funo  falha  no  caso  de  strings muito longas (maiores que 65535
bytes), dai precisaremos usar strlen()!

    Uma vez encontrado o caracter '\0' devemos  inverter  CX.   Note
que se invertermos 65535 obteremos  0.  Acontece que o caracter '\0'
tambem    contado...  dai,  depois  de  invertermos   CX,   devemos
decrement-lo tambm, excluindo o caracter nulo!

    No se preocupe com DI se  vc usa algum compilador da BORLAND, o
compilador trata de salv-lo e recuper-lo sozinho...

Ŀ
   strcpy() 


    Embora alguns compiladores sejam espertos o suficiente para usar
as intrues de manipulao de blocos a implementao mais comum  de
strcpy :

 Ŀ
   char *strcpy(char *dest, const char *src)                      
   {                                                              
       char *ptr = dest;                                          
       while (*dest++ = *src++);                                  
       return ptr;                                                
   }                                                              
 

    Para maior compreeno a linha:

 Ŀ
       while (*dest++ = *src++);                                  
 

    Pode ser expandida para:

 Ŀ
       while ((*dest++ = *src++) != '\0');                        
 

    O cdigo gerado, no modelo small, se assemelha a:

 Ŀ
   PROC    _strcpy                                                 
   ARG     dest:PTR, src:PTR                                       
       push    si          ; Salva SI e DI                         
       push    di                                                  
                                                                   
       mov     si,[dest]  ; Carrega os pointers                    
                                                                   
       push    si                  ; salva o pointer dest          
                                                                   
       mov     di,[src]                                            
                                                                   
   @@_strcpy_loop:                                                 
       mov     al,byte ptr [di]    ; Faz *dest = *src;             
       mov     byte ptr [si],al                                    
                                                                   
       inc     di                  ; Incrementa os pointers        
       inc     si                                                  
                                                                   
       or      al,al               ; AL == 0?!                     
       jne     short @@_strcpy_loop ; No! Continua no loop!       
                                                                   
       pop     ax                  ; Devolve o pointer dest.       
                                                                   
       pop     di          ; Recupera DI e SI                      
       pop     si                                                  
                                                                   
       ret                                                         
   ENDP                                                            
 

    Este cdigo foi  gerado  num  BORLAND  C++  4.02!  Repare que as
instrues:

 Ŀ
       mov        al,byte ptr [di]    ; Faz *dest = *src;          
       mov        byte ptr [si],al                                 
 

    Poderiam ser facilmente substituidas por um MOVSB se a ordem dos
registradores  de   ndice   no   estivesse   trocada.    Porm   a
substituio,  neste  caso, causaria mais mal do que bem.  Num 386 as
instrues MOVSB,  MOVSW  e  MOVSD  consomem  cerca  de  7 ciclos de
mquina.  No mesmo microprocessador, a instruo MOV, movendo de  um
registrador  para  a memria consome apenas 2 ciclos.  Perderiamos 3
ciclos em cada iterao (2 MOVS  =  4 ciclos).  Numa string de 60000
bytes, perderiamos cerca de 180000 ciclos de  mquina...   Considere
que  cada  ciclo de mquina NAO  cada ciclo de clock.  Na realidade
um nico ciclo de mquina equivale  a alguns ciclos de clock - vamos
pela mdia...  1 ciclo de mquina  2 ciclos de clock, no melhor dos
casos!

    Vamos dar uma olhada no mesmo cdigo no modelo LARGE:

 Ŀ
   PROC _strcpy                                                   
   ARG  dest:PTR, src:PTR                                         
   LOCAL temp:PTR                                                 
       mov        dx,[word high dest]                             
       mov        ax,[word low dest]                              
       mov        [word high temp],dx                             
       mov        [word low temp],ax                              
                                                                  
   @@_strcpy_loop:                                                
       les        bx,[src]                                        
                                                                  
       inc        [word low src]                                  
                                                                  
       mov        al,[es:bx]                                      
                                                                  
       les        bx,[dest]                                       
                                                                  
       inc        [word low dest]                                 
                                                                  
       mov        [es:bx],al                                      
                                                                  
       or         al,al                                           
       jne        short @@_strcpy_loop                            
                                                                  
       mov        dx,[word high temp]                             
       mov        ax,[word low temp]                              
       ret                                                        
   _strcpy    endp                                                
 

    Opa...  Cade  os  registradores  DI  e  SI?!   Os  pointers  so
carregados  varias  vezes  durante o loop!!!  QUE DESPERDICIO!  Essa
strcpy()  uma sria candidata a otimizao!

    Eis  a  minha  implementao  para  todos  os modelos de memria
(assim como Strlen()!):

Ŀ
   char *Strcpy(char *dest, const char *src)                        
   {                                                                
       _asm    push    es                                           
 #if defined(__LARGE__) || defined(__HUGE__) || defined(__COMPACT__)
       _asm    push    ds                                           
       _asm    lds     si,src                                       
       _asm    les     di,dest                                      
 #else                                                              
       _asm    mov     si,ds                                        
       _asm    mov     es,si                                        
       _asm    mov     si,src                                       
       _asm    mov     di,dest                                      
 #endif                                                             
       _asm    push    si                                           
                                                                    
   Strcpy_loop:                                                     
       _asm    mov     al,[si]                                      
       _asm    mov     es:[di],al                                   
                                                                    
       _asm    inc     si                                           
       _asm    inc     di                                           
                                                                    
       _asm    or      al,al                                        
       _asm    jne     Strcpy_loop                                  
                                                                    
       _asm    pop     ax                                           
 #if defined(__LARGE__) || defined(__HUGE__) || defined(__COMPACT__)
       _asm    mov     ax,ds                                        
       _asm    mov     dx,ax                                        
       _asm    pop     ds                                           
 #endif                                                             
       _asm    pop     es                                           
   }                                                                


    Deste jeito  os  pointers  so  carregados  somente  uma vez, os
registradores de  segmento  DS  e  ES  so  usados  para  conter  as
componentes  dos  segmentos  dos  pointers,  que podem ter segmentos
diferentes (no modelo large!), e os registradores SI e DI so usados
como indices separados para cada pointer!

    A parte critica do  cdigo    o  interior  do  loop.   A  nica
diferena  entre  essa rotina e a rotina anterior (a no ser a carga
dos pointers!)  a instruo:

Ŀ
       _asm    mov     es:[di],al                                   


    Que consome 4 ciclos  de  mquina.   Poderiamos usar a instruo
STOSB, mas esta consome 4 ciclos de mquina num  386  (porm  5  num
486).   Num  486  a instruo MOV consome apenas 1 ciclo de mquina!
Porque MOV consome 4 ciclos  neste  caso?!  Por causa do registrador
de segmento explicitado!  Lembre-se que o registrador de segmento DS
 usado como default a no ser que usemos os registradores BP ou  SP
como indice!

    Se vc  est  curioso  sobre  temporizao  de  instrues  asm e
otimizao de cdigo, consiga  a  mais  nova  verso  do  hypertexto
HELP_PC. Ele  muito bom. Quanto a livros, ai vo dois:

     Zen and the art of assembly language
     Zen and the art of code optimization

    Ambos de Michael Abrash.

    AHHHHHHHH...  Aos mais atenciosos e experientes:  No coloquei o
prlogo  e nem o eplogo das rotinas em ASM intencionalmente.  Notem
que estou usando o modo  IDEAL  do TURBO ASSEMBLY para no confundir
mais ainda o pessoal  com  notaes  do  tipo:   [BP+2],  [BP-6],  e
detalhes  do  tipo  decremento  do  stack  pointer  para alocao de
variveis locais...  Vou deixar a coisa o mais simples possvel para
todos...

    Da  mesma  forma:   Um  aviso  para  os  novatos...   NAO TENTEM
COMPILAR os cdigos em ASM (Aqueles que comeo por  PROC)...   Eles
so  apenas  uma  demonstrao  da  maneira  como as funes "C" so
traduzidas para o assembly pelo compilador, ok?

    Well... prximo texto tem mais...
ͻ
 ASSEMBLY XX 
ͼ

    Impressionante como as  demonstraes grficas (DEMOS) conseguem
ser  to  rpidas  com  todas  aquelas  transformaes   geomtricas
(objetos  movimentando-se  no  espao  tridimensional),  musicas  em
background, etc...  A complexidade sugere a utilizao de rotinas em
ponto-flutuante    para    os    calculos    "cabeludos"...     Opa!
Ponto-flutuante?!   Mas  isso  muito lerdo!!!!  Toma muito tempo de
CPU...  E nem sempre o  feliz proprietrio de um microcomputador tem
um 486DX ou um 386 com  co-processador!   Como    que  esses  caras
conseguem tanta velocidade?!

    A  resposta pode estar num mtodo conhecido como "aritimtica de
ponto-fixo", que  o objetivo deste texto!

    Imagine que possamos  escrever  um  nmero "quebrado" (com casas
decimais) da seguinte maneira:

  Ŀ
                                  
  
           parte inteira                 parte fracionria

    A "casa" mais a esquerda  o bit mais significativo, e a mais  a
direita o menos significativo.  Assim os 16 bits mais significativos
(parte  inteira)  nos diz a "parte inteira" do nmero (lgico, n?).
E os 16  bits  menos  significativos  (parte  fracionria) nos diz a
parte  fracionria  do  nmero (outra vez, lgico!).  De forma que o
bit menos significativo destes 32  bits   equivalente a 2 elevado a
potncia de -16 (ou seja: 1/65536). Eis um exemplo:

 Ŀ
   0000000000000000.1000000000000000b = 0.5 = 1/2                 
   0000000000000000.0100000000000000b = 0.25 = 1/4                
   0000000000000000.0010000000000000b = 0.125 = 1/8               
   0000000000000000.1110000000000000b = 0.875                     
   0000000000000001.1000000000000000b = 1.5 = 1 + 1/2             
   0000000000000011.0010010000111111b =
 (aprox.)                
   0000000000000000.1101110110110011b  cos(
/6)  0.866 (aprox.) 
 

    No sei se deu para entender, mas do bit menos significativo at
o mais significativo, o expoente vai aumentando, s que o bit  menos
significativo  tem expoente -16.  Assim, o bit 1 tem expoente -15, o
seguinte -14, etc...  at  o  ltimo,  15.   O  ponto  entre os dois
conjuntos  de  16  bits  foi  adicionado  apenas  para  facilitar  a
visualizao no exemplo acima.

    Ok... ento  possvel representar "nmeros quebrados"  em  dois
conjuntos de 16 bits... a pergunta : Pra que?!

    Aritimtica  com  nmeros inteiros sempre  mais rpida do que a
aritimtica com nmeros em ponto-flutuante.  Tendo co-processador ou
no!   Mesmo  que  vc  tenha   um  486DX4  100MHz,  os  calculos  em
ponto-flutuante sero mais lerdamente efetuados  do  que  os  mesmos
calculos  com  nmeros  inteiros  (usando os registradores da CPU!).
Neste ponto entra a  aritimtica  de  ponto-fixo  (note que o "ponto
decimal" no  muda  de  posio...).   Vejamos  o  que  acontece  se
somarmos dois nmeros em ponto fixo:

 Ŀ
   0.25 + 1.75 = 2.0                                              
                                                                  
     0000000000000000.0100000000000000b =    0.25                 
   + 0000000000000001.1100000000000000b =  + 1.75                 
                      
     0000000000000010.0000000000000000b =    2.00                 
 

    Realmente  simples...   apenas uma soma binria...  Suponha que
tenhamos um nmero em ponto fixo  no registrador EAX e outro no EDX.
O cdigo para somar os dois nmeros ficaria to simples quanto:

 Ŀ
   ADD     EAX,EDX                                                
 

    O  mesmo  ocorre na subtrao...  Lgicamente, a subtrao  uma
adico com o segundo  operando  complementado (complemento 2), ento
no h problemas em fazer:

 Ŀ
   SUB     EAX,EDX                                                
 

    A adio ou subtrao de dois nmeros em ponto fixo consome de 1
a  2  ciclos de mquina apenas, dependendo do processador... o mesmo
no ocorre com aritimtica em ponto-flutuante!

    A complicao comea a surgir na multiplicao e diviso de dois
nmeros em  ponto-fixo.   No  podemos  simplesmente  multiplicar ou
dividir como fazemos com a soma:

 Ŀ
     0000000000000001.0000000000000000                             
   * 0000000000000001.0000000000000000                             
                              
     0000000000000000.0000000000000000 + carry                     
 

    Nultiplicando 1 por 1 deveriamos  obter  1,  e no 0.  Vejamos a
multiplicao de dois valores menores que 1 e maiores que 0:

 Ŀ
     0000000000000000.100000000000000     0.5                      
   * 0000000000000000.100000000000000   * 0.5                      
                        
     0100000000000000.000000000000000  16384.0                     
 

    Hummm...  o  resultado  deveria  dar  0.25.   Se  dividirmos   o
resultado por 65536 (2^16) obteremos o resultado correto:

 Ŀ
     0100000000000000.000000000000000 >> 16 =                      
     0000000000000000.010000000000000       = 0.25                 
 

    Ahhh...  mas, e como ficam os nmeros maiores ou iguais a 1?!  A
instruo IMUL dos microprocessadores  386  ou superiores permitem a
multiplicao de dois inteiros de 32 bits resultando num inteiro  de
64  bits  (o  resultado  ficar  em  dois  registradores  de 32 bits
separados!).  Assim, para multiplicarmos  dois nmeros em ponto fixo
estabelecemos a seguinte regra:

 Ŀ
   resultado = (n1 * n2) / 65536           ou                     
   resultado = (n1 * n2) >> 16                                    
 

    Assim, retornando ao primeiro caso de multiplicao (em  notao
hexa agora!):

Ŀ
   0001.0000h * 0001.0000h = 000000010000.0000h                   
                                                                  
   Efetuando o shift de 16 bits para a direita:                   
                                                                  
   00010000.0000h >> 16 = 0001.0000h                              
                                                                  


    Em assembly isso seria to simples como:

 Ŀ
   PROC    FixedMul                                               
   ARG     m1:DWORD, m2:DWORD                                     
                                                                  
       mov     eax,m1                                             
       mov     ebx,m2                                             
       imul    ebx                                                
       shrd    eax,edx,16                                         
       ret                                                        
                                                                  
   ENDP                                                           
 

    A instruo IMUL, e  no  MUL,  foi  usada  porque os nmeros de
ponto fixo so sinalizados (o bit mais significativo    o  sinal!).
Vale  aqui a mesma regra de sinalizao para nmeros inteiros:  Se o
bit mais significativo  estiver  setado  o  nmero   negativo e seu
valor absoluto  obtido atravs do seu complemento (complemento  2).
Quanto a manipulao dos sinais numa multiplicao... deixe isso com
o IMUL! :)

    A diviso tambm tem as  suas complicaes... suponha a seguinte
diviso:

 Ŀ
    0001.0000h                                                     
    = 0000.0000h (resto = 0001.000h)                   
    0002.0000h                                                     
 

    A explicao deste resultado  simples:  estamos fazendo diviso
de dois nmeros inteiros...  Na  aritimtica inteira a diviso com o
dividendo menor que o divisor sempre resulta num quociente zero!

    Eis  a  soluo:   Se  o  divisor  est  deslocado  16 bits para
esquerda  (20000h    diferente  de  2,  certo!?),  ento precisamos
deslocar o dividendo 16 bits  para  esquerda  antes  de  fazermos  a
diviso!   Felizmente  os  processadores  386  e superiores permitem
divises com dividendos de 64bits  e  divisores de 32bits.  Assim, o
deslocamento  de  16  bits  para  esquerda  do   dividendo   no   
problemtica!

 Ŀ
   0001.0000h << 16 = 00010000.0000h                               
                                                                   
   00010000.0000h / 0002.0000h = 0000.8000h                        
                                                                   
       ou seja:                                                    
                                                                   
   1 / 2 = 0.5                                                     
 

    Eis a rotina em assembly que demonstra esse algorritmo:

 Ŀ
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       mov     eax,d1      ; pega dividendo                       
       mov     ebx,d2      ; pega divisor                         
                                                                  
       sub     edx,edx                                            
                                                                  
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       idiv    ebx                                                
       ret                                                        
                                                                  
   ENDP                                                           
 

    Isso tudo  muito interessante,  no?!  Hehehe... mas vou deixar
vc mais desesperado ainda:  A diviso  tem  um  outro  problema!   E
quanto aos sinais?!  O bit mais significativo de um inteiro pode ser
usado  para  sinalizar  o nmero (negativo = 1, positivo = 0), neste
caso teremos ainda que complementar o nmero para sabermos seu valor
absoluto.  Se simplesmente zeraramos EDX  e o bit mais significativo
estiver setado estaremos dividindo  um  nmero  positivo  por  outro
nmero  qualquer  (j  que  o  bit  mais  significativo  dos  64bits
resultantes ser 0!).  Vamos  complicar  mais  um pouquinho o cdigo
da diviso para sanar este problema:

 Ŀ
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       sub     cl,cl       ; CL = flag                            
                           ; == 0 -> resultado positivo.          
                           ; != 0 -> resultado negativo.          
                                                                  
       mov     eax,d1      ; pega dividendo                       
                                                                  
       or      eax,eax     ;  negativo?!                         
       jns     @@no_chs1   ; no! ento no troca sinal!          
                                                                  
       neg     eax         ; ! ento troca o sinal e...          
       inc     cl          ; incrementa flag.                     
   @@no_chs1:                                                     
                                                                  
       mov     ebx,d2      ; pega divisor                         
                                                                  
       or      ebx,ebx     ;  negativo?!                         
       jns     @@no_chs2   ; no! ento no troca sinal!          
                                                                  
       neg     ebx         ; ! ento troca sinal e...            
       dec     cl          ; decrementa flag.                     
   @@no_chs2:                                                     
                                                                  
       sub     edx,edx                                            
                                                                  
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       div     ebx         ; diviso de valores positivos...      
                           ; ... no precisamos de idiv!          
                                                                  
       or      cl,cl       ; flag == 0?                           
       jz      @@no_chs3   ; sim! resultado  positivo.           
                                                                  
       neg     eax         ; no! resultado  negativo...         
                           ; ... troca de sinal!                  
   @@no_chs3:                                                     
       ret                                                        
                                                                  
   ENDP                                                           
 

    Se ambos os valores so negativos (d1 e d2)  ento  o  resultado
ser  positivo.   Note que se d1  negativo CL  incrementado.  Logo
depois... se d2 tambm   negativo,  CL  decrementado (retornando a
0).  A rotina ento efetuar diviso de valores positivos e  somente
no final  que mudar o sinal do resultado, se for necessrio!

    Uma  considerao  a  fazer  :   Como "transformo" um nmero em
ponto flutuante em ponto-fixo e vice-versa?!

    Comecemos  pela transformao de nmeros inteiros em ponto-fixo:
O nosso ponto-fixo est situado exatamente no meio de uma doubleword
(DWORD), o que  nos  d  16  bits  de  parte  inteira  e 16 de parte
fracionria.  A transformao de um nmero inteiro para ponto-fixo 
mais que simples:

 Ŀ
   FixP = I * 65536          ou                                   
   FixP = I << 16                                                 
                                                                  
   onde FixP = Fixed Point (Ponto fixo)                           
        I    = Integer (Inteiro)                                  
 

    Desta forma os 16 bits superiores contero o nmero inteiro e os
16  bits  inferiores  estaro  zerados  (um  inteiro  no  tem parte
fracionria, tem?!).

    Se quisermos obter a  componente  inteira  de um nmero de ponto
fixo basta fazer o shift de 16 bits para direita.

    A  mesma  regra   pode   ser   usada   para   transformao   de
ponto-flutuante para ponto-fixo, s que no usaremos shifting e  sim
multiplicaremos  explicitamente  por 65536.0!  Suponha que queiramos
transforma o nmero PI em ponto-fixo:

 Ŀ
   FixP = FloatP * 65536.0                                         
                                                                   
   FixP = 3.1415... * 65536.0 = 205887.4161                        
   FixP = 205887                                                   
                                                                   
   FixP = 0003.2439h                                               
 

    O que nos d  uma  boa  aproximao (se transformarmos 32439h em
ponto flutuante novamente obteremos 3.14149475...).  Apenas a  parte
inteira  do  resultado  (205887.4161) nos interessa.  (205887).  Mas
apareceu um pequenino problema que talvez vc no tenha notado...

    Suponha que o  resultado  da  multiplicao  por  65536.0  desse
205887.865  (por  exemplo,  t?!).  Esse nmero est mais prximo de
205888 do que de 205887!  Se tomarmos apenas a componente inteira do
resultado obteremos um erro ainda  maior  (ponto-fixo  no    muito
preciso, como vc pode notar  pelo  exemplo acima!).  Como fazer para
obter sempre a componente inteira mais  aproximada?!   A  soluo  
somar 0.5 ao resultado da multiplicao por 65536.0!

    Se a componente fracionria for maior ou igual  a  0.5  ento  a
soma  da  componente  fracionria com 0.5 dar valor menor que 2.0 e
maior ou igual a 1.0 (ou  seja, a componente inteira dessa soma ser
sempre 1.0).  Ao contrrio, se a componente fracionria do resultado
da multiplicao por 65536.0 for menor que 0.5  ento  a  componente
inteira  da  soma  dessa componente por 0.5 ser sempre 0.0!  Ento,
somando  o  resultado  da  multiplicao  com  0.5  podemos  ou  no
incrementar a componente  inteira  de  acordo  com  a proximidade do
nmero real com o inteiro mais prximo!

    Se a aproximao no for feita, o erro gira em torno  de  15e-6,
ou seja: 0.000015 (erro a patir da quinta casa decimal!).

    A transformao de um  nmero de ponto-flutuante para ponto-fixo
fica ento:

 Ŀ
   FixP = (FloatP * 65536.0) + 0.5                                 
                                                                   
   FixP = (3.1415... * 65536.0) + 0.5 = 205887.4161 + 0.5          
   FixP = 205887.9161                                              
   FixP = 205887  (ignorando a parte fracionria!)                 
                                                                   
   FixP = 0003.2439h                                               
 

    A transformao contrria (de ponto-fixo para ponto-flutuante) 
menos  traumtica, basta dividir o nmero de ponto fixo por 65536.0.
Eis algumas macros, em C, para as transformaes:

 Ŀ
   #define INT2FIXED(x)    ((long)(x) << 16)                      
   #define FIXED2INT(x)    ((x) >> 16)                            
   #define DOUBLE2FIXED(x) (long)(((x) * 65536.0) + 0.5)          
   #define FIXED2DOUBLE(x) ((double)(x) / 65536.0)                
 

    Aritimtica de  ponto-fixo    recomendvel  apenas  no  caso de
requerimento de velocidade e quando no necessitamos de preciso nos
calculos.  O menor nmero  que  podemos  armazenar  na  configurao
atual     1.5259e-5   (1/65536)   e   o   maior     32767.99998,
aproximadamente.  Nmeros maiores  ou  menores  que  esses  no  so
representveis.   Se  o seu programa pode extrapolar esta faixa, no
use   ponto-fixo,   vc   obter   muitos   erros   de   preciso  e,
ocasionalmente, talvez at um erro de "Division By Zero".

    Ateno...   A  implementao dos procedimentos (PROC) acima so
um pouquinho diferentes para mixagem de cdigo...  Os compiladores C
e PASCAL atuais utilizam o par DX:AX para retornar um DWORD,  assim,
no fim de cada PROC e antes do retorno coloque:

 Ŀ
   shld    edx,eax,16                                             
   shr     eax,16                                                 
 

    Ou faa melhor ainda: modifique os cdigos!

    Eis a minha implementao  para  as  rotinas FixedMul e FixedDiv
para mixagem de cdigo com C ou TURBO PASCAL:

 Ŀ
   /*                                                             
   ** Arquivo de cabealho FIXED.H                                
   */                                                             
   #if !defined(__FIXED_H__)                                      
   #define __FIXED_T__                                            
                                                                  
   /* Tipagem */                                                  
   typedef long    fixed_t;                                       
                                                                  
   /* Macros de converso */                                      
   #define INT2FIXED(x)    ((fixed_t)(x) << 16)                   
   #define FIXED2INT(x)    ((int)((x) >> 16))                     
   #define DOUBLE2FIXED(x) ((fixed_t)(((x) * 65536.0) + 0.5))     
   #define FIXED2DOUBLE(x) ((double)(x) / 65536.0)                
                                                                  
   /* Declarao das funes */                                   
   fixed_t pascal FixedMul(fixed_t, fixed_t);                     
   fixed_t pascal FixedDiv(fixed_t, fixed_t);                     
                                                                  
   #endif                                                         
 
 Ŀ
   {*** Unit FixedPt para TURBO PASCAL ***}                       
   UNIT FIXEDPT;                                                  
                                                                  
   {} INTERFACE {}                                                
                                                                  
   {*** Tipagem ***}                                              
   TYPE                                                           
       TFixed  = LongInt;                                         
                                                                  
   {*** Declarao das funes ***}                               
   FUNCTION FixedMul(M1, M2 : TFixed) : TFixed;                   
   FUNCTION FixedDiv(D1, D2 : TFixed) : TFixed;                   
                                                                  
   {} IMPLEMENTATION {}                                           
                                                                  
   {*** Inclui o arquivo .OBJ compilado do cdigo abaixo ***}     
   {$L FIXED.OBJ}                                                 
                                                                  
   {*** Declara funes como externas ***}                        
   FUNCTION FixedMul(M1, M2 : TFixed) : TFixed; EXTERN;           
   FUNCTION FixedDiv(D1, D2 : TFixed) : TFixed; EXTERN;           
                                                                  
   {*** Fim da Unit... sem inicializaes! ***}                   
   END.                                                           
 
 Ŀ
   ; FIXED.ASM                                                    
   ; Mdulo ASM das rotinas de multiplicao e diviso em         
   ; ponto fixo.                                                  
                                                                  
   ; Modelamento de memria e modo do compilador.                 
   IDEAL                                                          
   MODEL LARGE,PASCAL                                             
   LOCALS                                                         
   JUMPS                                                          
   P386        ; Habilita instrues do 386                       
                                                                  
   ; Declara os procedimentos como pblicos                       
   GLOBAL FixedMul : PROC                                         
   GLOBAL FixedDiv : PROC                                         
                                                                  
   ; Inicio do segmento de cdigo.                                
   CODESEG                                                        
                                                                  
   PROC    FixedMul                                               
   ARG     m1:DWORD, m2:DWORD                                     
                                                                  
       mov     eax,[m1]                                           
       mov     ebx,[m2]                                           
       imul    ebx                                                
       shr     eax,16  ; Coloca parte fracionria em AX.          
                       ; DX j contm parte inteira!              
       ret                                                        
                                                                  
   ENDP                                                           
                                                                  
   ; Diviso em ponto fixo.                                       
   ; d1 = Dividendo, d2 = Divisor                                 
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       sub     cl,cl       ; CL = flag                            
                           ; == 0 -> resultado positivo.          
                           ; != 0 -> resultado negativo.          
                                                                  
       mov     eax,[d1]    ; pega dividendo                       
                                                                  
       or      eax,eax     ;  negativo?!                         
       jns     @@no_chs1   ; no! ento no troca sinal!          
                                                                  
       neg     eax         ; ! ento troca o sinal e...          
       inc     cl          ; incrementa flag.                     
   @@no_chs1:                                                     
                                                                  
       mov     ebx,[d2]    ; pega divisor                         
                                                                  
       or      ebx,ebx     ;  negativo?!                         
       jns     @@no_chs2   ; no! ento no troca sinal!          
                                                                  
       neg     ebx         ; ! ento troca sinal e...            
       dec     cl          ; decrementa flag.                     
   @@no_chs2:                                                     
                                                                  
       sub     edx,edx     ; Prepara para diviso.                
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       div     ebx         ; diviso de valores positivos...      
                           ; ... no precisamos de idiv!          
                                                                  
       or      cl,cl       ; flag == 0?                           
       jz      @@no_chs3   ; sim! resultado  positivo.           
                                                                  
       neg     eax         ; no! resultado  negativo...         
                           ; ... troca de sinal!                  
   @@no_chs3:                                                     
                                                                  
       ;                                                          
       ; Apenas adequa para o compilador                          
       ;                                                          
       shld    edx,eax,16  ; DX:AX contm o DWORD                 
       shr     eax,16                                             
                                                                  
       ret                                                        
                                                                  
   ENDP                                                           
                                                                  
   END                                                            
 

ͻ
 ASSEMBLY XXI 
ͼ

    Ol!!...  Acho que voc concorda comigo que essa srie de textos
no estaria completa se eu  no  falasse  alguma coisa a respeito de
progrmao da placa de vdeo VGA, n?!  Acho que ns temos razo  em
pensar assim! :)

    Inicialmente comearei a descrever  a  placa  VGA, depois vem as
descries da SVGA e VESA.  No pretendo gastar "trocentas" horas de
digitao e depurao de cdigo na descrio desses padres..  quero
apenas dar uma idia geral do funcionamento desses dispositivos para
que voc possa caminhar com as prprias pernas mais tarde...


     Video Graphics Array

    O  padro  VGA  o sucessor dos padres EGA e CGA, todos criados
pela IBM...  A diferena  bsica  do  VGA  para  os  outros dois  o
aumento da resoluo e de cores.  Eis uma comparao  dos  modos  de
maior  resoluo  e  cores  desses trs padres (aqui esto listados
apenas os modos grficos!):

     Ŀ
                            CGA        EGA        VGA     
     ͵
      Maior resoluo     640x200     640x350    640x480  
     Ĵ
       Maior nmero de       4          16         16     
            cores        (320x200)   (640x350)  (640x480) 
                                                          
                                                   256    
                                                (320x200) 
     

    O  padro  VGA  suporta  at 256 cores simultanemente no modo de
vdeo 13h (320x200x256).  E no modo de mais alta resoluo suporta o
mesmo nmero de cores que a EGA, que so apenas 16.

    Quanto ao  nmero  de  cores,  as  placas  EGA  e  VGA  so mais
flexveis  que  a  irm   mais   velha   (a   CGA).   As  cores  so
"reprogramveis", isto , de uma palette de 256k cores (256 * 1024 =
262144 cores), na VGA, podemos escolher 256...  Duma palette  de  64
cores  podemos  usar  16, na EGA...  A VGA , sem sombra de dvidas,
superior!

    A  forma como podemos selecionar essas cores todas ser mostrada
mais abaixo (Como  sempre  as  coisas  boas  so sempre deixadas pra
depois, n?! hehe).

    Em tempo:  O modo 640x480 (16 cores) ser usado como exemplo nas
prximas listagens dos textos daqui pra frente...  O modo grfico de
320x200 com 256 cores ser discutido em outra oportunidade, bem como
o famoso MODE X (modo de vdeo no documentado da VGA - e largamente
descrito por Michael Abrash em  seus  artigos  para  a  revista  Dr.
Dobb's).


     Memria de vdeo

    Existe  um  grande  obstculo  com  relao  a modos grficos de
resolues altas:   A  segmentao  de  memria!   Lembre-se  que os
processadores Intel enxergam  a  memria  como  blocos  de  64k  no
sequenciados  (na  verdade,  sobrepostos!)...   No  modo  grfico de
resoluo 640x480 da VGA (que  suporta  16 cores no mximo), suponha
que cada byte da memria de vdeo armazenasse  2  pixeis  (16  cores
poderia  equivaler a 4 bits, no poderia?!)...  Well isso nos d 320
bytes por linha (meio byte por pixel -> 640 / 2 = 320!).

    Com os 320 bytes por  linha  e  480 linhas teriamos 153600 bytes
numa tela cheia!  Ocupando  3  segmentos  da  memria  de  vdeo  (2
segmentos  contguos  completos  e mais 22528 bytes do terceiro!)...
Puts...  Imagine a complexidade  do  algoritmo que escreve apenas um
ponto no vdeo!  Seria necessrio selecionarmos o segmento do  pixel
e  o  offset...  isso  pra  aplicativos grficos de alta performance
seria um desastre!

    A  IBM  resolveu  esse  tipo  de  problema  criando  "planos" de
memria...  Cada plano equivale a um bit de um pixel.  Dessa  forma,
se  em  um  byte  temos  oito  bits e cada plano armazena 1 bit de 1
pixel... em um byte de  cada  plano  teremos  os 8 bits de 8 pixeis.
Algo como:  O byte no plano 0 tem os oito bits 0 de  oito  pixeis...
no  plano  1  temos  os  oito  bits  1 de oito pixeis... e assim por
diante.  De forma que o  circuito  da VGA possa "sobrepor" os planos
para formar os quatro bits de um  nico  pixel...   A  representao
grfica abaixo mostra a sobreposio dos planos:

            Ŀ
          Ŀ 
        Ŀ  
      Ŀ   
                                                     
                                                     
                                                    3
                                                   2
                                                  1
                                                0 
      

    Esses  so  os  quatro  planos  da memria de vdeo.  O plano da
frente   o  plano  0,  incrementando  nos  planos  mais interiores.
Suponha que na posio inicial de cada plano tenhamos  os  sequintes
bytes:

 Ŀ
   Plano 0: 00101001b                                             
   Plano 1: 10101101b                                             
   Plano 2: 11010111b                                             
   Plano 3: 01010100b                                             
 

    Os  bits  mais  significativos  de  cada  plano formam um pixel:
(0110b), os  bits  seguintes  o  segundo  pixel  (0011b), o terceiro
(1100b), e assim por diante at o oitavo pixel (1110b).  Como  temos
16 cores no modo 640x480, cada pixel tem 4 bits de tamanho.

    Com  esse  esquema  biruta temos um espao de apenas 38400 bytes
sendo usados para cada plano  de  vdeo...   Se cada byte suporta um
bit de cada pixel ento temos que uma linha tem 80 bytes de  tamanho
(640 / 8).  Se temos 480 linhas, teremos 38400 bytes por plano.

    Tome  nota  de duas coisas... estamos usando um modo de 16 cores
como exemplo para facilitar  o  entendimento  (os modos de 256 cores
so mais complexos!) e esses 38400 bytes em cada plano de bits   um
espao  de  memria  que pertence  placa de vdeo e  INACESSVEL a
CPU!!!!  Apenas a placa de vdeo pode ler e gravar nessa memria.  A
placa VGA (e  tambm  a  EGA)  usam  a  memria  RAM do sistema para
saberem quais  posies  de  um  (ou  mais)  planos  de  bits  sero
afetados.  Isso  assunto para o prximo tpico:


     A memria do sistema:

    Os  adaptadores  VGA  usam  o  espao  de "memria linear" entre
0A0000h  e  0BFFFFh  (todo  o  segmento  0A000h  e  todo  o segmento
0B000h)...  Essa memria  apenas uma rea de  rascunho,  j  que  a
placa  VGA  tem  memria  prpria...   A  CPU precisa de uma memria
fisicamente presente  para  que  possa  escrever/ler  dados... da a
existencia desses dois segmentos contguos de memria, mas a VGA no
os usa da mesma forma que a CPU!

    Citei dois segmentos contguos... mas no existe a limitao  de
apenas  um  segmento?!   Well... existe... o segmento 0B000h  usado
apenas nos modos-texto (onde o  segmento 0B800h  usado...  0B000h 
para o adaptador monocromtico - MDA)... os modos-grficos  utilizam
o  segmento 0A000h (a no ser aqueles modos grficos compatveis com
a CGA!).

    A memria do sistema  usada  como rascunho pela VGA (e pela EGA
tambm!!)...  A VGA colhe  as  modificaes  feitas  na  memria  do
sistema e transfere para a memria de vdeo.  A forma com que isso 
feito  depende  do  modo  com  que programamos a placa de vdeo para
faz-lo... podemos modificar  um  plano  de  bits  por vez ou vrios
planos, um bit por vez, vrios bits de uma vez, etc.  Na  realidade,
dependendo  do  modo  com que os dados so enviados para a placa VGA
no precisamos  nem  ao  menos  saber  O  QUE  estamos escrevendo na
memria do sistema, a VGA toma conta de ajustar a memria  de  vdeo
por  si  s,  usando apenas o endereo fornecido pela CPU para saber
ONDE deve fazer a modificao!


     Selecionando os planos de bits...

    Em todos os modos de  escrita precisamos selecionar os planos de
bits que sero afetados...  Isso  feito atravs de  um  registrador
da  placa  VGA:   MapMask...  Porm, antes de sairmos futucando tudo
quanto  endereo de I/O da  placa VGA precisamos saber COMO devemos
us-los!

    A maioria dos registradores da placa VGA  esto  disponveis  da
seguinte  maneira:  Primeiro informamos  placa qual  o registrador
que queremos acessar e  depois  informamos  o  dado a ser escrito ou
lido...  A tcnica  a seguinte:  escrevemos num endereo de  I/O  o
nmero  do  registrador... no endereo seguinte o dado pode ser lido
ou escrito...

    No caso de MapMask, este  registrador    o nmero 2 do CIRCUITO
SEQUENCIADOR  da  placa  VGA.   O  circuito  sequenciador  pode  ser
acessado pelos endereos de I/O 3C4h e 3C5h (3C4h conter  o  nmero
do registro e 3C5h o dado!).  Eis a estrutura do registro MapMask:

                            7 6 5 4 3 2 1 0
                           ͻ
                           ????    
                           ͼ
                                       
                                        plano 0
                                       plano 1
                                      plano 2
                                     plano 3

    De  acordo  com  o  desenho  acima...  os quatro bits inferiores
informam a placa VGA qual dos planos ser modificado.  Lembre-se que
cada plano tem um bit de um pixel (sendo o plano 0 o proprietrio do
bit menos significativo).  Vamos a nossa primeira rotina:

 Ŀ
   ; VGA1.ASM                                                     
   ; Compile com:                                                 
   ;                                                              
   ;   TASM vga1                                                  
   ;   TLINK /x/t vga1                                            
   ;                                                              
   ideal                                                          
   model tiny                                                     
   locals                                                         
   jumps                                                          
                                                                  
   codeseg                                                        
                                                                  
   org 100h                                                       
   start:                                                         
       mov     ax,12h      ; Poe no modo 640x480                  
       int     10h                                                
                                                                  
       mov     ax,0A000h   ; Faz ES = 0A000h                      
       mov     es,ax                                              
       sub     bx,bx       ; BX ser o offset!                    
                                                                  
       mov     dx,03C4h    ; Aponta para o registro               
       mov     al,2        ; "MapMask"                            
       out     dx,al                                              
                                                                  
       inc     dx          ; Incrementa endereo de I/O           
                                                                  
       mov     al,0001b    ; Ajusta para o plano 0                
       out     dx,al                                              
                                                                  
       mov     [byte es:bx],0FFh   ; Escreve 0FFh                 
                                                                  
       mov     al,0100b    ; Ajusta para o plano 2                
       out     dx,al                                              
                                                                  
       mov     [byte es:bx],0FFh   ; Escreve 0FFh                 
                                                                  
       sub     ah,ah       ; Espera uma tecla!                    
       int     16h         ; ... seno no tem graa!!! :)        
                                                                  
       mov     ax,3        ; Volta p/ modo texto 80x25            
       int     10h                                                
                                                                  
       int     20h         ; Fim do prog                          
                                                                  
   end start                                                      
 

    Depois de compilar e rodar  o  VGA1.COM voc vai ver uma pequena
linha magenta no canto superior esquerdo do vdeo...  Se voc quiser
que apenas o pixel em (0,0) seja aceso, ento mude o valor 0FFh  nas
instrues  "mov  [byte es:bx],0FFh" para 80h.  O motivo para isso 
que cada byte tem apenas um  bit  de  um  pixel, isto , cada bit do
byte equivale a um bit do pixel... necessitamos  alterar  os  quatro
planos  de  bits  para setarmos os quatro bits de cada pixel (quatro
bits nos do 16 combinaes)... assim,  se  um byte tem oito bits, o
primeiro byte dos quatro planos de bits tem os oito pixeis iniciais,
sendo o bit mais significativo do primeiro  byte  de  cada  plano  o
primeiro pixel.

    Deu  pra  notar  que  apenas  modificamos  os planos 0 e 2, n?!
Notamos tambm que desta maneira  no  temos como alterarar um nico
pixel...  sempre  alteraremos  os  oito  pixels!!    Mas,   no   se
preocupe...  existem  outros  recursos na placa VGA...  Entendendo o
esquema de "planos de bits" j est bom por enquando...

    At a prxima...
ͻ
 ASSEMBLY XXII 
ͼ

    Alguma vez aconteceu de voc ter aquela rotina quase concluda e
quando foi test-la viu  que  estava faltando alguma coisa?!  Bem...
se no aconteceu voc  um sortudo...  Quando eu estava comeando  a
entender  o funcionamento da placa VGA me dispus a construir rotinas
bsicas de  traagem  de  linhas  horizontais  e verticais... porm,
quando tinha algum bitmap atrs da linha acontecia  uma  desgraa!!!
Parte do bitmap sumia ou era substitudo por uma sujeirinha chata!

    Obviamente  eu  ainda  no  tinha  dominado  o  funcionamento da
placa... por isso, vamos continuar com os nossos estudos...


     A mascara de bits e os LATCHES da VGA.

    Existe uma maneira  de  no  alterarmos  bits indesejveis em um
byte de cada plano...  Suponha que queiramos modificar apenas o  bit
mais  significativo  de  um  byte  nos  planos  de  bits, deixando o
restante exatamente como estavam antes!

    Well...  Isso pode ser feito  de  duas formas:  Primeiro lemos o
byte de um plano, realizamos um OR ou um AND com esse byte e o  byte
com  o  bit  a  ser alterado (zerando-o ou setando-o de acordo com a
modificao que faremos...  veja  as  instrues  AND  e  OR num dos
textos iniciais do curso de ASM para ter um  exemplo  de  como  isso
pode  ser feito!)... depois da operao lgica, escrevemos o byte na
mesma posio... Essa  a maneira mais dispendiosa!

    A placa  VGA  permite  que  criemos  uma  mascara  de  bits para
podermos alterar apenas aqueles bits desejados...  Isso  feito pelo
registrador BitMask.  Mas, antes temos que  ler  o  byte  inteiro...
hummm...  acontece que existe um registrador intermedirio, interno,
que retm o ltimo byte lido de um plano de bits... esse registrador
 conhecido como LATCH.

    Basta ler um byte da memria  do sistema que os bytes dos quatro
planos de bits vo para seus LATCHES...  Depois precisamos  mascarar
os  bits  que  no queremos modificar no registrador BitMask para s
ento escrever na memria  do  sistema  (no  plano de bits!)...  No
esquecendo de setar os planos  de  bits  que  queremos  alterar  via
MapMask, como visto no ltimo texto!

    O  funcionamento  dos  latches  em  conjunto  com  BitMask    o
seguinte:   Uma vez carregados os latches, apenas os bits ZERADOS de
BitMask sero copiados de volta  para os planos de bits selecionados
por  MapMask.   Em  contrapartida,  os  bits  SETADOS   em   BitMask
correspondem  aos  bits  vindos  da  memria  do  sistema,  que  so
fornecidos  pela  CPU.   Dessa  maneira  a  nossa rotina no tem que
propriamente ler o contedo de  um  plano  de bits (alis, o que for
lido pela CPU pode muito bem ser ignorado!)... no necessitamos  nem
ao  menos  efetuar  operaes  lgicas  para  setar  ou  resetar  um
determinado bit do byte que ser escrito num plano de bits!

    Vmos  no  ltimo  texto  que  o  registro  MapMask faz parte do
circuito SEQUENCIADOR da VGA.  O registro BitMask est localizado em
outro circuito.  Mais  exatamente  no  controlador grfico (Graphics
Controller  - que chamaremos de GC)...  O funcionamento  o mesmo do
que o circuito sequenciador, em  termos  de endereos de I/O, citado
no ltimo texto:  Primeiro devemos informar o nmero do  registro  e
depois  o valor.  O GC pode ser acessado a partir do endereo de I/O
03CEh e o nmero do registro BitMask  8.

    Eis nosso segundo exemplo:

 Ŀ
   ; VGA2.ASM                                                     
   ; Compile com:                                                 
   ;                                                              
   ;   TASM vga2                                                  
   ;   TLINK /x/t vga2                                            
   ;                                                              
   ideal                                                          
   model tiny                                                     
   locals                                                         
   jumps                                                          
                                                                  
   codeseg                                                        
                                                                  
   org 100h                                                       
   start:                                                         
       mov     ax,12h      ; Poe no modo 640x480                  
       int     10h                                                
                                                                  
       mov     ax,0A000h   ; Faz ES = 0A000h                      
       mov     es,ax                                              
       sub     bx,bx       ; BX ser o offset!                    
                                                                  
       mov     dx,03C4h    ; Seleciona planos 0 e 2...            
                                                                  
       mov     ax,0502h    ; dem a fazer: mov al,2               
                           ;               mov ah,0101b           
                                                                  
       out     dx,ax                                              
                                                                  
       mov     dx,03CEh    ; Mascara todos os bits,               
       mov     ax,8008h    ;  exceto o bit 7                      
       out     dx,ax                                              
                                                                  
       mov     al,[byte es:bx]     ; carrega os latches da VGA    
                                   ;  note que AL no nos         
                                   ;  interessa!!!                
       mov     [byte es:bx],0FFh   ; Escreve 0FFh                 
                                                                  
       sub     ah,ah       ; Espera uma tecla!                    
       int     16h         ; ... seno no tem graa!!! :)        
                                                                  
       mov     ax,3        ; Volta p/ modo texto 80x25            
       int     10h                                                
                                                                  
       int     20h         ; Fim do prog                          
                                                                  
   end start                                                      
 

    Temos algumas novidades aqui...  Primeiro:   possvel  escrever
o  nmero  de um registro e o dado quase que ao mesmo tempo... basta
usar a instruno OUT DX,AX - recorra a textos anteriores para ver o
funcionamento dessa  instruo!.   Segundo:   mesmo  escrevendo 0FFh
(todos os bits setados) na memria do sistema, apenas o bit que  no
est mascarado ser modificado, graas ao BitMask!!  Terceiro:  Mais
de  um  plano  de  bits  pode ser alterado ao mesmo tempo!  Note que
nesse cdigo escrevemos na  memria  de  vdeo  apenas  uma vez e os
planos 0 e 2 foram alterados (continua a cor MAGENTA, no?!).


     Problemas  vista!

    Ok... aparentemente a  coisa  funciona  bem...  dai  eu fao uma
simples  pergunta:   O que aconteceria se o ponto em (0,0) estivesse
inicialmente "branco" e usassemos a rotina acima?!

    Hummmm...  Se o ponto   branco,  a  cor   15...  15  1111b em
binrio, ou seja, todos os planos de bits teriam o bit 7 do primeiro
byte setados...  A rotina acima "seta" os bits 7  do  primeiro  byte
dos planos 0 e 2... assim  a  cor CONTINUARIA branca!!  MAS COMO SOU
TEIMOSO, EU QUERO MAGENTA!!!

    A soluo seria colocar as seguintes linhas antes  da  instruo
"sub ah,ah" na listagem acima:

 Ŀ
   mov     dx,03C4h    ; Seleciona os planos 1 e 3                 
   mov     ax,0A02h                                                
   out     dx,ax                                                   
                                                                   
   mov     [byte es:bx],0 ; escreve 0 nos planos 1 e 3             
 

    Precisamos zerar os bits 7  dos  planos  1  e 3...  Note que nas
linhas acima no carreguei os latches da VGA atravs  de  leitura...
alis... no carreguei de forma  alguma.   No preciso fazer isso os
latches dos planos 1 e 3 no foram  alterados  desde  a  sua  ltima
leitura...   repare  que  no  "desmascarei"  os  bits  no  registro
BitMask... dai no ter  a  necessidade  de mascar-los de novo... s
preciso escrever 0 nos planos 1 e 3 para que o bit 7 seja alterado.

    Puts... que  mo-de-obra!!...   Felizmente  existem  meios  mais
simples  de  fazer  isso tudo...  Ahhhhhh, mas  claro que isso fica
pra um prximo texto! :))

ͻ
 ASSEMBLY XXIII 
ͼ

    Confesso a todos vocs  que  a  experincia  que venho tendo com
relao a programao da placa VGA comeou com a leitura de  artigos
e de um livro  de  um  camarada  chamado Michael Abrash...  Gostaria
muito de conseguir outros livros desse sujeito!!  Alis, se  puderem
colocar  as  mos  num livro chamado "Zen of Graphics Programming",
garanto  que  no  haver arrependimentos!   um excelente livro com
MUITOS macetes, rotinas e explicaes  sobre a VGA...  Tudo isso com
bom humor!!! :)

    Outra boa aquisio, pelo menos com  relao ao captulo 10,  o
livro "Guia do Programador para as placas  EGA  e  VGA"  da  editora
CIENCIA  MODERNA  (o  autor    Richard  E.  Ferraro).  Explicitei o
captulo 10 porque acho que  esse  livro  s  no  to bom devido a
falhas de traduo (coisa que acontece com  quase  todos  os  livros
traduzidos   no  Brasil!)...   O  captulo  10    to  somente  uma
referncia (enorme e confusa,  mas  quebra  bem  o galho) a todos os
registradores da VGA.  Esse  um dos livros que adoraria poder ter o
original, em ingls!


     Onde paramos?!

    Ahhh... sim... at aqui vimos o  modo  de  escrita  "normal"  da
placa VGA.  Esse modo de escrita  o usado pela BIOS e    conhecido
como  "modo  de  escrita 0".  Antes de passarmos pra outros modos de
escrita  vale  a   pena   ver   o   funcionamento   de  outros  dois
registradores:   o  "Enable  Set/Reset"  e  o  "Set/Reset".    Esses
registros,  como  voc vai ver, facilita muito o trabalho de escrita
nos planos de bits.


     Ligando e desligando bits...

    Na listagem do text 22 vimos que  possvel a escrita em mais de
um plano de bits ao mesmo tempo (basta habilitar em MapMask).  Vimos
tambm que  os  planos  de  bits  no  habilitados  para escrita via
MapMask no so automaticamente  zerados...  lembra-se  do  caso  do
pixel branco que queriamos transformar em magenta?!

    Com tudo isso, tinhamos que fazer pelo menos 3 acessos  memria
do  sistema:  Uma leitura para carregar os latches, uma escrita para
setar bits nos planos selecionados, e mais uma escrita para zerar os
bits dos outros planos...  Isso  sem contar com os registradores que
teremos que atualizar:  MapMask  e  BitMask.   Surpreendentemente  a
instruo  OUT   uma das que mais consomem ciclos de mquina da CPU
(especialmente nos 386s e 486s! Veja no seu HELP_PC).

    Na tentativa de  reduzir  os  acessos    memria  do sistema (e
indiretamenta aos planos de bits!), lanaremos mo dos registradores
"Enable Set/Reset" e "Set/Reset".  Eis a descrio deles:

         REGISTRO ENABLE SET/RESET

                            7 6 5 4 3 2 1 0
                           ͻ
                           ????    
                           ͼ
                                       
                                        S/R bit 0
                                       S/R bit 1
                                      S/R bit 2
                                     S/R bit 3

         REGISTRO SET/RESET

                            7 6 5 4 3 2 1 0
                           ͻ
                           ????    
                           ͼ
                                       
                                        plano 0
                                       plano 1
                                      plano 2
                                     plano 3

    O registrador "Enable Set/Reset" informa  a placa VGA quais bits
do registrador "Set/Reset" vo ser transferidos para  os  planos  de
bits.  Note que cada bit de "Set/Reset" est associado a um plano de
bits!  Os bits no habilitados em "Enable Set/Reset" viro da CPU ou
dos latches, dependendo do contedo  de  BitMask  -  como  vimos  no
exemplo do texto 22.

    No sei se voc percebeu, mas podemos agora escrever quatro bits
diferentes  nos quatro planos de bits ao mesmo tempo...  Se setarmos
os quatro bits de "Enable  Set/Reset", os quatro bits em "Set/Reset"
sero transferidos para a memria de vdeo.  Nesse caso o que a  CPU
enviar para a memria do sistema ser ignorado (j que  "Set/Reset"
que est fornecendo os dados!).

    Os registradores MapMask  e  BitMask  continuam funcionando como
antes...  Se no habilitarmos um ou  mais planos de bits em MapMask,
este(s) plano(s)  no  ser(o)  atualizado(s)!   Note  que  "Enable
Set/Reset"  diz ao circuito da placa VGA que deve ler os respectivos
bits de "Set/Reset" e  coloc-los  nos respectivos planos de bits...
mas, MapMask pode ou no permitir essa transferncia!!!   Quanto  ao
registrador  BitMask,  vai bem obrigado (veja discusso sobre ele no
texto anterior).

    Hummm... virou baguna!  Agora podemos  ter dados vindos de trs
fontes:  da  CPU  (via  memria  do  sistema),  dos  latches,  e  do
registrador  Set/Reset.   Bem...  podemos  at  usar essa baguna em
nosso favor!

    "Enable Set/Reset" e "Set/Reset"  pertencem ao mesmo circuito de
BitMask:  o controlador grfico  (GC).   S  que  o  ndice (que  o
nmero do registro no circuito!) de "Set/Reset"   0  e  de  "Enable
Set/Reset"  1.

    Vamos a um exemplo com esses dois registradores:

 Ŀ
   ; VGA3.ASM                                                     
   ; Compile com:                                                 
   ;                                                              
   ;   TASM vga3                                                  
   ;   TLINK /x/t vga3                                            
   ;                                                              
   ideal                                                          
   model tiny                                                     
   locals                                                         
   jumps                                                          
                                                                  
   codeseg                                                        
                                                                  
   org 100h                                                       
   start:                                                         
       mov     ax,12h      ; Poe no modo 640x480                  
       int     10h                                                
                                                                  
       mov     ax,0A000h   ; Faz ES = 0A000h                      
       mov     es,ax                                              
       sub     bx,bx       ; BX ser o offset!                    
                                                                  
       mov     dx,03C4h                                           
       mov     ax,0F02h    ; MapMask = 1111b                      
       out     dx,ax                                              
                                                                  
       mov     dx,03CEh                                           
       mov     ax,8008h    ; BitMask = 10000000b                  
       out     dx,ax                                              
       mov     ax,0500h    ; Set/Reset = 0101b                    
       out     dx,ax                                              
       mov     ax,0F01h    ; Enable Set/Reset = 1111b             
       out     dx,ax                                              
                                                                  
       mov     al,[byte es:bx]     ; carrega os latches da VGA    
                                   ;  note que AL no nos         
                                   ;  interessa!!!                
                                   ; Isso  necessrio pq vamos   
                                   ;  alterar apenas o bit 7. Os  
                                   ;  demais so fornecidos pelos 
                                   ;  latches.                    
                                                                  
       mov     [byte es:bx],al     ; Escreve qualquer coisa...    
                                   ;  AL aqui tambm no nos      
                                   ;  interessa, j que Set/Reset 
                                   ;   quem manda os dados para  
                                   ;  os planos de bits.          
                                                                  
       sub     ah,ah       ; Espera uma tecla!                    
       int     16h         ; ... seno no tem graa!!! :)        
                                                                  
       mov     ax,3        ; Volta p/ modo texto 80x25            
       int     10h                                                
                                                                  
       int     20h         ; Fim do prog                          
                                                                  
   end start                                                      
 

    Explicando a listagem acima:  Os quatro planos  so  habilitados
em MapMask... depois habilitamos somente o bit 7 em BitMask, seguido
pela   habilitao   dos  quatro  bits  de  "Set/Reset"  em  "Enable
Set/Reset".  Uma vez  que  os  quatro  planos esto habilitados (por
MapMask) e que os quatro  bits  de  "Set/Reset"  tambm  esto  (via
"Enable  Set/Reset"),  colocamos  em  "Set/Reset" os quatro bits que
queremos que  sejam  escritos  nos  planos:   0101b  (ou 05h).  Pois
bem... precisamos apenas carregar os latches e  depois  escrever  na
memria do sistema.

    Tudo bem, vc diz, mas qual  a grande  vantagem?!   Ora,  ora...
temos condies de alterar os quatro planos de bits ao mesmo tempo!!
E,  melhor  ainda,  estamos  em condio de setar at oito pixeis ao
mesmo tempo!!!!  Experimente trocar a linha:

 Ŀ
       mov     ax,8008h    ; BitMask = 10000000b                  
 

    por:

 Ŀ
       mov     ax,0FF08h   ; BitMask = 11111111b                  
 

    Voc ver oito pixeis magenta  com  uma nica escrita na memria
do sistema!!

    Outra  grande  vantagem    o  ganho de velocidade:  Na listagem
acima os dados que  vo  ser  colocados  nos  planos de bits no so
fornecidos diretamente pela CPU, mas sim  por  "Set/Reset"  e  pelos
latches.   Assim,  a  placa VGA no se interessa pelo contedo de AL
que foi escrito na memria do sistema e no adiciona WAIT STATES, j
que esse dado no vai para a memria de vdeo (fica s na memria do
sistema!!).

     um grande avano, n?!   Well... prximos avanos nos prximos
textos.

ͻ
 ASSEMBLY XXIV 
ͼ

    At  agora  vimos  os  registradores  MapMask,  BitMask, "Enable
Set/Reset" e Set/Reset.  Vimos tambm que  MapMask  permite  ou  no
mudanas  nos  quatro  planos  de  bits  idependentemente.   BitMask
mascara os bits no desejveis (e esses so lidos dos latches quando
escrevemos  na  memria).   Ainda  por  cima,  vimos  que  possvel
atualizar  os  quatro  planos  de  bits  ao  mesmo  tempo  com  bits
diferentes usando "Enable Set/Reset"  e Set/Reset.  Isso tudo usando
o modo de escrita 0!


     Modo de escrita 1

    O modo de escrita 1  lida  somente  com os latches da placa VGA.
Com esse modo podemos copiar o contedo dos quatro planos de bits de
uma posio para outra com uma nica instruo em assembly!

    Como  j  vimos,  os  latches  dos  quatro planos so carregados
sempre que fazemos uma leitura  na  memria  do sistema (em todos os
modos de escrita!).  No modo 1 isso tambm vale.  S que nesse  modo
no  possvel  escrever  nada  nos  planos de bits!!  Simplesmente,
quanto mandamos escrever numa  determinada  posio  da  memria  do
sistema,  os  latches   que atualizaro essa posio.  No modo 1 os
registros Set/Reset, "Enable Set/Reset" e BitMask no funcionam para
nada. Assim, depois de setado o modo 1, podemos usar:

 Ŀ
   REP MOVSB                                                      
 

    Para copiarmos bytes dos quatro  planos  de vdeo de uma posio
da tela para outra.  E RAPIDO!  S  que  tem  um  pequeno  problema:
Podemos  copiar  BYTES  e  no pixeis individuais!  Lembre-se que um
byte contm oito pixeis (com  cada  bit  de  um pixel em um plano de
bits!).  Se sua inteno  copiar um bloco inteiro,  porm  alinhado
por  BYTE, ento o modo 1  a escolha mais sensata.  Caso contrrio,
use outro modo de escrita (o modo 0, por exemplo!).

    Ahhh... podemos conseguir o mesmo efeito do modo de escrita 1 no
modo de escrita 0!  Basta zerarmos  todos os bits de BitMask!  Pense
bem:  Se BitMask est completamente zerado,  ento  os  dados  viro
apenas  dos  latches!   O  que  nos  deixa  com  um  modo de escrita
obsoleto, j que podemos fazer o mesmo trabalho no modo 0! :)


     O registrador MODE

    Para  ajustar o modo de escrita precisamos de um registrador.  O
registrador MODE  descrito abaixo:

                            7 6 5 4 3 2 1 0
                           ͻ
                           ?    ?  
                           ͼ
                                     
                              Ĵ      Modo de escrita
                                   Modo de leitura
                                  Odd/Even
                                 Deslocamento

    O  nico  campo  que  nos  interessa  no  momento    o "Modo de
escrita".  Por isso, para  modificar  o  modo,  precisaremos  ler  o
registro  MODE,  setar  o  modo de escrita, e depois reescrev-lo...
para que no faamos mudanas nos  demais bits.  Os modos de escrita
vlidos so os citados anteriormente (repare que esse  campo  tem  2
bits de tamanho!).

    O  registrador  MODE  faz  parte  do  circuito  GC  (o  mesmo de
BitMask, "Enable Set/Reset" e Set/Reset)  da placa VGA, seu ndice 
5.

    Well... j que o  modo  1    obsoleto,  vou colocar aqui alguns
macros para facilitar o entendimento dos prximos cdigos-fonte, ok?

 Ŀ
   ; VGA.INC                                                      
   ; Macros para VGA!                                             
   ; Todos os macros alteram dx e ax                              
                                                                  
   ; Macro: Ajusta o modo de escrita                              
   macro   SetWriteMode    mode                                   
           ifdifi <mode>,<ah>                                     
               mov     ah,mode                                    
           endif                                                  
           mov     dx,3CEh                                        
           mov     al,5                                           
           out     dx,al                                          
           inc     dx                                             
           in      al,dx                                          
           and     ax,1111111100b                                 
           or      al,ah                                          
           out     dx,al                                          
   endm                                                           
                                                                  
   ; Macro: Habilita/Mascara os planos de vdeo                   
   macro   MapMask plane                                          
           ifdifi  <plane>,<ah>                                   
               mov     ah,plane                                   
           endif                                                  
           mov     al,2                                           
           mov     dx,3C4h                                        
           out     dx,ax                                          
   endm                                                           
                                                                  
   ; Macro: Habilita os bits                                      
   macro   BitMask bit                                            
           ifdifi  <bit>,<ah>                                     
               mov     ah,bit                                     
           endif                                                  
           mov     al,8                                           
           mov     dx,3CEh                                        
           out     dx,ax                                          
   endm                                                           
                                                                  
   ; Macro: Altera "Enable Set/Reset"                             
   macro EnableSetReset    bitmsk                                 
           ifdifi  <bitmsk>,<ah>                                  
               mov     ah,bitmsk                                  
           endif                                                  
           mov     al,1                                           
           mov     dx,3CEh                                        
           out     dx,ax                                          
   endm                                                           
                                                                  
   ; Macro: Ajusta Set/Reset                                      
   macro SetReset value                                           
           ifdifi  <value>,<ah>                                   
               mov     ah,value                                   
           endif                                                  
           sub     al,al       ; altera tb os flags..             
           mov     dx,3CEh                                        
           out     dx,ax                                          
   endm                                                           
 

ͻ
 ASSEMBLY XXV 
ͼ

    O modo de  escrita  1  no    to  til,  como  vimos no ltimo
texto...  A plca VGA possui algumas redundancias que  podem  parecer
desnessesrias   primeira vista, como por exemplo o modo de escrita
3.  Nesse modo podemos despresar  o registrador "Enable Set/Reset" e
usar "Set/Reset" para ajustar os bits dos quatro planos de vdeo.


     Modo de escrita 3

    Well...  No modo 0 vimos como  atualizar  os  quatro  planos  de
bits de uma s vez...  Isso  feito setando  o  registrador  "Enable
Set/Reset"  e  "Set/Reset"...  usando  tambm MapMask e BitMask para
habilitarmos  os  planos  e   os  bits  desejados,  respectivamente.
Acontece que no modo 0 podemos ter uma mistura de  dados  vindos  da
CPU,  dos  latches e do registro Set/Reset... a mistura pode ser to
confusa que podemos  ter  a  CPU  atualizando  um  plano e Set/Reset
outro.  , sem sombra de dvida, um recurso interessante e  bastante
til...  mas  se  no  tomarmos  cuidado pode ser uma catastrofe, em
termos visuais!

    O modo de escrita 3 trabalha da  mesma forma que o modo 0 s que
"seta" automaticamente os quatro bits de "Enable  Set/Reset".   Isto
,  a  CPU  no  escreve  nada  nos  planos de bits... isso fica sob
responsabilidade do registrador "Set/Reset".  O que a CPU escreve na
memria so sistema sofre uma operao  lgica  AND  com  o  contedo
atual  de  BitMask...   O resultado  usado como se fosse o BitMask!
(Para facilitar  as  coisas...  se  BitMask  for  11111111b  e a CPU
escrever 01100011b, ento o "novo" BitMask ser 01100011b, sem que o
registrador BitMask seja afetado!!)

    Com  esse  modo  de escrita descartamos a necessidade de ajustar
"Enable Set/Reset", eliminando a  confuso  que  pode ser causada no
modo  0...  descartamos  a  atualizao  de  BitMask,  que  pode  feita
indiretamente pela  CPU...   Mas,  infelizmente  no  descartamos  a
necessidade  de leitura da memria do sistema para carga dos latches
e nem mesmo  a  necessidade  de  habilitarmos  os  planos de bits em
MapMask!  Se  MapMask  estiver  zerado  nenhum  plano  de  bit  ser
atualizado,  lembre-se  sempre disso!!!  Isso  vlido para TODOS os
modos de escrita!

    Eis um exemplo  prtico  do  uso  do  modo  de escrita 3...  Uma
rotina que traa uma linha horizontal:

 Ŀ
   ideal                                                           
   model small,c                                                   
   locals                                                          
   jumps                                                           
   p386                                                            
                                                                   
   ; inclui os macros definidos no ltimo texto!                   
   include "VGA.INC"                                               
                                                                   
   SCREEN_SEGMENT  equ 0A000h                                      
                                                                   
   ; Tamanho de uma linha... (modo 640x480)                        
   LINE_SIZE       equ 80                                          
                                                                   
   ; Coordenadas mximas...                                        
   MAX_X_POS       equ 639                                         
   MAX_Y_POS       equ 479                                         
                                                                   
   global  grHorizLine:proc                                        
   global  grVertLine:proc                                         
   global  setGraphMode:proc                                       
   global  setTextMode:proc                                        
                                                                   
   codeseg                                                         
                                                                   
   ;*** DESENHA LINHA HORIZONTAL ***                               
   proc    grHorizLine                                             
   arg     left:word, right:word, y:word, color:word               
   local   bitmask1:byte, bitmask2:byte                            
   uses    si, di                                                  
                                                                   
           ; Verifica se a coordenada Y  vlida...                
           mov     ax,[y]                                          
           or      ax,ax                                           
           js      @@grHorizLineExit                               
                                                                   
           cmp     ax,MAX_Y_POS                                    
           ja      @@grHorizLineExit                               
                                                                   
           ; Verifica validade das coordenadas "left" e "right"... 
           mov     ax,[left]                                       
           cmp     ax,[right]                                      
           jb      @@noSwap                                        
                                                                   
           ; Troca "left" por "right"                              
           ;  se "right" for menor que "left".                     
           xchg    ax,[left]                                       
           mov     [right],ax                                      
                                                                   
   @@noSwap:                                                       
           ; Verifica a validade das coordenadas "left" e "right"  
           cmp     ax,MAX_X_POS    ; "left"  valido?              
           ja      @@grHorizLineExit                               
                                                                   
           or      [right],0       ; "right"  valido?             
           js      @@grHorizLineExit                               
                                                                   
           WriteMode   3     ; Ajusta no modo de escrita 3.        
           BitMask     0FFh  ; BitMask totalmente setado!          
           MapMask     1111b ; Habilita todos os quatro planos     
                             ;  de bits.                           
           SetReset    <[byte color]> ; Ajusta a cor desejada...   
                                                                   
           mov     ax,SCREEN_SEGMENT                               
           mov     es,ax   ; ES = segmento de vdeo.               
                                                                   
           ; Calcula os offsets das colunas...                     
           mov     si,[left]                                       
           mov     di,[right]                                      
           shr     si,3        ; si = offset da coluna 'left'      
           shr     di,3        ; di = offset da coluna 'right'     
                                                                   
           ; Calcula o offset da linha 'y'                         
           mov     bx,[y]                                          
           mov     ax,LINE_SIZE                                    
           mul     bx                                              
           mov     bx,ax   ; BX contm o offset da linha.          
                                                                   
           ; Pr-calcula a mascara da coluna 'left'                
           mov     cx,[left]                                       
           mov     ch,cl                                           
           and     ch,111b                                         
           mov     cl,8                                            
           sub     cl,ch                                           
           mov     ah,0FFh                                         
           shl     ah,cl                                           
           not     ah                                              
           mov     [bitmask1],ah                                   
                                                                   
           ; pr-calcula a mascara da coluna 'right'               
           mov     cx,[right]                                      
           and     cl,111b                                         
           inc     cl                                              
           mov     ah,0FFh                                         
           shr     ah,cl                                           
           not     ah                                              
           mov     [bitmask2],ah                                   
                                                                   
           ; Verifica se apenas um byte ser atualizado.           
           cmp     si,di                                           
           jz      @@OneByte                                       
                                                                   
           mov     ah,[bitmask1]                                   
           xchg    [es:bx+si],ah  ; Escreve na memria da video... 
                                  ; ... XCHG primeiro l o que     
                                  ;  est no operando destino,     
                                  ;  depois efetua a troca.        
                                  ;  Com isso economizamos um MOV! 
           inc     si                                              
           cmp     si,di                                           
           je      @@doMask2                                       
                                                                   
   @@MiddleDraw:                                                   
           mov     [byte es:bx+si],0ffh    ; Linha cheia...        
                                           ; No precisamos        
                                           ;  carregar os latches  
                                           ;  pq todos os bits     
                                           ;  sero atualizados!   
           inc     si                                              
           cmp     si,di                                           
           jne     @@MiddleDraw                                    
                                                                   
   @@doMask2:                                                      
           mov     ah,[bitmask2]                                   
           xchg    [es:bx+si],ah   ; Escreve na memria de vdeo   
           jmp     @@HorizLineEnd                                  
                                                                   
   @@OneByte:                                                      
           and     ah,[bitmask1]                                   
           xchg    [es:bx+si],ah                                   
                                                                   
   @@HorizLineEnd:                                                 
           WriteMode 0         ; Poe no modo 0 de novo...          
                               ;  Necessrio somente se essa       
                               ;  rotina for usada em conjunto     
                               ;  com as rotinas da BIOS ou de     
                               ;  seu compilados (p.ex: BGIs!).    
   @@grHorizLineExit:                                              
           ret                                                     
   endp                                                            
                                                                   
   ;;*** DESENHA LINHA VERTICAL ***                                
   proc    grVertLine                                              
   arg     x:word, top:word, bottom:word, color:byte               
   uses    si, di                                                  
                                                                   
           ; Verifica se X est na faixa                           
           mov     ax,[x]                                          
           or      ax,ax               ; x < 0?                    
           js      @@grVertLineExit                                
                                                                   
           cmp     ax,MAX_X_POS        ; x > 639?                  
           ja      @@grVertLineExit                                
                                                                   
           ; Verifica se precisa fazer swap                        
           mov     ax,[top]                                        
           cmp     ax,[bottom]                                     
           jb      @@noSwap                                        
                                                                   
           xchg    ax,[bottom]                                     
           mov     [top],ax                                        
                                                                   
   @@noSwap:                                                       
           ; Verifica se as coordenadas "Y" esto dentro da faixa. 
           cmp     ax,MAX_Y_POS                                    
           ja      @@grVertLineExit                                
                                                                   
           cmp     [bottom],0                                      
           js      @@grVertLineExit                                
                                                                   
           mov     ax,SCREEN_SEGMENT                               
           mov     es,ax                                           
                                                                   
           WriteMode 3                                             
           BitMask 0FFh                                            
           MapMask 0Fh                                             
           SetReset <[byte color]>                                 
                                                                   
           mov     si,[top]                                        
                                                                   
           mov     ax,LINE_SIZE                                    
           mul     si                                              
           mov     bx,ax       ; BX contm o offset da linha       
                                                                   
           mov     di,[x]                                          
           mov     cx,di                                           
           shr     di,3        ; DI contm o offset da coluna      
                                                                   
           and     cl,111b                                         
           mov     ah,10000000b                                    
           shr     ah,cl                                           
                                                                   
   @@SetPixelLoop:                                                 
           mov     cl,ah                                           
           xchg    [es:bx+di],cl                                   
           add     bx,LINE_SIZE                                    
           inc     si                                              
           cmp     si,[bottom]                                     
           jbe     @@SetPixelLoop                                  
                                                                   
           WriteMode 0                                             
                                                                   
   @@grVertLineExit:                                               
           ret                                                     
   endp                                                            
                                                                   
   proc    setGraphMode                                            
       mov     ax,12h                                              
       int     10h                                                 
       ret                                                         
   endp                                                            
                                                                   
   proc    setTextMode                                             
       mov     ax,3                                                
       int     10h                                                 
       ret                                                         
   endp                                                            
                                                                   
   end                                                             
 

    No sei se percebeu a engenhosidade dessa pequena rotina...  Ela
pr-calcula  os  bitmasks do inicio e do fim da linha...  Se a linha
est contida somente em um  byte  ento  fazemos  um AND com os dois
bitmasks  pr-calculados  pra  obter  o  bitmask   necessrio   para
atualizar  um  nico byte...  Suponha que queiramos traar uma linha
de (2,0) at (6,0). Eis os bitmasks:

 Ŀ
   BitMask1    =   00111111b   ; BitMask do inicio da linha       
   BitMask2    =   11111110b   ; BitMask do fim da linha          
                                      
   BitMask3    =   00111110b   ; BitMask1 AND BitMask2            
 

    Ok...  E se a linha ocupar  2 bytes?!  Por exemplo, de (2,0) at
(11,0)...  O ponto (2,0) est, com certeza, no primeiro byte...  mas
o ponto (11,0) no (j que um byte suporta apenas 8 pixeis!).  Ento
calculados os dois bitmasks:

 Ŀ
   BitMask1    =   00111111b   ; BitMask do inicio da linha       
   BitMask2    =   11110000b   ; BitMask do fim da linha          
 

    Dai  escrevemos o primeiro byte com o bitmask1 e o segundo com o
bitmask2.  Se a linha ocupar mais  de  2 bytes o processo  o mesmo,
s que os bytes intermedirios  tero  bitmasks  totalmente  setados
(no necessitando, neste caso, carregar os latches!).

    Na   mesma  listagem  temos  a  rotina  de  traagem  de  linhas
verticais... d uma olhada nela.  bem mais simples que grHorizLine!

    No prximo texto:  O modo de  escrita  2!  E depois, os modos de
256 cores! (finalmente, n?!)
ͻ
 ASSEMBLY XXVI 
ͼ

    Vistos os trs  primeiros  modos  de  escrita  da placa VGA, nos
resta apenas o modo 2.  Esse modo    muito  til  para  escrita  de
bitmaps  nos modos de vdeo de 16 cores...  Ele trabalha basicamente
como o  registro  Set/Reset,  sem  que  tenhamos  que  manusear esse
registro explicitamente.


     O modo de escrita 2

    Uma vez setado, o modo de escrita 2  habilita  todos  os  quatro
bits  de "Enable Set/Reset", da mesma forma que o modo de escrita 3.
No entanto, diferente do modo de escrita 3, o registro Set/Reset no
precisa ser ajustado com  a  "cor"  desejada.  Neste modo o registro
Set/Reset  setado com os quatro bits menos significativos  enviados
pela  CPU    memria do sistema.  Precisaremos mascarar os bits no
desejados em BitMask, bem como  ajustar  os planos de bits desejados
em MapMask.

    Repare na fora  deste  modo  de  vdeo...  poderemos  atualizar
pixels  com a "cor" que quisermos sem usarmos Set/Reset diretamente,
e sem termos que setar os  bits de "Enable Set/Reset".  Mas, teremos
que ajustar BitMask para no setarmos todos os oito pixels  no  byte
que estamos escrevendo dos planos de bits...  Eis um exemplo do modo
de escrita 2:

 Ŀ
   ideal                                                          
   model tiny                                                     
   locals                                                         
   jumps                                                          
                                                                  
   include "vga.inc"                                              
                                                                  
   LINE_LENGTH     equ     80                                     
                                                                  
   codeseg                                                        
   org     100h                                                   
   start:                                                         
       mov     ax,12h  ; Ajusta modo de vdeo 640x480x16          
       int     10h                                                
                                                                  
       WriteMode   2      ; modo de escrita 2                     
       MapMask     1111b  ; todos os planos de bits               
                                                                  
       mov     ax,0A000h                                          
       mov     es,ax      ; ES = segmento de vdeo                
                                                                  
       sub     di,di      ; DI = offset                           
       sub     bl,bl      ; usaremos BL p/ contar as linhas.      
                                                                  
       mov     ah,10000000b ; ah = bitmask inicial                
       mov     cl,1000b     ; CL = cor inicial                    
                                                                  
   @@1:                                                           
       BitMask ah                                                 
       mov     al,[es:di]  ; carrega latches                      
       mov     [es:di],cl  ; escreve nos planos                   
       ror     ah,1        ; rotaciona bitmask                    
       inc     cl          ; prxima cor                          
       cmp     cl,10000b   ; ops... ultrapassou?!                 
       jb      @@1         ; no... ento permanece no loop.      
       mov     cl,1000b    ; ajusta p/ cor inicial.               
       add     di,LINE_LENGTH ; prxima linha                     
       inc     bl          ; incrementa contador de linhas        
       cmp     bl,8        ; chegou na linha 8?                   
       jb      @@1         ; no... continua no loop.             
                                                                  
       sub     ah,ah       ; espera tecla, seno no tem graa!   
       int     16h                                                
                                                                  
       mov     ax,3        ; volta ao modo texto...               
       int     10h                                                
                                                                  
       int     20h         ; fim do programa.                     
   end start                                                      
 

    Esse modo parece mais fcil  que os demais, no?!  Aparentemente
... mas tenha em mente que os outros modos de  escrita  tambm  tm
suas vantagens.


     E os modos de leitura?!

    Na  grande maioria das vezes no  vantajoso lermos os dados que
esto nos planos de bits...  Isso  porque  a memria de vdeo  mais
lenta que a memria do sistema (mesmo a memria do sistema associada
 placa VGA  mais lenta que o resto da memria  do  seu  PC...  por
causa  dos WAIT STATES que a placa VGA adiciona para no se perder -
a velocidade da CPU  maior  que  a  do circuito de vdeo!).

    Para  encerrarmos  os  modos  de  16 cores  interessante vermos
alguma coisa sobre o modo  de  leitura  0,  que   o modo default da
placa VGA.

    No modo de leitura 0 devemos ler um plano de bits por vez... no
 possvel ler mais que um plano ao mesmo tempo... e ainda,  MapMask
no  responsvel pela habilitao dos planos de bits.  Nesse caso a
leitura  feita atravs de uma ramificao do circuito de vdeo... a
escrita  feita por  outra.   O  registrador  BitMask tambm no tem
nenhum efeito na leitura.  Por isso a seleo dos bits fica por  sua
conta (atravs de instrues AND).

    A  seleo  do  plano  de  bits  que  ser  lido    feito  pelo
registrador ReadMap que  descrito abaixo:

         Registrador READMAP

                7 6 5 4 3 2 1 0
               ͻ
               ??????  
               ͼ
                             
                             Seleo do plano de bits

    ReadMap  tambm  faz  parte do circuito GC...  Ento  acessvel
via endereos de I/O 3CEh  e  3CFh,  da  mesma forma que BitMask e o
registro de MODE, s que seu ndice  4.

    Uma nota importante  a de que, embora a leitura seja feita  por
uma ramificao diferente (por isso a existncia de ReadMap), quando
fazemos   uma   leitura   dos   planos   de  bits,  os  latches  so
automaticamente carregados... e  os  latches pertencem  ramificao
do  circuito  de escrita (somente os latches dos planos selecionados
por MapMask so carregados, lembra?!).

    E z fini... pelo menos at o prximo texto! :)
