Escolar Documentos
Profissional Documentos
Cultura Documentos
-------------------Assembler uma linguagem de baixo nvel, que voc pode usar em seus programas
para acelerar tarefas lentas. Basicamente ela consite de sentenas que
representam instrues em linguagem de mquina, e, como ela est prxima
ao cdigo de mquina, ela rpida.
H muito tempo atrs, quando o 8086 apareceu, programar no era uma tarefa fcil.
Quando os primeiros computadores foram desenvolvidos, a programao tinha que ser feita
em cdigo de mquina, que no era uma tarefa fcil, e assim o Assembler nasceu.
Por que us-lo?
----------------Assembler veloz. Ele tambm permite a voc falar com
a mquina a nvel de hardware, e lhe d muito maior controle e flexibilidade
sobre o PC.
LIO 1 - Registradores
-------------------------Quando voc est trabalhando com Assembler, voc tem que usar registradores.
Voc pode imagin-los como sendo variveis j definidas para voc. Os mais
comuns esto listados abaixo:
AX - o acumulador. Compreende AH e AL, os bytes alto e baixo
de AX. Comumente usado em operaes matemticas e de E/S.
BX - a base. Compreende BH e BL. Comumente usado como uma base ou
registrador apontador.
CX - o contador. Compreende CH e CL. Usado frequentemente em loops.
DX - o deslocamento, similar ao registrador de base. Compreende DH
e DL. Acho que voc est pegando o esprito da coisa agora.
Estes registradores so definidos como registradores de uso geral pois podemos
realmente armazenar qualquer coisa que quisermos neles. So tambm
registradores de 16 bits, o que significa que podemos armazenar um inteiro
positivo de 0 a 65535, ou um inteiro com sinal de -32768 to 32768.
- Carrega um byte
- Carrega uma word
STOSB
STOSW
- Armazena um byte
- Armazena uma word
50 32 38 03 23 01 12
50 32 38 03 23 50 12
Posio de Memria 06 07 08 09 10 11 12
Valor
50 32 38 03 23 50 12
Em ES:DI:
Posio de Memria 06 07 08 09 10 11 12
Valor
10 11 20 02 67 00 12
10 11 20 02 67 32 12
0000 = 0
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = 8
1001 = 9
1010 = 10
1011 = 11
1100 = 12 10000 = 16
1101 = 13 ...
1110 = 14
1111 = 15
O NIBBLE, ou quatro bits. Um nibble pode ter um valor mximo de 1111 que
15 em decimal. aqui que o hexadecimal entra. Hex baseado naqueles
16 nmeros, (0-15), e quando escrevemos em hex, usamos os 'dgitos'
abaixo:
0123456789ABCDEF
Hexadecimal na verdade muito fcil de se usar, e, apenas como
curiosidade, eu acho que os Babilnios - uma civilizao antiga qualquer
- usava um sistema de numerao em BASE 16. Tem algum historiador a
fora que queira confirmar isso?
IMPORTANTE >>> Um nibble pode aguentar um valor at Fh <<< IMPORTANTE
O BYTE - o que mais usaremos. O byte tem 8 bits de tamanho - isso 2
nibbles, e o nico valor que voc vai conseguir colocar num registrador
de 8 bits. EX.: AH, AL, BH, BL, ...
Um byte tem um valor mximo de 255 em decimal, 11111111 em binrio,
ou FFh em hexadecimal.
A WORD - outra unidade comumente usada. Uma word um nmero de 16 bits,
e capaz de armazenar um nmero at 65535. Isso 1111111111111111 em
binrio, e FFFFh em hex.
Obs.: Por causa de uma word ser quatro nibbles, tambm representada
por quatro dgitos hexadecimais.
Obs.: Isto um nmero de 16 bits, e corresponde aos registradores de
16 bits. Ou seja, AX, BX, CX, DX, DI, SI, BP, SP, DS, ES, SS
e IP.
A DWORD, ou double word consiste de 2 words ou 4 bytes ou 8 nibbles ou
32 bits. Voc no vai usar muito as double words nestes tutoriais, mas
vamos mencion-las mais tarde quando falarmos de PROGRAMAO EM 32 BITS.
Uma DWORD pode armazenar de 0 a 4,294,967,295, que FFFFFFFFh, ou
11111111111111111111111111111111. Espero que haja 32 um's l atrs.
A DWORD tambm o tamanho dos registradores extendiddos de 32 BITS,
ou seja, EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP e EIP.
O KILOBYTE, 1024 bytes, NO 1000 bytes. O kilobyte igual a
256 double-words, 512 words, 1024 bytes, 2048 nibbles ou 8192 BITS.
Eu no vou escrever todos os um's.
O MEGABYTE, ou 1024 kilobytes. Isso 1,048,576 bytes ou 8,388,608
bits.
Agora que j cobrimos a terminologia, vamos dar uma olhada mais de perto como
aqueles registradores so estruturados. Ns dissemos que AL e AH eram
registradores de 8 bits, logo, eles no deveriam se parecer com algo assim?
AH
00000000
AL
00000000
AH
AL
00000000
00000000
EAX
AX
00000000
EG: 3CE5:502A
^^^^ ^^^^
SEG OFS
Okay, aqui est a especificao:
Um OFFSET = SEGMENT X 16
Um SEGMENT = OFFSET / 16
Algums registradores de segmento so:
CS, DS, ES, SS e FS, GF - Obs.: Os ltimos 2 so registradores que s existem
em 386 ou superiores.
Alguns registradores de offset so:
BX, DI, SI, BP, SP, IP
MOV AX, 0
MOV AL, 0
MOV AH, 0
; AX = 0
; AL = 0
; AH = 0
; AX = AX + 1
; AX = 0100h
; AH = 01h
; AL = 00h
COISAS A FAZER:
1) Aprender aquele negcio de BIT/NIBBLE/BYTE... de cor.
2) Voltar nos exemplos de segmento e offset.
3) Tenha certeza que voc entendeu a relao entre AX, AH e AL.
4) Que tal um problemas de adio hexadecimal?
A Pilha
----------A pilha uma caracterstica muito til de que podemos tirar vantagem. Pense
nela como uma pilha de papis numa bandeja de ENTRADA. Se voc pe algo no
topo, ela ser a primeira a ser tirada.
medida que voc adiciona algo pilha, o apontador de pilha DECREMENTADO,
e quando tira, INCREMENTADO. Para explicar isso melhor, veja o diagrama
abaixo:
A PILHA
SP
E na prtica:
MOV AX, 03h ; AX = 03h
PUSH AX
; PUSH AX na pilha (coloca no topo)
MOV AX, 04Eh ; AX = 04Eh
; Faa alguma coisa... uma soma?
POP AX
; AX = 03h
Ou:
MOV AX, 03h ; AX = 03h
PUSH AX
; Adiciona AX pilha
MOV AX, 04Eh ; AX = 04Eh
; Faa alguma coisa... uma soma?
POP BX
; BX = 03h
Por ltimo, algumas procedures que demonstram algo disso tudo. Note que
os comentrios foram DELIBERADAMENTE REMOVIDOS. seu dever tentar
coment-los. Note tambm, que algumas novas instrues so introduzidas.
MODEL
STACK
DATA
CODE
START
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
.CODE
START:
MOV AX, 4C00h ; AH = 4Ch, AL = 00h
INT 21h
END START
Vamos ver em detalhes. Abaixo, cada uma das frases acima est explicada.
DOSSEG
Segmentos de Cdigo;
Segmentos de Dados;
Segmentos de Pilha.
No se preocupe muito com isso por enquanto, apenas inclua
at que voc saiba o que est fazendo.
MODEL
- isso permite CPU determinar como seu programa est
estruturado. Voc pode ter os seguintes MODELos:
1) TINY - tanto cdigo quanto dados se encaixam
no mesmo segmento de 64K.
2) SMALL - cdigo e dados esto em segmentos diferentes,
embora cada um tenha menos de.
3) MEDIUM - cdigo pode ser maior que 64K, mas os dados
tm que ter menos que 64K.
4) COMPACT - cdido menos de 64K, mas dados podem ter
mais que 64K.
5) LARGE - cdigo e dados podem ter mais que 64K, embora
arrays no possam ser maiores que 64K.
6) HUGE
STACK
- isso instrui ao PC para arrumar uma pilha to grande quanto
for especificado.
DATA
CODE
START
- Apenas um label para dizer ao compilador onde a parte
principal do seu programa comea.
MOV AX, 4C00h ; AH = 4Ch, AL = 00h
Isso move 4Ch para ah, que coincidentemente nos traz de volta ao DOS.
Quando a interrupo 21h chamada e AH = 4Ch, de volta ao DOS l vamos
ns.
INT 21h
END START - Voc no tem imaginao?
Okay, Espeo que voc tenha entendido tudo isso.
Neste exemplo ns vamos usar a interrupo 21h, (a interrupo do DOS), para
imprimir uma string. Para ser preciso, vamos usar a subfuno 9h, e ela se
parece com isso:
INTERRUPO 21h
SUBFUNO 9h
Requer:
AH = 9h
DS:DX = ponteiro FAR para a string a ser impressa. A string deve ser
terminada com um sinal $.
Assim, aqui est o exemplo:
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
OurString DB "Isto uma string de caracteres"
.CODE
START:
MOV AX, SEG OurString ; Move o segmento onde OurString est
MOV DS, AX
; para AX, e agora para DS
MOV DX, OFFSET OurString ; Offset de OurString -> DX
MOV AH, 9h
; Subfuno de imprimir strings
INT 21h
; Gera a interrupo 21h
MOV AX, 4C00h
; Subfuno de sada para o DOS
INT 21h
; Gera a interrupo 21h
END START
Se voc assemblar isso com TASM - TASM SEJALADOQUEVOCECHAMOUELE.ASM
ento linkar com TLINK - TLINK SEJALADOQUEVOCECHAMOUELE.OBJ voc vai
conseguir um arquivo EXE de cerca de 652 bytes. Voc pode usar estes programas no
DEBUG com algumas modificaes, mas eu vou deixar isso contigo. Para trabalhar com
Assembler puro voc _precisa_ de TASM e TLINK, embora eu ache que MASM
faria o mesmo trabalho muito bem.
Agora vamos ao cdigo com um pouco mais detalhado:
MOV AX, SEG OurString ; Move o segment onde OurString est
MOV DS, AX
; para AX, e agora para DS
MOV DX, OFFSET OurString ; Move o offset onde OurString est localizado
MOV AH, 9h
; Subfuno de escrita de strings
INT 21h
; Gera a interrupo 21h
Voc vai notar que tivemos que usar AX para pr o endereo do segmento de
OurString em DS. Voc vai descobrir que no d pra referenciar um registrador
de segmento diretamente em Assembler. Na procedure PutPixel do ltimo
tutorial, eu movi o endereo da VGA para AX, e ento para ES.
A instruo SEG tambm introduzida. SEG retorna o segmento onde a string
OurString est localizada, e OFFSET retorna, adivinha o qu?, o offset do
incio do segmento para onde a string termina.
Note tambm que ns usamos DB. DB no nada de especial, e significa Declare
Byte, que o que tudo o que ela faz. DW, Declare Word e DD, Declare Double
Word tambm existem.
Voc poderia ter tambm colocado OurString segmento de cdigo, a vantagem
que estaria CS apontando para o mesmo segmento que OurSting, de modo que
voc no tem que se preocupar em procurar o segmento em que OurString est.
O programa acima no segmento de cdigo seria mais ou menos assim:
DOSSEG
.MODEL SMALL
.STACK 200h
.CODE
OurString
START:
MOV AX, CS
MOV DS, AX
MOV DX, OFFSET OurString
MOV AH, 9
INT 21h
MOV AX, 4C00h
INT 21h
END START
Simples, no?
Ento, o que so flags?
--------------------------
Esta parte para meu companheiro Clive que tem me perguntado sobre flags,
ento l vamos ns Clive, com FLAGS.
Eu no me lembro se j introduzimos a instruo CMP ou no, CMP - (COMPARE),
mas CMP compara dois nmeros e reflete a comparao nos FLAGS. Para
us-la voc faria algo desse tipo:
CMP AX, BX
ento seguir com uma instruo como essas abaixo:
COMPARAES SEM SINAL:
----------------------- JA
JAE
JB
JBE
JNA
JNAE
JNB
JNBE
JZ
JE
JNZ
JNE
NO TO COMUNS:
---------------- JC
JNC
JO
JNO
JP
JNP
JPE
JPO
JS
JNS
PS:
- Procedure: apenas um sumrio do que cada comando faz.
Ex.:
MOV AX, 0003h ; AX agora igual a 03h;
ADD AX, 0004h ; AX agora igual a 07h;
Ento, aqui vai o conjunto completo das procedures com comentrios:
{ Esta procedure limpa a tela em modo texto }
Procedure ClearScreen(A : Byte; Ch : Char); Assembler;
Asm { ClearScreen }
mov ax, 0B800h { Move o endereo de vdeo para AX }
mov es, ax
{ Aponta ES para o segmento de vdeo }
xor di, di
{ Zera DI
}
mov cx, 2000
{ Move 2000 (80x25) para CX
}
mov ah, A
{ Move o atributo para AH
}
mov al, &Ch
{ Move o caracter a usar para AL
}
rep stosw
{ Faz isso
}
End; { ClearScreen }
Explicao:
Ns zeramos DI, logo igual a 0 - o canto esquerdo da tela. Isto de onde
vamos comear a encher a tela.
Movemos 2000 para CX porque vamos colocar 2000 caracteres na tela.
{ Esta procedure move o cursor para a posio X, Y }
Procedure CursorXY(X, Y : Word); Assembler;
Asm
mov
mov
dec
mov
mov
dec
{ CursorXY }
ax, Y
{ Move o valor Y para AX
}
dh, al
{ Y vai para DH
}
dh
{ rotina baseada em ajustar para zero }
ax, X
{ Move o valor de X para AX
}
dl, al
{ X vai para DL
}
dl
{ rotina baseada em ajustar para zero }
mov ah, 2
{ Chama a funo correspondente
}
xor bh, bh
{ Zera BH
}
int 10h
{ faz isso (pe o cursor na posio) }
End; { CursorXY }
Explicao:
A ' rotina baseada em ajustar para zero' realizada porque a BIOS refere-se
posio (1, 1) como (0, 0), e igualmente (80, 25) como (79, 24).
End;
{ Delay }
Um Exemplo de Comparao
-------------------------Eu no vou perder tempo explicando minuciosamente o seguinte exemplo - ele
muito fcil de entender e voc deve pegar a idia basica seja l como for.
DOSSEG
.MODEL SMALL
.STACK 200h
.DATA
FirstString DB 13, 10, "Este um grande tutorial ou o qu? :) - $"
SecondString DB 13, 10, "NO? NO? O que voc quer dizer, NO?$"
ThirdString DB 13, 10, "Excelente, vamos ouvir voc dizer isso de novo.$"
FourthString DB 13, 10, "Apenas um Y ou N j basta.$"
ExitString DB 13, 10, "Bem, deixa pra l!$"
.CODE
START:
MOV AX, @DATA
MOV DS, AX
KeepOnGoing:
MOV AH, 9
MOV DX, OFFSET FirstString ; DX -> OFFSET FirstString
INT 21h
; Escreve a primeira mensagem
MOV AH, 0
INT 16h
PUSH AX
MOV DL, AL
MOV AH, 2
INT 21h
POP AX
CMP AL, "Y"
JNE HatesTute
MOV AH, 9
; Mostra a mensagem "Excelente..."
MOV DX, OFFSET ThirdString
INT 21h
JMP KeepOnGoing
; Volta ao incio e comea de novo
HatesTute:
CMP AL, "N"
JE DontLikeYou
Voc deveria entender este exemplo, brincar um pouco com ele e escrever
algo melhor. Aqueles com um livro do Peter Norton ou algo semelhante,
experimentem as subfunes do teclado, e veja quais outras combinaes de
GetKey existem, ou melhor ainda, brinque com a interrupo 10h e entre em
algum modo de vdeo sobrenatural - um que seu PC suporte! - e use algumas
cores.
Shifts
-------Um simples conceito, e um que eu j devia ter discutido antes, mas como eu
disse - eu tenho minha prpria maneira desconjuntada de fazer as coisas.
Primeiro voc vai precisar de entender um pouco de aritmtica hexadecimal e
binria. Eu geralmente uso uma calculadora cientfica, mas bom ser capaz de saber como
multiplicar, somar e converter entre as vrias bases.
CONVERTENDO DE BINRIO PARA DECIMAL:
De Volta ao Tutorial Um, ns vimos como nmeros binrios se parecem, ento
imagine que eu tenha um nmero binrio de oito dgitos, como:
11001101
O que isso em decimal??? H vrias formas de converter tal nmero, e eu
uso a seguinte, que acredito se provavelmente a mais fcil:
Nmero Binrio
1 1 0 0 1 1 0 1
7 6 5 4 3 2 1 0
Equivalente Decimal 2 2 2 2 2 2 2 2
Equivalente Decimal 128 64 32 16 8 4 2 1
Valor Decimal
128 + 64 + 0 + 0 + 8 + 4 + 0 + 1 = 205
Pegou a idia? Note que para a ltima linha, seria mais preciso escrever:
1 x 128 + 1 x 64 + 0 x 32 + 0 x 16 + 1 x 8 + 1 x 4 + 0 x 2 + 1 x 1
= 28 + 64 + 0 + 0
+ 8 + 4 + 0 + 1
= 205
Desculpe se isto um pouco confuso, mas difcil explicar sem demonstrar.
Aqui vai outro exemplo:
Nmero Binrio
0 1 1 1 1 1 0 0
7 6 5 4 3 2 1 0
Equivalente Decimal 2 2 2 2 2 2 2 2
Equivalente Decimal 128 64 32 16 8 4 2 1
Valor Decimal
0 + 64 + 32 + 16 + 8 + 4 + 0 + 0 = 124
Obs.:
Voc pode usar esta tcnica com palavras de 16 ou 32 bits tambm, apenas
faa do jeito certo. Ex: Depois de 128, voc escreveria 256, depois 512,
1024 e assim por diante.
Voc pode dizer se o equivalente decimal serpar ou mpar pelo primeiro
bit. Ex.: No exemplo acima, o primeiro bit = 0, ento o nmero PAR.
No primeiro exemplo, o primeiro bit 1, ento o nmero MPAR.
6 - 4= 2
2 - 2= 0
=
=
2 X1
1 X0
E isto nos d - 11000110. Note como voc pode checar o primeiro dgito para
ver se voc conseguiu sua converso certa. Quando eu escrevi o primeiro
exemplo, eu notei que eu fiz um erro quando eu chequei o primeiro bit. No
primeiro exemplo, eu consegui 0 - no muito bom para um nmero mpar.
Eu entendi o erro e corrigi o exemplo.
CONVERTENDO DE HEXADECIMAL PARA DECIMAL:
Antes de comear, voc deveria saber que o sistema numrico hexadecimal usa os
'dgitos':
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
= 0 (decimal) = 0 (binrio)
= 1 (decimal) = 1 (binrio)
= 2 (decimal) = 10 (binrio)
= 3 (decimal) = 11 (binrio)
= 4 (decimal) = 100 (binrio)
= 5 (decimal) = 101 (binrio)
= 6 (decimal) = 110 (binrio)
= 7 (decimal) = 111 (binrio)
= 8 (decimal) = 1000 (binrio)
= 9 (decimal) = 1001 (binrio)
= 10 (decimal) = 1010 (binrio)
= 11 (decimal) = 1011 (binrio)
= 12 (decimal) = 1100 (binrio)
= 13 (decimal) = 1101 (binrio)
= 14 (decimal) = 1110 (binrio)
= 15 (decimal) = 1111 (binrio)
Voc vai comumente ouvir hexadecimal referenciado como hex, ou base-16 e ela
comumente denotada por um 'h' - ex.: 4C00h, ou um '$', ex.: - $B800.
Trabalhar com hexadecimal no to difcil como pode parecer, e converter
pra l ou pra c bem fcil. Como exemplo, vamos converter B800h para
decimal:
FATO ENGRAADO: B800h o endereo inicial do vdeo em modo texto para CGA e
placas superiores. :)
B = 4096 x B = 4096 x 11 = 45056
8 = 256 x 8 = 256 x 8 = 2048
0 = 16 x 0 = 16 x 0 = 0
1x0=
1x 0=
= 7 (decimal) = 7h
= 15 (decimal) = Fh
= 15 (decimal) = Fh
=1
= 1 (decimal) = 1h
= 72
Agora SHL 2:
36 + 36 + 36 + 36
= 144
E SHL 3:
36 + 36 + 36 + 36 + 36 + 36 + 36 + 36 = 288
Notou os neros que se formaram? Havia 2 36's com SHL 1, 4 36's com SHL 2
e 8 36's com SHL 3. Seguindo este padro, seria justo assumir que 36 SHL 4
equivaler a 36 x 16.
Note porm, o que est realmente acontecendo. Se voc fosse trabalhar com
o valor binrio de 36, que mais ou menos isso: 100100, e ento shiftasse 36
esquerda de 2, voc teria 144, ou 10010000. Tudo o que a CPU faz na verdade
colocar alguns 1's e 0's extras na posio de memria.
Como outro exemplo, pegue o nmero binrio 1000101. Se fizermos um shift
esquerda de 3, terminaramos com:
1000101
<---------- SHL 3
1000101000
Brinque com as rotinas de PutPixel e veja o que voc pode fazer com elas. Para aqueles
com um livro do Peter Norton, veja que outros procedimentos voc pode fazer usando
interrupes.
COISAS PARA FAZER:
1) Cobrimos muita coisa nesse tutorial, e alguns conceitos importantes
esto nele. Certifique-se de estar comfortvel com comparaes, porque
vamos comear a testar bits em breve.
a <DEST> tambm.
Basicamente, DEST = DEST + FONTE + CF
EX.: ADD AX, BX
ADD <DEST>, <FONTE>
- Nome: Add
Tipo: 8086+
Descrio: Esta instruo soma <FONTE>
e <DEST>, armazenando o resultado em
<DEST>.
EX.: ADD AX, BX
AND <DEST>, <FONTE>
- Nome: E Lgico
Tipo: 8086+
Descrio: Esta instruo realiza uma
comparao bit a bit de <DEST> e
<FONTE>, armazenando o resultado em
<DEST>.
EX.: AND 0, 0 = 0
AND 0, 1 = 0
AND 1, 0 = 0
AND 1, 1 = 1
BT <DEST>, <BIT NUMBER>
Tipo: 80386+
EX.: BT AX, 3
JC EraIgualAUm
CALL <DEST>
CBW
CLC
CLD
EX.: CLD
CLI
CMC
; Testa o bit 1 de AX
; Retorna CF para 0
Fim:
CMP <VALOR1>, <VALOR2>
Tipo: 8086+
- Nome: Decrementa
Tipo: 8086+
Descrio: Esta instruo subtrai
um do valor em <VALOR> e armazena
o resultado em <VALOR>.
EX.: DEC AX
DIV <VALOR>
IN <ACUMULADOR>, <PORTA>
Tipo: 8086+
- Nome: Incrementa
Tipo: 8086+
Descrio: Esta instruo soma um ao
nmero em <VALOR>, e armazena
o resultado em <VALOR>.
EX.: MOV AX, 13h ; AX = 13h
INC AX
; AX = 14h
INT <INTERRUPO>
- Nome: Gera uma Interrupo
Tipo: 8086+
Descrio: Esta instruo salva os valores
correntes dos flags e IP na pilha, e ento
chama a <INTERRUPO> baseada no valor de
AH.
EX.: MOV AH, 00h ; Seta o modo de
; vdeo
MOV AL, 13h ; Modo 13h
INT 10h
; Gera interrupo
Jcc
- Nome: Jump
Tipo: 8086+
Descrio: Esta instruo simplesmente
carrega um novo valor, <DEST>, em IP,
assim transferindo o controle para outra
parte do cdigo.
EX.: JMP MyLabel
LAHF
Flag SF ZF
AF
PF
CF
Bit 07 06 05
04 03 02 01 00
Tipo: 8086+
Descrio: Esta instruo uma forma
do loop For...Do que existe na maioria
das linguagens de alto nvel. Basicamente
ele volta ao label, ou segmento de memria
at que CX = 0.
EX.: MOV CX, 12
FazAlgumaCoisa:
;...
;...
;... Isto ser repetido 12 vezes
LOOP FazAlgumaCoisa
Lseg <DEST>, <FONTE>
- Nome: Carega Registrador de Segmento
Tipo: 8086+
Descrio: Esta instruo existe de
vrias formas. Todas aceitam mesma
sintaxe, em que <FONTE> especifica um
ponteiro de 48 bits, consistindo de um
offset de 32 bits e um seletor de 16 bit.
O offset de 32 bis caregado em
<DEST>, e o seletor carregado no
registrador de segmento especificado por
seg.
As seguintes formas existem:
LDS
LES
LFS
LGS
LSS
* 32 bits
* 32 bits
<FONTE> em <DEST>.
EX.: MOV AX, 3Eh
MOV SI, 12h
MUL <FONTE>
NEG <VALOR>
- Nome: Nega
Tipo: 8086+
Descrio: Esta instruo subtrai
<VALOR> de 0, resultando na negao
do complemento a dois de <VALOR>.
EX.: MOV AX, 03h
NEG AX
NOT <VALOR>
; AX = -3
OR <DEST>, <FONTE>
- Nome: OU Lgico
Tipo: 8086+
Descrio: Esta instruo realiza uma
operao de OU booleano entre cada bit de
<DEST> e <FONTE>, guardando o resultado
em <DEST>.
EX.: OR 0, 0 = 0
OR 0, 1 = 1
OR 1, 0 = 1
OR 1, 1 = 1
OUT <PORTA>, <ACUMULADOR>
Tipo: 8086+
POPA
POPF
PUSH <REGISTRADOR>
Tipo: 8086+
PUSH AX
PUSH BX
PUSH CX
...
EX.: PUSHA
PUSHF
REP
RET
STD
STI
- Armazena um byte
- AL
- Armazena uma word
- AX
- Armazena uma doubleword - EAX
Tipo: 8086+
Descrio: Esta instruo realiza uma
operao AND bit-a-bit em <FONTE> e
<DEST>. O resultado refflete nos flags,
e eles so so setados como se fizssemos
um AND.
EX.: TEST AL, 0Fh ; Checa se algum
; bits est setado
; no mais baixo
; nibble de AL
XCHG <VALOR1>, <VALOR2>
Tipo: 8086+
- Nome: Troca
0, 0
0, 1
1, 0
1, 1
=0
=1
=1
=0
Instruo
Clocks no 386
ADC
ADD
AND
BT
CALL
CBW
CLC
CLD
CLI
CMC
CMP
CWD
DEC
DIV
- Byte
- Word
- DWord
IN
INC
INT
Jcc
- Em loop
- No loop
JMP
LAHF
LEA
LOOP
Lseg
MOV
MUL
- Byte
- Word
- DWord
NEG
NOT
OR
2
2
2
3
7+m
3
2
2
5
2
2
2
2
9-14
9-22
9-38
12/13
2
depende
7+m
3
7+m
2
2
11
7
2
9-14
9-22
9-38
2
2
2
Clocks no 486
1
1
1
3
3
3
2
2
3
2
1
3
1
13-18
13-26
13-42
14
1
depende
3
1
3
3
1
6
6
1
13-18
13-26
13-42
1
1
1
OUT
POP
POPA
POPF
PUSH
PUSHA
PUSHF
REP
RET
ROL
ROR
SAHF
SBB
SHL
SHR
STC
STD
STI
STOS
SUB
TEST
XCHG
XOR
10/11
4
24
5
2
18
4
depende
10+m
3
3
3
2
3
3
2
2
3
4
2
2
3
2
16
1
9
9
1
11
4
depende
5
3
3
2
1
3
3
2
2
5
5
1
1
3
1
Voc provavlmente j notou que sua placa de vdeo tem mais que 256K de RAM.
(se no tem, ento estes tutoriais no so provavelmente para voc.) Mesmo que
voc tenha s 256K de RAM, como meu velho 386, voc ainda capaz de entrar
no modo 13h - 320x200x256. Porm, isto levanta algumas quetes.
Multiplique 320 por 200 e voc vai notar que voc s precisa de 64,000 bytes
de memria para armazenar uma tela simples. (A VGA na verdade nos d 64K,
que 65,536 bytes para quem no sabe.) O que aconteceu com os restantes 192K?
Bem, a VGA na verdade arrumada em planos de bits, como isso:
64,000
Cada plano sendo de 64,000 bytes. Aqui est como isso funciona:
Um pixel at 0, 0 mapeado no plano 0 no offset 0;
Um pixel at 1, 0 mapeado no plano 1 no offset 0;
DESENHANDO LINHAS
guardar isso, sendo que: i) CX j tem o valor de X2, e ii) vamos usar
uma instruo REP, que usar CX como contador.
SUB CX, AX ; CX = X2 - X1
Agora vamos precisar de calcular o DI para o primeiro pixel que plotaremos,
ento vamos fazer o que fizemos na rotina de PutPixel:
MOV
MOV
SHL
SHL
ADD
ADD
DI, AX ; DI = X1
DX, BX ; DX = Y
BX, 8 ; Shift Y esquerda 8
DX, 6 ; Shift Y esquerda 6
DX, BX ; DX = Y SHL 8 + Y SHL 6
DI, DX ; DI = Y x 320 + X
Temos o offset do primeiro pixel agora, ento tudo o que temos que fazer
colocar a cor que queremos desenhar em AL, e usar STOSB para plotar o resto
da linha.
MOV AL, Cor ; Move a cor a plotar em AL
REP STOSB ; Plota CX pixels
Note que usamos STOSB porque ele vai incrementar DI para ns, assim
economizando um monte de MOV's e INC's. Agora, dependendo de que linguagem
voc vai usar para implementar, voc vai chegar a algo assim:
void Draw_Horizontal_Line(int x1, int x2, int y, unsigned char color)
{
_asm
{
mov ax, 0A000h
mov es, ax
; Aponta ES para a VGA
mov ax, x1
mov bx, y
mov cx, x2
; AX = X1
; BX = Y
; CX = X2
sub cx, ax
; CX = Diferena de X2 e X1
mov di, ax
mov dx, bx
shl bx, 8
shl dx, 6
add dx, bx
add di, dx
; DI = X1
; DX = Y
; Y SHL 8
; Y SHL 6
; DX = Y SHL 8 + Y SHL 6
; DI = Offset do primeiro pixel
Semana passada ns chegamos seguinte rotina de linhas horizontais mov ax, 0A000h
mov es, ax
; Aponta ES para a VGA
mov ax, x1
mov bx, y
mov cx, x2
; AX = X1
; BX = Y
; CX = X2
sub cx, ax
; CX = Diferena de X2 e X1
mov di, ax
mov dx, bx
shl bx, 8
; DI = X1
; DX = Y
; Y SHL 8
shl dx, 6
add dx, bx
add di, dx
; Y SHL 6
; DX = Y SHL 8 + Y SHL 6
; DI = Offset do primeiro pixel
; Guarda Y2 em CX
; Guarda a cor a plotar em AL
; CX = tamanho da linha
No uma rotina fantstica, mas muito boa. Note como foi possvel realizar
uma comparao depois de DEC CX. Isto um conceito extremamente til, logo,
no se esquea de que isso possvel.
Brinque um pouco com o cdigo, e tente faz-lo mais rpido. Tente outros
mtodos de calcular o offset, ou mtodos diferentes de controle de fluxo.
Agora, isso foi a coisa fcil. Vamos ver agora uma rotina capaz de desenhar
linhas diagonais.
A seguinte rotina foi tirada de SWAG, autor desconhecido, e uma rotina ideal
para demonstrar um algoritmo de linhas. Ele est precisando muito de uma
otimizao, assim, essa pode ser uma tarefa para voc - se voc quiser.
Alguns dos pontos a considerar so:
1) Seja l quem o escreveu nunca ouviu falar de XCHG - isso economizaria
alguns clocks;
2) Ele comete um dos grandes pecados do cdigo no-otimizado - ele move
um valor para AX, e ento realiza uma operao envolvendo AX na prxima
instruo, assim fazendo um ciclo a mais. (Vamos falar sobre isso semana
que vem).
3) Ele trabalha com BYTES e no WORDS, assim, a velocidade de escrita para a
VGA poderia se dobrada se usasse words.
4) E o maior pecado de todos, ele usa um MUL para achar o offset. Tente
usar shifts ou um XCHG para acelerar as coisas.
De qualquer modo, eu pus os comentrios nele, e acho que ele
auto-explicativo, assim, eu no vou entrar em detalhes como ele funciona.
Voc deve ser capaz de pegar isso sozinho. Adentre a rotina, e veja como
a derivada (gradiente, variao, inclinao...) da linha foi calculada.
Procedure Line(X1, Y1, X2, Y2 : Word; Color : Byte); Assembler;
Var
DeX
DeY
: Integer;
: Integer;
IncF
: Integer;
Asm { Line }
mov ax, [X2] { Move X2 para AX
sub ax, [X1]
{ Pega o tamanho horizontal da linha
jnc @Dont1
{ X2 - X1 negativo?
neg ax
{ Sim, ento faz com que seja positivo
}
(X2 - X1)
}
}
@Dont1:
mov [DeX], ax { Agora, move o tamanho horizontal da linha para DeX }
mov ax, [Y2] { Move Y2 para AX
}
sub ax, [Y1]
{ Subtrai Y1 de Y2, dando o tamanho vertical
}
jnc @Dont2
{ Foi negativo?
}
neg ax
{ Sim, ento faa-o positivo
}
@Dont2:
mov [DeY], ax { Move o tamanho vertica para DeY
}
cmp ax, [DeX] { Compara o tamanho vertivcal com o horizontal
jbe @OtherLine { Se o vertical foi <= horizontal ento pula
}
mov ax, [Y1] { Move Y1 para AX
}
cmp ax, [Y2]
{ Compara Y1 a Y2
}
jbe @DontSwap1 { Se Y1 <= Y2 ento pula, seno...
mov bx, [Y2] { Pe Y2 em BX
}
mov [Y1], bx { Pe Y2 em Y1
}
mov [Y2], ax { Move Y1 para Y2
}
{ Para que depois de tudo isso.....
}
{ Y1 = Y2 e Y2 = Y1
}
mov
mov
mov
mov
ax, [X1]
bx, [X2]
[X1], bx
[X2], ax
{ Pe X1 em AX
{ Pe X2 em BX
{ Pe X2 em X1
{ Pe X1 em X2
@DontSwap1:
mov [IncF], 1 { Pe 1 em IncF, i.e., plota outro pixel
mov ax, [X1] { Pe X1 em AX
cmp ax, [X2] { Compara X1 com X2
jbe @SkipNegate1 { Se X1 <= X2 ento pula, seno...
neg [IncF]
{ Nega IncF
}
@SkipNegate1:
mov ax, [Y1] { Move Y1 para AX
mov bx, 320
{ Move 320 para BX
mul bx
{ Multiplica 320 por Y1
mov di, ax
{ Pe o resultado em DI
}
}
}
}
}
}
}
}
}
}
}
}
add
mov
mov
mov
mov
mov
mov
di, [X1]
{ Soma X1 a DI, e tcham - offset em DI
bx, [DeY] { Pe DeY em BX
}
cx, bx
{ Pe DeY em CX
}
ax, 0A000h { Pe o segmento a ser plotado, em AX
es, ax
{ ES aponta para a VGA
}
dl, [Color] { Pe a cor a usar em DL
}
si, [DeX] { Aponta SI para DeX
}
}
}
@DrawLoop1:
mov es:[di], dl { Pe a cor a plotar, DL, em ES:DI
}
add di, 320
{ Soma 320 a DI, i.e., prxima linha abaixo
}
sub bx, si
{ Subtrai DeX de BX, DeY
}
jnc @GoOn1
{ Ficou negativo?
}
add bx, [DeY] { Sim, ento soma DeY a BX
}
add di, [IncF] { Soma a quantidade a incrementar a DI
}
@GoOn1:
loop @DrawLoop1 { Nenhum resultado negativo, ento plota outro pixel }
jmp @ExitLine { Acabou, ento vamos embora!
}
@OtherLine:
mov ax, [X1] { Move X1 para AX
cmp ax, [X2]
{ Compara X1 a X2
jbe @DontSwap2 { X1 <= X2 ?
mov bx, [X2] { No, ento move X2 para BX
mov [X1], bx { Move X2 para X1
mov [X2], ax { Move X1 para X2
mov ax, [Y1] { Move Y1 para AX
mov bx, [Y2] { Move Y2 para BX
mov [Y1], bx { Move Y2 para Y1
mov [Y2], ax { Move Y1 para Y2
}
}
}
}
}
}
}
}
}
}
@DontSwap2:
mov [IncF], 320 { Move 320 para IncF, i.e., o prximo pixel est na }
{ prxima linha
}
mov ax, [Y1] { Move Y1 para AX
}
cmp ax, [Y2]
{ Compara Y1 a Y2
}
jbe @SkipNegate2 { Y1 <= Y2 ?
}
neg [IncF]
{ No, ento nega IncF
}
@SkipNegate2:
mov ax, [Y1] { Move Y1 para AX
mov bx, 320
{ Move 320 para BX
mul bx
{ Multiplica AX por 320
mov di, ax
{ Move o resultado para DI
add di, [X1] { Soma X1 a DI, dando o offset
}
}
}
}
}
mov
mov
mov
mov
mov
mov
}
}
@DrawLoop2:
mov es:[di], dl { Pe o byte em DL para ES:DI
inc di
{ Incrementa DI de um, o prximo pixel
sub bx, si
{ Subtrai SI de BX
}
jnc @GoOn2
{ Ficou negativo?
}
add bx, [DeX] { Sim, ento soma DeX a BX
add di, [IncF] { Soma IncF a DI
}
@GoOn2:
loop @DrawLoop2
{ Continua plotando
}
}
}
}
}
}
}
@ExitLine:
{ Pronto!
End;
Acho que no fiz nenhum erro com os comentrios, mas eu estou bem cansado,
e no tenho bebido cafena h dias - se voc encontrar um erro - por favor
me diga.
03C8h
03C9h
O que tudo isso significa Se ns fssemos setar um valor RGB de uma cor RGB, ns mandaramos o valor da
cor que queramos mudar para 03C8h, ento ler os 3 valores de 03C9h. Em
Assembler, faramos isso:
mov dx, 03C8h
; Pe o registrador DAC de leitura em DX
mov al, [Color]
; Pe o valor da cor em AL
out dx, al
; Manda AL para a porta DX
inc dx
; Agora usa a porta 03C9h
mov al, [R]
; Pe o novo valor VERMELHO em AL
out dx, al
; Manda AL para a porta DX
mov al, [G]
; Pe o novo valor VERDE em AL
out dx, al
; Manda AL para a porta DX
mov al, [B]
; Pe o novo valor AZUL em AL
out dx, al
; Manda AL para a porta DX
E aquilo deveria fazer a coisas direitinho. Para ler a palette, faramos isso:
mov dx, 03C7h
; Pe o registrador DAC de escrita em DX
mov al, [Color]
; Pe o valor da cor em AL
out dx, al
; Manda AL para a porta DX
add dx, 2
; Agora usa a porta 03C9h
in al, dx
les di, [R]
stosb
in al, dx
les di, [G]
stosb
in al, dx
les di, [B]
stosb
Note como essa rotina foi codificada diferentemente. Isso era originalmente
uma rotina em Pascal, e como o Pascal no gosta que voc mexa com variveis
de Pascal em Assembler, voc tem que improvisar.
Se voc est trabalhando com Assembler puro, ento voc pode codificar
isso muito mais eficientemente, como o primeiro exemplo. Eu deixei o cdigo
como estava para que aqueles trabalhando com uma linguagem de alto nvel
possam chegar a um problema particularmente irritante.
Agora voc j viu como IN e OUT podem ser teis. Controlar diretamente o
Voc pode, se quiser, mover uma copia do flags para AH com LAHF - (Carrega AH
com Flags) - e modificar ou ler bits individualmente, ou mudar o status dos
bits mais facilmente com CLx e STx. Contudo se voc planeja mudar os flags,
lembre-se de que eles podem ser extremamente teis em muitas situaes.
(Eles podem tambm ser muito enjoados quando tarde da noite, linhas comeam
a desenhar para trs, e voc gasta um hora procurando o porqu - e ento se
lembra que voc se esqueceu de limpar o flag de direo!)
Acho que cobrimos muito pouca coisa importante neste tutorial. D uma olhada
nos flags, e volte naquela rotina compridona de fazer linhas, j que ela
um timo exemplo de controle de fluxo. Assegure-se de que suas capacidades de
controlar fluxos de intrues esto perfeitas.
Quando eu comecei a brincar com Assembler eu logo vi que o Turbo Pascal, (a linguagem
com que eu trabalhava at ento), tinha poucas limitaes - uma delas que
ela era, e ainda , uma linguagem de 16 bits. Isso significava que se eu
quisesse brincar com escritas super-rpidas em 32 bits, eu no poderia.
Nem mesmo com seu prprio Assembler (bem, no facilmente).
O que eu precisava fazer era escrever cdigo separadamente 100% em Assembler
e linkar ao Turbo. Isso no uma tarefa particularmente difcil, e uma das
que eu vou tentar ensinar a voc hoje.
A outra vantagem de escrever rotinas em Assembler puro que voc tambm pode
linkar o cdigo objeto resultante a outra linguagem de alto nvel, como o C.
ESCREVENDO CDIGO EXTERNO PARA SUA LINGUAGEM DE ALTO NVEL
Antes de comearmos, voc precisa de uma idia do que so chamadas far e near.
Se voc j sabe, ento pule essa pequena seo.
Como discutimos antes, o PC tem uma arquitetura segmentada. Como voc sabe,
voc s pode acessar um segmento de 64K de cada vez. Agora se voc est
trabalhando com cdigo de menos de 64K de tamanho, ou em uma linguagem que
cuida de todas as preocupaes para voc, voc no precisa de se preocupar
tanto. Contudo, trabalhando em Assembler, precisamos sim.
Imagine que tenhamos o seguinte programa caregado na memria:
64K
ROTINA DOIS
ROTINA UM
64K
Entradada
PROGRAMA PRINCIPAL
Sada
Quando um JMP for executado para transferir o controle para a Rotina Um, esse
ser uma chamada near(perto). Ns no deixamos o segmento em que o corpo
principal do programa est localizado, e assim quando o JMP ou CALL
executado, e CS:IP mudado por JMP, s o IP precisa ser mudado, no CS.
O offset muda, mas o segmento no.
Agora, pular para a Rotina Dois seria diferente. Isto deixa o segmento
corrente, e assim ambas as partes do par CS:IP precisaro de ser alteradas.
Isto uma chamada far (longe).
O problema ocorre quando a CPU encontra um RET ou RETF no fim da chamada.
Digamos que voc, por acidente, colocou RET no fim da Rotina Dois, ao invs
de RETF. Como a CPU viu RET, ela s tiraria IP da pilha, e assim, sua
mquina travaria, provavelmente, j que CS:IP estaria apontando para um lixo.
Este ponto especialmente importante quando for linkar a uma linguagem de
alto nvel. Seja l quando for que voc escrever um cdigo em Assembly e
linkar, digamos, ao Pascal, lembre-se de usar a diretiva de compilao
{$F+}, mesmo se no foi uma chamada FAR. Deste modo, depois de o Turbo
chamar a rotina, ele tira CS e IP da pilha, e tudo vai bem.
Falhas em fazer isso so problemas seus!
.DATA
.CODE
START:
END START
Agora, acho que hora de vocs pularem um grau no uso daquele esqueleto.
Vejamos outros modos de arrumar uma rotina-esqueleto.
DATA
DATA
ENDS
ENDS
END
Este , obviamente, um esqueleto diferente. Note como eu omiti o ponto antes
de DATA e CODE. Dependendo de que Assembler/Linker voc usar, voc pode
precisar do ponto ou no. TASM, o Assembler que eu uso, aceita os dois
formatos, ento, pegue um com que voc e seu assembler estejam felizes.
Note tambm o uso de DATA SEGMENT WORD PUBLIC. Primeiramente, WORD diz
ao Assembler para alinhar o segmento em limites de word.
FATO ENGRAADO: Voc no precisa se preocupar com isso por enquanto, pois o
Turbo Pascal faz isso de qualquer modo, assim, colocar
BYTE ao invs de word no faria diferena nenhuma. :)
PUBLIC permite ao compilador que voc usar, acessar quaisquer variveis no
segmento de dados. Se voc no quer que seu compilador tenha acesso a qualquer
varivel que voc declarar, ento apenas omita isso. Se voc no precisar
de acessar o segmento de dados, ento esquea o segmento de dados todo.
Agora, o segmento de cdigo. Geralmente, voc vai precisar incluir isso em
todo o cdigo que voc escrever. :) A sentena ASSUME tambm ser um padro
em tudo que voc vai trabalhar. Voc tambm pode esperar ver CSEG e DSEG
ao invs de CODE e DATA. Note de novo que ele declarado como PUBLIC.
nele que todas as nossas rotinas vo.
ENDS
END
Agora, tudo o que temos a fazer codific-los. Mas, espere um minuto - a
rotina PutPixel tem PARMETROS! Como us-los em cdigo externo??
Isto um macete. O que fazemos colocar os valores na pilha, simplesmente
dizendo -- PutPixel(10,25,15); -- j faz isso para ns. tirar eles de l
que o mais difcil. O que eu geralmente fao, e sugiro a vocs fazer,
se certificar de DECLARAR TODAS AS PROCEDURES EXTERNAS COMO FAR. Isso
faz
as coisas trabalharem com a pilha mais fcil.
Ok, agora que eu tenho voc realmente confuso, e com razo, isso piora!
Para referenciar estes parmetros no seu cdigo, voc tem que usar o ponteiro
de pilha, SP. O problema que voc no pode brincar com SP diretamente, voc
tem que botar BP na pilha, e mover SP para ele. Isso agora coloca mais dois
bytes na pilha. Digamos que BP era igual a 0A45h. Antes de colocar BP, a
pilha era assim:
4C EF 43 12 0F 00 14 00 0A 00
^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
CS:IP Color Y X
Depois, ela ficou assim:
45 0A 4C EF 43 12 0F 00 14 00 0A 00
^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^
BP CS:IP Color Y X
Agora que ns passamos por isso tudo, podemos realmente acessar essas as
porcarias! O que voc faria depois de chamar -- PutPixel(10, 20, 15); -para acessar o valor de Color isto:
PUSH BP
MOV BP, SP
MOV AX, [BP+6] ; Agora temos Color
Podemos acessar X e Y assim:
MOV BX, [BP+8] ; Agora temos Y
MOV CX, [BP+10] ; Agora temos X
E agora restauramos BP:
POP BP
Agora, retornamos de uma chamada FAR, e removemos os seis bytes de dados que
pusemos na pilha:
RETF 6
E s isso!
Procedure InitMCGA
InitMCGA PROC FAR
MOV AX, 0A000H
; Aponta AX para a VGA
MOV FS, AX
; Porque no FS?
MOV AH, 00H
MOV AL, 13H
INT 10H
RETF
InitMCGA ENDP
Procedure Init80x25
Init80x25 PROC FAR
MOV AH, 00H
MOV AL, 03H
INT 10H
RETF
Init80x25 ENDP
CODE ENDS
END
Procedure Init80x25;
{$F-}
External;
Begin
InitMCGA;
PutPixel(100, 100, 100);
ReadLn;
Init80x25;
End.
Voc pode fazer suas rotinas Assembler retornarem valores que voc pode usar
em sua linguagem de alto-nvel, se voc quiser. A tabela abaixo contm
toda a informao que voc precisa saber.
Tipo a Retornar Registrador(es)a Usar
Byte
AL
Word
AX
LongInt
DX:AX
Pointer
DX:AX
Real
DX:BX:AX
Agora que voc j viu como escrever cdigo externo, voc provavelmente quer
saber como melhor-lo para obter a performance total que o cdigo externo
pode oferecer.
Alguns pontos para voc trabalhar se seguem:
No se pode trabalhar com SP diretamente, mas voc pode usar ESP.
Isso vai acabar com a lentido de empilhar/desempilhar BP.
Lembre-se de mudar [xx+6] para [xx+4] para o ltimo (primeiro) parmetro,
j que BP no est mais na pilha.
Gaste um tempo e veja o que voc pode fazer com isso. possvel atravs de
melhorias, fazer um cdigo mais rpido que a rotina no MODE13H.ZIP verso 1
(disponvel na minha homepage).
OTIMIZAO
Por mais rpido que o Assembler seja, voc sempre pode acelerar as coisas.
Eu vou falar como acelerar seu cdigo no 80486, e no 80386.
Eu no vou me preocupar muito com o Pentium por enquanto, j que os truques
de uso do Pentium so realmente truques, e demoraria um pouco para explicar.
Tambm, voc deveria evitar cdigo especfico para Pentium (embora isso
esteja mudando lentamente).
Bem, agora voc j viu como escrever cdigo externo, declarar procedures
em Assembler e otimizar suas rotinas.
Ok, ento voc viu como criar variveis corretamente. Mas, e constantes?
Bem, em Assembler, constantes so conhecidas como Equates. Equates fazem a
cofificao em Assembler muito mais fcil, e pode simplificar muito as coisas.
Por exemplo, se eu tivesse usado o seguinte em tutoriais anteriores:
LF EQU 10
CR EQU 13
DB LF, CR "Isso uma string$"
...as pessoas teriam entendido direito aquela coisa de 10 e 13. Mas, para fazer as
coisas um pouco mais complicadas, h ainda um outro modo que voc pode usar para dar
valores a identificadores. Voc pode fazer como voc faria em BASIC:
Population = 4Ch
Magnitude = 0
Basicamente, voc pode ter em mente os seguintes pontos:
Uma vez que voc tenha usado EQU para dar um valor a um identificador, voc no
pode
mudar isto.
EQU pode ser usado para definir quase qualquer tipo - inclusive strings.
Contudo, voc no pode fazer isto quando se usa um ' = '. Um ' = ' s pode
definir valores numricos.
Voc pode usar EQU quase em qualquer lugar de seu programa.
Valores definidos com ' = ' podem ser mudados.
E agora, vamos a um dos pontos mais macetosos de codificao em Assembler estruturas. Estruturas no so variveis, so um TIPO - basicamente um esquema
de uma varivel.
Como um exemplo, se voc tivesse o seguinte em Pascal:
Type
Date
= Record;
Day : Byte;
Month : Byte;
Year : Word;
End; { Record }
Voc poderia representar isto em Assembler como segue:
Date
Day
Month
Year
Date
STRUC
DB ?
DB ?
DW ?
ENDS
Porm, um das vantagens de Assembler que voc pode inicializar todos ou alguns
dos campos da estrutura antes mesmo de voc se referir estrutura em seu
segmento de cdigo.
Aquela estrutura acima poderia ser escrita facilmente como:
Date
Day
Month
Year
Date
STRUC
DB ?
DB 6
DW 1996
ENDS
Bem, voc viu como definir estruturas, mas como voc se refere de verdade a elas
em seu cdigo?
Tudo o que voc tem a fazer, colocar em algum lugar algumas linhas como as seguintes
em seu programa - de preferncia no segmento de dados.
Date
Day
Month
Year
Date
STRUC
DB 19
DB 6
DW 1996
ENDS
; ou algo como
; ou at mesmo
Simples, n?
Bem, agora que voc j viu como criar arrays, eu suponho que voc queira saber como
referenciar elementos individualmente. Bem, digamos que voc tivesse o seguinte array:
OutroArray DB 50 DUP (?)
Se voc quisesse mover o elemento 24 para, digamos, BL, ento voc poderia fazer isto:
MOV BL, [OutroArray + 23]; Ou, seria possvel dizer:
MOV AX, 23,
MOV BL, [OutroArray + AX]
NOTA: No esquea que todos os arrays comeam no elemento ZERO. Linguagens de
alto-nvel como C e Pascal fazem voc esquecer isto devido ao modo que eles
deixam voc referenciar arrays.
Agora, isso foi fcil, mas, e se OutroArray fosse de 50 WORDS, no BYTES?
OutroArray DW 50 DUP (?) ; como esse.
Bem, para acessar o elemento 24, voc teria que multiplicar o valor de ndice por dois,
e ento somar isso a OutroArray para conseguir o elemento desejado.
MOV AX, 23
; Acesso elemento 24
SHL AX, 1
; Multiplique AX por dois
MOV BX, [OutroArray + AX]
; Adquira elemento 24 em BX
No to difcil assim, n? Porm, este mtodo fica um pouco macetoso quando
voc no tem clculos fceis para fazer quando o ndice no uma potncia de dois.
Digamos que voc tivesse um array que tem um tamanho de elemento de 5 bytes.
Se ns quisssemos conferir o stimo elemento, ns teramos que fazer algo
assim:
MOV AX, 6
; Pega o stimo elemento
MOV BX, 5
; Cada elemento tem cinco bytes
MUL BX
; AX = 6 x 5
MOV DX, [YetAnotherArray + AX]
; Coloca o elemento 7 em DX
Antes de continuarmos com mais alguma coisa, eu suponho que seja hora de
falar sobre nmeros de ponto flutuante. Agora, nmeros de ponto flutuantes
podem ser desajeitados para se manipular em Assembler, assim v se no sai
escrevendo aquele programa de planilha eletrnica que voc sempre quis, em
cdigo de mquina! Porm, quando estiver trabalhando com mapeamento de textura,
crculos e outras funes mais complicadas, inevitvel que voc precise de
algo para declarar nmeros de ponto flutuante.
Digamos que quisssemos armazenar Pi. Para declarar Pi, ns precisamos usar a
DT diretiva. Voc poderia declarar Pi assim:
Pi DT 3.14
DT na verdade reserva dez bytes de memria, assim seria possvel declarar Pi com
um nmero maior de casas decimais.
Eu no vou entrar nas particularidades de nmeros de ponto flutuante neste
tutorial. Quando ns precisarmos deles mais tarde, eu falo sobre isso.
Certo, no ltimo tutorial disse eu que eu daria algum tipo de resumo do que ns
cobrimos durante os ltimos quatro meses. (Ei - isso como se fosse um tutorial
a cada duas semanas, ento talvez eles no tenham sado to irregularmente, afinal
de contas!)
De qualquer maneira, eu vou falar sobre a parte de pegar e setar bits individuais
num registrador, porque este um tpico importante que eu deveria ter coberto h
muito tempo atrs.
OPERADORES LGICOS
e XOR.
(A propsito, em uma edio de Tutorial Cinco, eu errei a tabela para XOR,
amavelmente apontado por Keith Weatherby, assim se voc no tem a verso
mais atual, (V 1.3), ento pegue agora. Por favor, embora eu tente o meu melhor
para excluir qualquer erro dos Tutoriais, alguns ficam com erros, assim se voc achar
algum, por favor me avise.
Mas tenha certeza de que voc tem as edies mais recentes dos tutoriais antes de fazer
isto!)
Certo, chega de meus erros. Essas tabelas se pareciam com estas:
AND
0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1
OR
0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1
XOR
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
Isto est tudo muito bem, mas pra qu vamos usar isso? Bem, em primeiro
lugar, vamos dar uma olhada no que o AND pode fazer. Ns podemos usar o AND
para mascarar bits em um registrador ou varivel, e assim setar e resetar bits
individuais.
Como um exemplo, usaremos o AND para testar um valor de um nico bit. Olhe os
exemplos seguintes, e veja como voc pode usar AND para seus prprios fins. Um
uso bom para AND seria conferir se um caracter lido do teclado uma maiscula
ou no. (Voc pode fazer isto, porque a diferena entre uma maiscula e sua minscula
de um bit.
Ex: 'A' = 65 = 01000001
'a' = 97 = 01100001
'S' = 83 = 01010011
's' = 115 = 01110011)
Assim, da mesma forma que voc pode azer um AND de nmeros binrios, voc
poderia usar uma aproximao semelhante para escrever uma rotina que confere se
um caracter maisculo ou minsculo.
Ex:
0101 0011
0111 0011
0101 0011
OR 0010 0000
= 0111 0011
^^^ S maisculo agora foi mudado para s minsculo ^^^
1010 0010
XOR 1110 1011
= 0100 1001
Faa alguma tentativa para aprender estes operadores binrios, e o que eles
fazem. Eles so uma ferramenta inestimvel quando se est trabalhando com
nmeros binrios.
OBS.: Para simplicidade, o Turbo Assembler lhe permite usar nmeros binrios
em seu cdigo. Por exemplo, seria possvel dizer, AND AX, 0001000b em vez de AND
AX, 8h para testar o bit 3 de AX. Isto pode facilitar as coisas para voc quando
codificar.
O PROGRAM DEMONSTRATIVO
Certo, chega da parte chata - vamos ao programa demonstrativo que eu inclu!
Eu pensei que j era sem tempo escrever outra demonstrao - 100% Assembler
desta vez, e vamos a uma rotina de fogo. Rotinas de fogo podem parecer bem
efetivas, e so surpreendentemente fceis de se fazer, assim, pensei,
por que no...
Bem, no importa o quo bem eu expliquei isso tudo, muito difcil de ver o
que est acontecendo sem olhar o cdigo. Ento agora ns vamos dar uma olhada no
programa, seguindo o que est acontecendo.
Bem, em primeiro lugar, voc tem o header.
.MODEL SMALL ; Segmento de dados < 64K, segmento de cdigo < 64K
.STACK 200H ; Arruma 512 bytes de espao para a pilha
.386
Aqui, eu disse que o programa ter um segmento de cdigo e de dados
total de menos que 128K. Eu vou dar para o programa uma pilha de 512
bytes, e permitir instrues do 386.
.DATA
CR
LF
EQU 13
EQU 10
DW 3749h
INCLUDE PALETTE.DAT
; A palette, gerada com
; Autodesk Animator, e um programa simples em
; Pascal.
AX, AX
; Zera AX
AL, DS:[SI]
; Pega o valor do pixel atual
AL, DS:[SI+1]
; Pega o valor do pixel direita
AH, 0
AL, DS:[SI-1]
; Pega o valor do pixel esquerda
AH, 0
AL, DS:[SI+BufferX]
; Pega o valor do pixel abaixo
AH, 0
AX, 2
; Divide o total por quatro
JZ NextPixel
DEC AX
; O resultado zero?
; No, ento decrementa de um
NOTA:
O valor de decay (queda) UM. Se voc mudar a linha acima
para, por exemplo "SUB AX, 2" voc vai ver que o fogo no chega to
alto. Experimente... seja criativo! :)
NextPixel:
MOV DS:[SI-BufferX], AL
; Pe o novo valor no array
INC SI
; Prximo pixel
DEC CX
; Um a menos para fazer
JNZ Alter
; J fizemos todos?
RET
AveragePixels ENDP
Agora ns vimos a procedure que faz toda a suavizao. Basicamente, ns
s temos um loop que soma os valores de cor dos pixels ao redor de um pixel,
carregando os valores dos pixels antes. Quando ela tem o total em AX,
dividido por quatro para conseguir uma mdia. A mdia ento plotada
diretamente sobre o pixel atual.
Para mais informao relativo instruo de ADC, observe isto em Tutorial 5, e
olhe os programas abaixo:
Var
W : Word;
Begin
Asm
MOV AL, 255
ADD AL, 1
MOV AH, 0
ADC AH, 0
MOV W, AX
End;
Var
W : Word;
Begin
Asm
MOV AL, 255
ADD AL, 1
MOV W, AX
End;
Write(W);
End;
Write(W);
End;
^^^ Este programa returna 256
Lembre-se de que ADC usado para ter certeza que quando um registrador ou
varivel no grande bastante para armazenar um resultado, o resultado no ser
perdido.
OK, depois de pular algumas procedures um pouco mais irrelevantes, chegamos ao
corpo principal do programa, que algo desse tipo:
Start:
INT 21H
END Start
; Volta ao DOS
E eu acho que essa ltima parte tambm bem fcil de entender. Eu tentei comentar
o fonte o tanto quanto eu pude, talvez um pouco mais fortemente em algumas
partes, mas eu espero que agora todo mundo tenha uma idia de como uma rotina de fogo
funciona.
E/S DE ARQUIVOS
Cedo ou tarde, voc vai querer mexer com arquivos. Tudo que voc tem que
ter em mente aqui que tudo BASEADO EM HANDLES. Aqueles de vocs que usaram
ou
experimentaram com XMS percebero o que eu quero dizer com handles
exatamente, mas se voc no, ento aqui vai um resumo rpido:
* Voc abre/cria um arquivo.
* Voc recebe um inteiro de 16 bits sem sinal para referenci-lo.
Qual a dificuldade nisso?
Nota: Antigamente, antes do DOS 2, voc tinha que usar Blocos de Controle de
Arquivo (FCB) para referenciar seus arquivos. (Voc provavelmente j viu
FCBS=xxxx em arquivos de CONFIG.SYS, e isso para ajudar programas que
foram projetados para o XT.) Ns podemos esquecer agora tudo sobre FCBs, j
eles esto quase obsoletos.
Abrindo UM Arquivo:
(Interrupo 21H)
AH = 3DH
AL = tipo de operao:
0 = operao s de leitura;
1 = operao s de escrita;
2 = operao de leitura/escrita.
DS:DX = nome do arquivo
Retorna:
Se foi realizada com sucesso, o flag de carry zerado, e o handle de arquivo
returnado em AX. Porm, se algo saiu errado, o flag de carry setado em um,
e o cdigo de erro volta em MACHADO. Para uma lista de todo os cdigos de erro,
veja a seguinte tabela mais abaixo.
Agora, depois de tudo isso, um exemplo:
.MODEL SMALL
.STACK 200H
.DATA
FileName DB "EXAMPLE.TXT$"
Error
DB "Uh oh$"
.CODE
START:
MOV AX, @DATA
; Aponta AX para o segmento de dados
MOV DS, AX
; AX --> DX
MOV DX, OFFSET FileName ; Pe o offset do arquivo a abrir em DX
MOV AH, 3DH
; Abre
MOV AL, 00H
; s para leitura
INT 21H
JC
Problem
; Nada
Problem:
MOV DX, OFFSET Error
MOV AH, 09H
INT 21H
; Uh oh
Done:
MOV AX, 4C00H
; Pula de volta pro DOS - fechando qualquer
INT 21H
; arquivo aberto. Relaxado, ns ainda no sabemos
; como fechar arquivos.
END START
(Interrupo 21H)
AH = 3CH
CX = tipo de arquivo:
0 = arquivo normal;
1 = s de leitura;
2 = arquivo escondido;
4 = arquivo de sistema;
DS:DX = nome do arquivo
Retorna:
Como antes, se realizar com sucesso, o flag de carry zerado, e o
handle do arquivo retornado em AX. Note que voc deve tomar cuidado
com arquivos existentes antes de criar um arquivo novo com mesmo nome.
O DOS no conferir se um arquivo do mesmo nome j existe, e escrever
por cima do velho.
Antes de criar um arquivo novo - tente abrir o arquivo primeiro. Se
voc obtiver o cdigo de erro 2 em AX, (arquivo no existe), ento prossiga e
crie o arquivo novo. Se voc no conseguiu o erro 2, voc estar escrevendo
por cima de um arquivo j existente!
Como voc deveria saber de experincias com linguagens de alto-nvel, voc tem que
fechar seus arquivos antes de terminar seu programa. (Na verdade, a funo
4CH fecha todos os arquivos abertos de qualquer maneira, mas isso um modo
relaxado de fazer as coisas.) Para fechar um arquivo aberto, voc deveria fazer
isto:
Fechando UM Arquivo:
(Interrupo 21H)
AH = 3EH
BX = handle de arquivo
Retorna:
De novo, qualquer erro refletido no flag de carry e AX.
Cdigo Explicao
00H erro Desconhecido
01H
nmero de funo invlido
02H
Arquivo no achado
03H
Caminho no achado
04H
muitos arquivos abertos
05H
Acesso negado
06H
handle invlido
07H
Blocos de controle destrudos
08H
Falta de memria
09H
endereo de bloco de controle Ruim
0AH ambiente invlido
0BH formato invlido
0CH cdigo de acesso invlido
0DH dados invlidos
0EH erro desconhecido
0FH drive invlido
10H
no pode remover diretrio atual
11H
Dispositivo no o mesmo
12H
mais nenhum arquivo disponvel
13H
14H
15H
16H
17H
18H
19H
1AH
1BH
1CH
1DH
1EH
1FH
Certo, assim ns vimos como criar, abrir e fechar arquivos. Agora vamos fazer
algo com eles. Para ler alguns bytes de um arquivo, voc tem que usar funo
3FH. Assumindo que voc j abriu o arquivo de onde voc quer ler, voc pode
usar um pouco de cdigo como o abaixo:
MOV AH, 3FH
; L byte(s)
MOV BX, Handle
; arquivo a trabalhar
MOV CX, BytesToRead
; quanto a ler
MOV DX, OFFSET WhereToPutThem ; um array ou varivel
INT 21H
JC
DidSomethingGoWrong
; Checa erros
Se voc est tendo problemas em sacar algo disso - no se preocupe muito. Apenas
volte aos exemplos acima e veja como pode fazer sentido.
Prximo tutorial ns continuaremos com sprites - (e como carrreg-los do disco) assim voc ver um bom exemplo.
Bem... agora, escrevendo em um arquivo. Muito semelhante a ler, ns usamos a funo
OK... agora ns vamos escrever uma pequena rotina externa e linkar isto ao C.
Vamos dar uma olhada num esqueleto bsico que apenas pe algum texto na tela.
==================== LIBRARY.ASM ===============
.MODEL SMALL
.DATA
_sample
; --------------------------------------------------------------------------;
; void sample();
;
_sample
PROC NEAR
; Fora daqui!
ENDP
END
Bem.... no h nada muito engenhoso l. Agora, e o cdigo C que vai junto com isto?
======================== EXAMPLE.C ===========================
extern void sample();
int main()
{
sample();
return 0;
_YourVariable DW 9999
.CODE
END
Mas que tal passar parmetros para suas rotinas? Ns poderamos fazer isso
do modo difcil, como ns fizemos com Pascal, ou alternativamente, poderamos usar a
diretiva ARG.
ARG brilhante, porque simplifica grandemente as coisas -- mas tem algumas
negligncias. Isto , em toda rotina voc precisa de umas trs instrues
adicionais. Se voc quer velocidade e no se incomoda com um pouco de trabalho duro,
trabalhe diretamente com a pilha como ns fizemos no Tutorial Sete.
Aqui est como se usa ARG:
======================== LIBRARY.ASM =========================
.MODEL SMALL
.DATA
.CODE
PUBLIC _putpixel
; --------------------------------------------------------------------------;
; void putpixel(int x, int y, char color, int location);
;
_putpixel PROC NEAR
ARG X : Word, Y : Word, Color : Byte, Location : Word
PUSH BP
MOV BP, SP
MOV
MOV
MOV
MOV
MOV
MOV
SHL
SHL
ADD
ADD
MOV
; Salva BP
; BP *deve ser* igual a SP para ARG funcionar
AX, [Location]
ES, AX
BX, [X]
DX, [Y]
DI, BX
BX, DX
DX, 8
BX, 6
DX, BX
DI, DX
AL, [Color]
MOV ES:[DI], AL
POP BP
RET
_putpixel ENDP
END
========================= EXAMPLE.C ==========================
extern void putpixel(int x, int y, char color, int location);
int main()
{
asm {
mov ax, 0x13
int 0x10
}
putpixel(100, 100, 12, 0xa000);
sleep(2);
asm {
mov ax, 0x03
int 0x10
}
return(0);
}
Macros so uma das caractersticas mais poderosas que voc tem sua disposio
quando est trabalhando com o Assembler. Freqentemente voc se achar
repetindo as mesmas poucas linhas de cdigo inmeras vezes quando estiver escrevendo
programas maiores. Voc no quer fazer aquela dificuldade de criar um
procedimento -- que reduziria a velocidade do cdigo, mas voc no quer continuar
se repetindo.
A resposta.... MACROS.
Uma macro s um conjunto de instrues que recebe um nome pelo qual ela ser
referenciada no cdigo. Voc pode definir um macro assim:
MyMacroName
MACRO
;
; Suas instrues vo aqui
;
ENDM
MyMacroName
E dali em diante, sempre que voc puser MyMacroName em seu cdigo, sero
colocadas as instrues contidas dentro da macro no lugar do nome da macro.
OBS.: provavelmente melhor declarar qualquer macro antes de declarar o
segmento de dados. Para ficar mais claro, coloque todas suas macros em outro arquivo
de texto e ento use INCLUDE<nomedoarquivo> para incluir as macros.
Macros tambm podem ter parmetros e podendo ser muito teis. Por
exemplo, eu usei muito a funo DOS 09H para pr uma string na tela. Eu
poderia fazer os programas que eu escrevo mais fceis de ler primeira vista
criando a seguinte macro:
PutText MACRO TextParam
MOV AH, 09H
; TextParam o parmetro--NO
MOV DX, OFFSET TextParam ; uma varivel. Substitua TextParam com
INT 21H
; qualquer nome que voc escolher.
ENDM
PutText
Ento, assumindo no segmento de dados que eu tenha declarado uma string assim:
AString DB "This is a string$"
Eu poderia exibir aquela string escrevendo:
PutText AString
OBS.: Quando voc est trabalhando com macros, tenha cuidado em observar
que registradores elas mudam. Se estiver em dvida, d um push e um pop em
quaisquer registradores que voc sente que possam ser afetados.
O PROGRAMA DEMONSTRATIVO
Voc pode fazer experincias com Temp3 -- quanto maior for o nmero
que voc subtrair, mais rpido o plasma vai mover.
Voc agora vai para o Loop (2).
Loop (2): Ao incio de cada linha voc deve:
* Incrementar Temp4 de um;
* Fazer Temp1 = Sintable[Linha corrente + Temp3];
* Fazer Temp2 = SinTable[Temp4];
Voc agora vai para o Loop (3).
Loop (3): Para todo pixel na linha atual voc deve:
* Calcular a cor daquele pixel a ser plotado;
O valor de cor daquele pixel simplesmente definido por:
SinTable[Temp1 + Temp2] + SinTable[Linha corrente + Temp2]
Infelizmente, isto um pouco mais difcil de calcular no
Assembler e acaba levando muitas linhas de cdigo!!
* Incrementar Temp1 de um;
* Incrementar Temp2 de um;
Depois de fazer uma linha inteira, voc ento volta atrs ao Loop (2).
Uma vez feitas todas as linhas (200), voc pode ento voltar ao Loop (1).
Claro que, voc tambm vai querer pr algo para checar o retrace, e seria uma boa
idia tambm se algum apertou alguma tecla!!
NOTA: Para quem no sabe, o VGA tem um registrador de estado que vale a pena
prestar ateno em que ele serve. registrador 03DAH, e conferindo seus
vrios bits, podemos ver o que est acontecendo com o VGA.