
                               ASSEMBLY
                              Funes e Procedures
                                Aula nr. 12 de 26

    A partir de agora veremos 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.  O registrador DS  usado por todo o cdigo PASCAL
a  fim  de  localizar  o  segmento  de dados correntes (os quais no
sabemos onde se encontram...  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:

 Ŀ
   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 = FAFBFDFEh                                             
   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;
                                                                                                   
