Você está na página 1de 107

O que Assembler?

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

Incidentalmente, o assunto do alto e do baixo byte destes resgistradores


causou muita confuso no passado, logo, tentarei dar alguma explicao aqui.
AX tem um intervalo de 0 at FFFFh. Isto significa que voc tem um intervalo
de 0 at FFh para AH e AL. (Se sabe pouco sobre hexadecimal, no se
preocupe. O prximo tutorial vai falar sobre ele.)
Agora, se ns tivermos que armazenar 0A4Ch em AX, AH conter 0Ah, e AL conter
4Ch. Sacou? Este um conceito muito importante, e eu falarei sobre ele
mais profundamente no prximo tutorial.
Os registradores de segmento: - ta da!
Estes so outros registradores que ns no vamos ver nos primeiros tutorias,
mas vamos v-los em maior profundidade mais tarde. Eles so imensamente teis,
mas podem ser tambm perigosos.
CS - o segmento de cdigo. O bloco de memria onde o cdigo armazenado.
NO brinque com esse, a menos que saiba o que est fazendo.
DS - o segmento de dados. A rea na memria onde os dados so armazenados.
Durante operaes de bloco, quando grandes blocos de dados so movidos,
este o segmento a que a CPU comumente se refere.
ES - o segmento extra. Apenas outro segmento de dados, mas este
comumente usado quando se quer acessar o vdeo.
SS - o segmento de pilha, em que a CPU armazena endereos de retorno de
subrotinas. Tome cuidado com ele.
Alguns outros que voc vai comumente usar:
SI - o ndice de fonte. Frequentemente usado para movimentaes de blocos
de instrues. Este um ponteiro que, com um segmento, geralmente
DS, usado pela CPU para leitura.
DI - o ndice de destino. Novamente, voc o usar muito. Um outro
ponteiro que, com um segmento, geralmente ES, usado para escrita
pela CPU.
BP - o apontador da base, usado em conjunto com o segmento de pilha. Ns
no vamos us-lo muito.
SP - o apontador da pilha, comumente usado com o segmento de pilha. NO
brinque com isso de jeito nenhum.

Por enquanto voc deveria saber o que so registradores. H outros


registradores tambm, e coisas conhecidas como flags, mas ns no iremos
a eles agora.
COISAS PARA FAZER:
1) Aprender os vrios registradores de cor.
2) Arrumar uma calculadora que suporte hexadecimal - ou pelo menos uma
tabela ASCII. Isso cobre 0 - 255, ou, de 0h a FFh.

LIO 2 - O conjunto de instrues do 8086:


---------------------------------------------Okay, ento voc j aprendeu sobre registradores, mas, como us-los,
e como se codifica em Assembler? Bem, primeiro voc precisa de algumas
instrues. As seguintes instrues podem ser usadas em todas as CPU's
do 8086 para cima.
MOV <dest>, <valor> - MOVE. Esta instruo permite MOVER um valor
para uma posio na memria.
Ex.: MOV AX, 13h
Isso deveria mover 13h (19 em decimal) para o
registrador AX. Logo, se AX valia antes 0, ele
agora seria 13h.
ISSO APENAS MOVE UM VALOR PARA UM REGISTRADOR,
NO FAZ NADA MAIS.
Ex.: (Em Pascal) AX := $13;
INT <nmero>
- INTERRUPO. Esta instruo gera uma interupo.
Voc pode pensar nisso como sendo quase uma
procedure.
Ex.: INT 10h
Geraria a interrupo 10h (16 em decimal). Agora,
o que isso faria depende do contedo do registrador
AH, entre outras coisas. Por exemplo, se AX = 13h
e a interrupo 10h foi gerada, o vdeo seria

colocado no modo 320x200x256.


Mais precisamente:
AH seria igual a 00 - seleciona a subfuno do
modo, e
AL seria igual a 13h - modo grfico 320x200x256.
Contudo, se AH = 2h, e a interrupo 16h foi
gerada, isso instruiria a CPU para checar se
alguma tecla pressionada est no buffer do
teclado.
Se AH = 2h, e BH = 0h e a interrupo 10h foi
gerada, ento a CPU moveria o cursor para a
posio X em DL e posio Y DH.
NO SE PREOCUPE COM ISSO POR ENQUANTO! NS
FALAREMOS NISSO MAIS TARDE, COM MAIS DETALHES.
ADD <dest> <valor> - ADICIONA. Esta instruo soma um nmero ao valor
armazenado em dest.
Ex: MOV AX, 0h ; AX agora igual a 0h
ADD AX, 5h ; AX agora igual a 5h
ADD AX, 10h ; AX agora igual a 15h
SUB <dest> <valor> - SUBTRAI. Acho que d pra voc adivinhar o que isso
faz.
Ex: MOV AX, 13h ; AX agora igual a 13h (19 dec)
SUB AX, 5h ; AX agora igual a 0Eh (14 dec)
DEC <registrador> - DECREMENTA algo.
Ex: MOV AX, 13h ; AX agora igual a 13h
DEC AX
; AX agora igual a 12h
INC <registrador> - INCREMENTA algo.
Ex: MOV AX, 13h ; Adivinha...
INC AX
; AX = AX + 1
JMP <posio>

- PULA para uma posio.

EG: JMP 020Ah ; Pula para a instruo em 020Ah


JMP @MyLabel ; Pula para @MyLabel.
NO SE PREOCUPE SE ISTO UM POUCO CONFUSO - VAI
FICAR PIOR! H OUTRAS 28 INSTRUES JUMP PARA
APRENDER, TALVEZ MAIS. FALAREMOS NELAS MAIS TARDE.
CALL <procedimento> - CHAMA uma subfuno.
EG: Procedure MyProc;
Begin { MyProc }
{ ... }
End; { MyProc }
Begin { Main }
Asm
CALL MyProc ; Adivinha o que isso faz!
End;
End.
Ou: CALL F6E0h ; Chama subfuno em F6E0h
LOOP <rtulo/label> - Faz LOOPS (repetio) durante um certo tempo.
EG: MOV CX, 10h ; Isto o porque de CX ser
; chamado de registro CONTADOR.
; 10h = 16
@MyLabel:
; alguma coisa
; mais coisa
LOOP @MyLabel ; At que CX = 0
; Note: CX decrementado
; a cada vez. No decremente-o
; voc mesmo (DEC CX).
; ISSO DEVERIA SE REPETIR 16 vezes - i.e., 10
em hexadecimal.
LODSB
LODSW

- Carrega um byte
- Carrega uma word

STOSB
STOSW

- Armazena um byte
- Armazena uma word

Estas instrues so usadas para pr ou conseguir algo numa posio na


memria. O registrador DS:SI, (lembra que ns falamos sobre isso antes,
sobre SI ser o ndice de fonte?), aponta para a localizao de onde
queremos obter os dados, e DS:DI aponta para onde colocaremos informaes.
claro, no somos obrigados a usar DS - poderia ser ES por exemplo.
Meu procedimento PutPixel colocar um byte em ES:DI.
De qualquer modo, imagine que temos a seguinte configurao na memria:
Posio na memria 06 07 08 09 10 11 12
Valor

50 32 38 03 23 01 12

Quando ns usamos LODSB ou STOSB, ele retorna ou pega um nmero de AL.


Assim, se DS:SI apontava para 07 e executssemos uma instruo LODSB,
AL seria agora igual a 32.
Agora, se ns apontssemos DS:DI para 11, colocando, diria, 50 no
registrador AL, e executasse STOSB, ento teramos o seguinte resultado:
Posio na Memria 06 07 08 09 10 11 12
Valor

50 32 38 03 23 50 12

OBS.: Quando usamos LODSB/STOSB, usamos AL. Isto porque estaremos


mexendo com um nmero de 8 bits (um byte), apenas. Podemos
armazenar um nmero de 8 bits em AL, AH, ou AX, mas no podemos
armazenar um nmero de 16 bits em AH ou AL porque eles so
REGISTRADORES DE 8 BITS.
Como resultado, quando usarmos LODSW ou STOSW, ns devemos usar
AX e no AL, j que estaremos pegando/colocando um nmero de
16 bits.
MOVSB - Move um byte
MOVSW - Move uma word
Como exemplo vamos pegar um byte de DS:SI e mand-lo para ES:DI.
Em DS:SI:

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

Se apontarmos DS:SI para a posio 07, apontarmos ES:SI para a posio


11 e executarmos MOVSB, o resultado em ES:DI pareceria com:
Em ES:DI:
Posio de Memria 06 07 08 09 10 11 12
Valor

10 11 20 02 67 32 12

ESPERO QUE VOC PEGUE A IDIA GERAL. CONTUDO, CLARO, NO


TO SIMPLES. POSIES DE MEMRIA NO SO ARRUMADOAS EM FORMA
DE ARRAY, EMBORA EU DESEJASSE MUITO QUE FOSSEM. QUANDO FOR
MOVER/PEGAR/COLOCAR, VOCE ESTAR MEXENDO COM UMA POSIO TAL
COMO: 100:102H. AINDA ASSIM, VOC DEVERIA PEGAR A IDIA.
REP - REPETE o nmero de vezes especificado no registrador CX.
Um REP na frente de um MOVSB/LODSB/STOSB causaria a repetio da
instruo. Logo:
Se CX = 5, e
se ES:DI apontava para 1000:1000h,
ento REP STOSB armazenaria o que estava no registrador AL na
posio 1000:1000h 5 vezes.
COISAS A FAZER:
1) Memorizar todas as instrues acima - no to difcil assim e no h tantas
l.
2) Tenha certeza que voc entendeu a teoria por trs delas.

LIO 3 - Segmentos e Offsets


--------------------------------Antes de explorarmos o grande e mau mundo dos segmentos e offsets, h umas
terminologias que voc precisar conhecer.
O BIT - a menor parte de dados que podemos usar. Um bit - um oitavo de
um byte pode ser ou um 1 ou um 0. Usando esses dois dgitos podemos
fazer nmeros em BINRIO ou BASE 2.
EX.

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

Neste caso, ambos AH e AL = 0, OU 00h e 00h. Como resultado, para calcular


AX usamos: AX = 00h + 00h. Quando digo + eu quero dizer, 'ponha junto' no
AX = AH MAIS AL.
Assim, se AH era igual a 00000011 e AL era igual a 0000100, para calcular
AX ns devemos fazer o seguinte.
1) Pegue os valores hexadecimais de AH e AL.
00000011 = 03h 00010000 = 10h
2) Combine-os.
AX = AH + AL
AX = 03h + 10h
AX = 0310h
E a voc consegue o resultado. No to macetoso assim.

Okay, agora vamos ver os registradores de 16 bits:


AX

AH

AL

00000000

00000000

De onde podemos ver que AX = 00000000 e 00000000, ou 0000000000000000.


Agora por ltimo, vejamos como um registrador de 32 bits se parece:

EAX
AX

00000000 00000000 00000000


AH AL

00000000

No muito difcil, espero. E se entendeu isso, voc est pronto para


SEGMENTOS e OFFSETS.
Uma Arquitetura Segmentada
----------------------------H muito, muito tempo atrs, quando a IBM construiu o primeiro PC, no era
costume programas terem mais de 1 megabyte - eca, os primeiros XT's tinham
apenas 64K de RAM! De qualquer modo, vendo que os projetistas do XT no
consideravam aplicaes enormes, decidiram dividir a memria em SEGMENTOS,
pequenas reas de memria RAM que voc pode colocar APENAS uma tela virtual
para grficos em modo 320x200x256.
claro, voc pode acessar mais de um megabyte de RAM, mas voc tem que
dividi-la em segmentos para us-la, e esse o problema. obvio, com
programao em 32 bits d pra acessas at 4GB de RAM sem usar segmentos, mas
isso uma outra histria.
Segmentos e offsets so apenas um mtodo de especificar uma posio na memria.

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

- Obs.: Quando em modo protegido, voc pode usar


qualquer registrador de uso geral como
um registrador de offset - EXCETO IP.

Alguns segmentos e offsets comuns so:


CS:IP - Endereo do cdigo executando no momento.
SS:SP - Endereo da posio atual da pilha.
OBS.: NO SE INTROMETA COM ELES !
Assim quando nos referirmos a segmentos e offsets, faremos dessa forma:
SEGMENTO:OFFSET
Um bom exemplo seria:
A000:0000 - que na verdade corresponde ao topo esquerdo da tela VGA em
modo colorido 320x200x256.
** FATO ENGRAADO ** A RAM da VGA comea em A000h :)
Ufa! Isso foi muito para o segundo tutorial. Contudo, ainda no terminamos.
Esse negcio de AX, AH, AL um conceito que voc pode no ter sacado ainda,
ento l vamos ns:

MOV AX, 0
MOV AL, 0
MOV AH, 0

; AX = 0
; AL = 0
; AH = 0

MOV AL, FFh ; AL = FFh


; AX = 00FFh
; AH = 00h
INC AX

; AX = AX + 1
; AX = 0100h
; AH = 01h
; AL = 00h

MOV AH, ABh ; AX = AB00h


; AH = ABh
; 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

<<< Quando colocamos um byte na pilha, ele vai


aqui - ltimo a entrar, primeiro a sair.

SP

<<< O ponteiro de pilha se move para baixo.

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

Voc acabou de aprender duas instrues:


PUSH <registrador> - PUSH (coloca algo na pilha), e
POP <registrador>

- POP (retira ele de volta).

tudo o que voc precisa de aprender sobre pilha - por enquanto.

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.

Procedure ClearScreen(A : Byte; Ch : Char); Assembler;


Asm { ClearScreen }
mov ax, 0B800h
mov es, ax
xor di, di
mov cx, 2000
mov ah, A
mov al, &Ch
rep stosw
End; { ClearScreen }
Procedure CursorXY(X, Y : Word); Assembler;
Asm { CursorXY }
mov ax, Y
mov dh, al
dec dh
mov ax, X
mov dl, al
dec dl
mov ah, 2
xor bh, bh
int 10h
End; { CursorXY }
Procedure PutPixel(X, Y : Integer; C : Byte; Adr : Word); Assembler;
Asm { PutPixel }
mov ax, [Adr]
mov es, ax
mov bx, [X]
mov dx, [Y]
xchg dh, dl
mov al, [C]
mov di, dx
shr di, 2
add di, dx
add di, bx
stosb
End; { PutPixel }

Procedure Delay(ms : Word); Assembler;


Asm { Delay }
mov ax, 1000
mul ms
mov cx, dx
mov dx, ax
mov ah, 86h
int 15h
End; { Delay }
COISAS A FAZER:
1) V ao exemplo de pilha. Faa seu prprio cdigo exemplo.
2) Comente as procedures acima do melhor modo que puder. Tente adivinhar o que
as novas intrues fazem. No to difcil.

Durante este tutorial, voc achar os livros "Peter Norton's Guide to


Assembler", "Peter Norton's Guide to the VGA Card", ou qualquer
um dos livros "Peter Norton's Guide to..." muito teis. Voc no pode
programar em Assembler sem saber pra que so todas as interrupes
e o que so todas as subfunes.
Eu lhe recomendo conseguir uma cpia desses livros assim que possvel.
Um Programa Assembly
----------------------Eu geralmente no escrevo cdigo 100% em Assembly. muito mais conveniente
usar uma linguagem de alto nvel como C ou Pascal, e usar Assembly para
acelerar os bits lentos. Contudo, voc pode querer se torturar e escrever uma
aplicao completamente em Assembly, ento aqui vai a configurao bsica:
DOSSEG

- diz CPU como organizar o segmento

MODEL

- declara o modelo que vamos usar

STACK

- quanta pilha vamos alocar?

DATA

- o qu vai no segmento de dados

CODE

- o qu vai no segmento de cdigo

START

- o incio do seu cdigo

END START - o fim do seu cdigo

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

- isto ordena os segmentos na ordem:

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

- cdigo, dados e arrays podem ter mais de 64K.

STACK
- isso instrui ao PC para arrumar uma pilha to grande quanto
for especificado.
DATA

- permite a voc criar um segmento de dados. Por exemplo:


MySegment SEGMENT PARA PUBLIC 'DATA'
; Declare alguns bytes, words, etc.
MySegment ENDS
Isso similar a CONSTANTES in Pascal.

CODE

- permite a voc criar um segmento de cdigo. Ex.:


MyCodeSegment SEGMENT PARA PUBLIC 'CODE'
; Declare algo
MyCodeSegment ENDS

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

DB "Abaixo o segmento de dados!$"

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

- pula (jump) se AX foi MAIOR que BX;


- pula se AX foi MAIOR ou IGUAL a BX;
- pula se AX foi MENOR que BX;
- pula se AX foi MENOR ou IGUAL a BX;
- pula se AX foi NO MAIOR que BX;
- pula se AX foi NO MAIOR ou IGUAL a BX;
- pula se AX foi NO MENOR que BX;
- pula se AX foi NO MENOR ou IGUAL a BX;
- pula se o flag de ZERO est setado - o mesmo que JE;
- pula se AX for IGUAL a BX;
- pula se o flag de ZERO NO est setado - o mesmo que JNE;
- pula se AX NO for IGUAL a BX;

COMPARAES COM SINAL:


----------------------- JG
- pula (jump) se AX foi MAIOR que BX;
JGE - pula se AX foi MAIOR ou IGUAL a BX;
JL - pula se AX foi MENOR que BX;
JLE - pula se AX foi MENOR ou IGUAL a BX
JNG - pula se AX foi NO MAIOR que BX
JNGE - pula se AX foi NO MAIOR ou IGUAL a BX;
JNL - pula se AX foi NO MENOR que BX;
JNLE - pula se AX foi NO MENOR ou IGUAL a BX;
JZ
- pula se o flag de ZERO est setado - o mesmo que JE;
JE
- pula se AX for IGUAL a BX;
JNZ - pula se o flag de ZERO NO est setado - o mesmo que JNE;
JNE - pula se AX NO for IGUAL a BX;

NO TO COMUNS:
---------------- JC
JNC
JO
JNO
JP
JNP
JPE
JPO
JS
JNS

- pula se o flag de CARRY est setado;


- pula se o flag de CARRY NO est setado;
- pula se o flag de OVERFLOW est setado;
- pula se o flag de OVERFLOW NO est setado;
- pula se o flag de PARIDADE est setado;
- pula se o flag de PARIDADE NO est setado;
- pula se a PARIDADE for PAR - o mesmo que JP;
- pula se a PARIDADE for MPAR - o mesmo que JNP;
- pula se o flag de SINAL NO est setado;
- pula se o flag de SINAL est setado.

De qualquer modo, aqui est com o que eles se parecem:


Flag SF ZF -- AF -- PF -- CF
Bit 07 06 05 04 03 02 01 00
Legenda:
---------SF - Flag de Sinal;
ZF - Flag de Zero;
AF - Flag Auxiliar;
PF - Flag de Paridade.
CF - Flag de Carry (vai um).
Nota: H MUITO MAIS FLAGS PARA APRENDER. Eles sero vistos num Tutorial
mais
frente.

COISAS PARA FAZER:


1) Volte ao frame da configurao bsica de Assembler e memorize-o.
2) Tenter escreer um programa simples que mostre alguns comentrios
criativos.
3) Aprenda as instrues JUMP menos criptogrficos de cor.

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

Procedure PutPixel(X, Y : Integer; C : Byte; Adr : Word); Assembler;


Asm { PutPixel }
mov ax, [Adr]
{ Move o endereo do VGA em AX
mov es, ax
{ Joga AX em ES
}
mov bx, [X]
{ Move o valor de X para BX
}
mov dx, [Y]
{ Move o valor de Y para DX
}
xchg dh, dl
{ Daqui pra frente calcula o
}
mov al, [C]
{ offset do pixel a ser plotado
}
mov di, dx
{ e pe este valor em DI. Vamos
}
shr di, 2
{ ver isso mais tarde - prximo tutorial}
add di, dx
{ quando falarmos sobre shifts
}
add di, bx
{ versus muls
}
stosb
{ Guarda o byte em ES:DI
}
End; { PutPixel }

NOTA: Eu estaria muito interessado em achar uma procedure PutPixel mais


rpida que essa. Eu j vi uma inline que faz isso em metade do tempo,
mas mesmo assim, essa muito quente.
{ Esta procedure uma funo de delay independente de CPU }
Procedure Delay(ms : Word); Assembler;
Asm { Delay }
mov ax, 1000
mul ms
mov cx, dx
mov dx, ax
mov ah, 86h
int 15h

{ Move o nmero de ms em um segundo para AX }


{ Faz AX = nmero de ms a esperar
}
{ Prepara para o delay - pe nmero de ms }
{ onde necessrio
}
{ Cria o delay
}

End;

{ Delay }

Vamos falar um pouco sobre o VGA. Isso basicamente para onde


eu tenho conduzido no meu modo desconjuntado, de qualquer modo, j que
programao grfica no s recompensante, divertido tambm! Bem, eu
acho que .
Primeiramente porm, devemos terminar aquela coisa de CMP/JMP, e falar de
shifts. Quando se est programando em Assembler, a gente acha que comparaes,
shifts e testar bits so operaes muito comuns.

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

; Novo modo de dizer:


; DS -> SEG segmento de dados

KeepOnGoing:
MOV AH, 9
MOV DX, OFFSET FirstString ; DX -> OFFSET FirstString
INT 21h
; Escreve a primeira mensagem
MOV AH, 0

; Pega uma tecla - armazena-a em AX

INT 16h

PUSH AX
MOV DL, AL
MOV AH, 2
INT 21h
POP AX
CMP AL, "Y"
JNE HatesTute

; AL - cdigo ASCII, AH - "scan code"


; Ela no ecoa na tela, contudo,
; ns mesmos temos que fazer isso.
; Aqui ns mostramos na tela o caracter
; note que ns salvamos AX. Obviamente,
; usando-se AH para imprimir uma string
; destri-se AX
; Checa se foi teclado 'Y'
; Se foi, continua

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

; Certifica que foi teclado 'N'


; Infelizmente, sim.

MOV DX, OFFSET FourthString ; Pede ao usurio para tentar de novo


MOV AH, 9
INT 21h
JMP KeepOnGoing
; Deixa ele tentar
DontLikeYou:
MOV DX, OFFSET SecondString
MOV AH, 9
INT 21h
MOV DX, OFFSET ExitString
MOV AH, 9
INT 21h
MOV AX, 4C00h
INT 21h
END START

; Mostra a string "NO? NO? O que..."

; Mostra a string "Bem, deixa pra l!"

; Volta para o DOS

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.

CONVERTENDO DE DECIMAL PARA BINRIO:


Isso provavelmente mais fcil que da base-2 para base-10. Para calcular
o que 321 seria em binrio, voc faria o seguinte:
321
= 256 X 1
321 - 256 = 65
= 128 X 0
65
= 64 X 1
65 - 64 = 1
= 32 X 0
1
= 16 X 0
1
= 8 X 0
1
= 4 X 0
1
= 2 X 0
1
= 1 X 1
E voc obteria o nmero binrio - 101000001. Fcil, n? Vamos tentar outro
para ter certeza que sabemos fazer:
198
= 128 X 1
198 - 128 = 70
= 64 X 1
70 - 64 = 6
= 32 X 0
6
= 16 X 0
6
= 8 X0
6
= 4 X1

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=

Logo B800h = 45056 + 2048 + 0 + 0


= 47104
Obs.: Para nmeros em hexadecimal maiores que FFFFh (65535 em
decimal), voc somente segue o mesmo procedimento como para
binrio, logo, para o quinto dgito hexadecimal, voc
multiplicaria por 65535.
Tecle 16 X X na sua calculadora, e fique apertando =.
Voc ver os nmeros que precisaria usar. O mesmo aplica-se
para binrio. Ex.: 2 X X e = lhe daria 1, 2, 4, 8, 16...
etc.
Vamos dar uma olhada em:
CONVERTENDO DE DECIMAL PARA HEXADECIMAL:
Mais uma vez, o mesmo tipo de procedimento como usamos para binrio. Logo,
para converter 32753 para hexadecimal, voc faria assim:
32753 / 4096

= 7 (decimal) = 7h

32753 - (4096 x 7) = 4081


4081 / 256

= 15 (decimal) = Fh

4081 - (256 x 15) = 241


241 / 16
241 - (16 x 15)
1/1

= 15 (decimal) = Fh
=1
= 1 (decimal) = 1h

Assim, eventualmente temos 7FF1h como resposta. Este no particularmente


um bom processo e requer alguma explicao.
1) Quando voc divide 32753 por 4096 voc consegue 7.9963379... No estamos
interessados no lixo .9963379, s pegamos o 7, j que 7 o maior
nmero inteiro que podemos usar.

2) O resto da operao acima 4081. Devemos agora realizar a mesma


operao nisso, mas com 256. Dividindo 4081 por 256 nos d
15.941406... Novamente, pegamos s o 15.
3) Agora temos um resto de 241. Dividindo isto por 16 nos d 15.0625.
Pegamos o 15, e calculamos o resto.
4) Nosso ltimo resto acontece que um. Dividindo isso por um chegamos a,
voc advinhou - um. VOC NO DEVERIA CONSEGUIR UMA RESPOSTA COM
MUITAS CASAS DECIMAIS AQUI. SE VOC TEM - VOC CALCULOU ERRADO.
um processo muito imundo, mas funciona.
OK, agora que j lidamos com os clculos horripilantes, Voc j est pronto
para os shifts.
- SHIFTS:
H geralmente 2 formas da instruo shift - SHL (shift left/esquerda) e SHR (shift
right/direita). Basicamente, tudo o que essas instrues fazem deslocar uma expresso
para a esquerda ou direita um certo nmero de bits. Sua principal vantagem a habilidade
de lhe deixar
substituir multiplicaes lentas com shifts mais rpidos. Voc vai achar que
isso acelerar pra caramba os algoritmos de pixel/linhas/crculo.
Os PC's esto ficando cada vez mais rpidos a cada dia. De volta aos dias do XT - a
multiplicao era realmente lenta - talvez levando at 4 segundos para certas operaes.
Hoje
em dia isso no acontece assim, mas uma boa idia otimizar seu cdigo.
Quando ns plotamos um pixel na tela, temos que encontar o offset do pixel
a plotar. Basicamente, o que fazemos multiplicar a posio Y por 320, somar
a posio X, e somar isso ao endereo A000h.
Assim basicamente, temos: A000:Yx320+X
Agora, seja l quo rpido seu maravilhoso 486 ou Pentium ,
isso poderia se feito um pouco mais rpido. Vamos reescrever aquela equao
acima, assim, vamos usar alguns nmeros diferentes:
8
6
Offset = Y x 2 + Y x 2 + X
Ou:
Offset = Y x 256 + y x 64 + X

Reconhece esses nmeros? Eles parecem terrivelmente com aqueles


que ns vimos naquela tabela de converso binrio-decimal.
Contudo, ns ainda estamos usando multiplicao.
Como podemos incorporar shifts?
Que tal:
Offset = Y SHL 8 + Y SHL 6 + X
Agora, isso _muito_ mais rpido, j que tudo o que o computador tem que
fazer um shift esquerda com o nmero - muito melhor.
Note que o shift esquerda AUMENTA o nmero, e o shift direita
DIMINUI o nmero.
Aqui est um exemplo que pode te ajudar se voc ainda est em dvida no que
est acontecendo. Digamos que estamos trabalhando em base-10 - decimal. Agora
Peguemos o nmero 36 como exemplo. "Shiftando" este nmero esquerda de 1,
temos:
36 + 36

= 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

Agora vamos deslocar o nmero 45 DIREITA de 2 unidades. Em binrio isso


101101. De onde:
101101
SHR 2 ---->
1011
Notou o que ocorreu? muito mais fcil para a CPU apenas mover alguns bits
(aproximadamente 2 unidades de clock), do que multiplicar um nmero. (Pode
demorar at 133 unidades de clock).
Ns vamos usar bastante shifts quando estivermos programando a VGA, assim,
tenha certeza de que voc entendeu os conceitos por trs disso.
PROGRAMANDO A VGA EM ASSEMBLER
Quando ns falamos sobre programar a VGA, ns estamos geralmente falando do
modo 13h, ou um de seus parentes. Em VGA padro este o _nico_ modo
de usar 256 cores, e provavelmente um dos modos mais fceis tambm.
Se voc j tentou experincias com a SVGA, voc vai entender o pesadelo
que para o programador dar suporte a todas as diferentes placas SVGA que
existem - exceto se voc usar VESA que o que discutiremos outra hora. A
grande vantagem do modo padro 13h que voc sabe que todas as placas VGA
que existem vo suport-lo. As pessoas hoje frequentemente ignoram o modo 13h,
achando a resoluo muito granulada para os padres de hoje, mas no se esquea
que Duke Nukem, DOOM, DOOM II, Halloween Harry e a maioria dos jogos da Apogee
usam este modo para realizar alguns grandes efeitos.
A grande coisa sobre o modo 13h - isto 320x200x256 caso voc desconhea,
que acessar a VGA RAM incrivelmente fcil. Como 320 x 200 igual a
64,000, possvel encaixar a tela inteira em um segmento de 64K.
As ms notcias so que o modo padro 13h realmente s te d uma pgina para
usar, seriamente embaraante para scroll e page-flipping. ns vamos cobrir
mais tarde estes assuntos, como entrar em seus prprios modos - e modo X que
evitar esses problemas.
Ento, como entrar no modo padro 13h?
A resposta simples. Usamos a interrupo 10h - interrupo de vdeo,
e chamamos a subfuno 00h - seleciona o modo. Em Pascal, voc poderia
declarar uma procedure como esta:

Procedure Init300x200; Assembler;


Asm { Init300x200 }
mov ah, 00h
{ Acerta o modo de vdeo }
mov al, 13h
{ Usa o modo 13h
}
int 10h
{ Faz isso
}
End; { Init300x200 }
voc tambm pode ver:
mov ax, 13h
int 10h
Isso perfeitamente correto, e provavelmente economiza um tempo de clock por
no colocar 00h em AH e ento 13h em AL, mas mais correto usar o primeiro
exemplo.
OK, ento estamos no modo 13h, mas o que podemos realmente fazer nele, alm
de olhar para uma tela em branco? Poderamos voltar ao modo texto usando:
mov ah, 00h
mov al, 03h
int 10h
Mas isso um pouco idiota. Porque no pintar um pixel?
H inmeros modos de colocar um pixel na tela. O modo mais fcil em Assembler
usar interrupes. Voc faria mais ou menos assim em Pascal:
Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;
Asm { PutPixel }
mov ah, 0Ch
{ subfuno de desenhar pixel }
mov al, [Color] { Move a cor a plotar para AL }
mov cx, [X]
{ Move o valor X para CX
}
mov dx, [Y]
{ Move o valor Y para DX
}
mov bx, 1h
{ BX = 1, pgina 1
}
int 10h
{ Plota
}
End; { PutPixel }
Contudo, mesmo isso sendo em Assembler, no particularmente rpido. Por qu?,
voc pergunta. Porque isso usa interrupo. Interrupes so timas para entar

e sair de modos de vdeo, ligar e desligar o cursor, etc... mas no para


grficos.
Voc pode imaginar interrupes como uma secretria eletrnica. "A CPU est
ocupada neste momento, mas se voc deixar sua subfuno aps o sinal - ns
entraremos em contato."
No bom. Vamos usar a tcnica que discutimos anteriormente durante shifts.
O que queremos fazer botar o valor da cor que desejamor plotar na VGA
diretamente. Para fazer isso, precisamos mover o endereo da VGA para ES,
e calcular o offset do pixel que queremos plotar. Um exemplo disso mostrado
abaixo:
Procedure PutPixel(X, Y : Integer; Color : Byte); Assembler;
Asm { PutPixel }
mov ax, 0A000h { Move o segmento da VGA para AX,
}
mov es, ax
{ e agora para ES
}
mov bx, [X]
{ Move o valor X para BX
}
mov dx, [Y]
{ Move o valor Y para DX
}
mov di, bx
{ Move X para DI
}
mov bx, dx
{ Move Y para BX
}
shl dx, 8
{ Nesta parte usamos shifts para multiplicar }
shl bx, 6
{ Y por 320
}
add dx, bx
{ Agora somamos X ao valor acima calculado, }
add di, dx
{ dando DI = Y x 320 + X
}
mov al, [Color] { Pe a cor a plotar em AL
}
stosb
{ Pe o byte, AL, em ES:DI
}
End; { PutPixel }
Esta procedure rpida o suficiente para comear, embora eu tenha dado uma
muito mais rpida uns tutoriais atrs que usa uma tcnica genial para pegar
DI.

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.

2) Tenha certeza que entendeu aquela coisa de binrio -> decimal,


decimal -> binrio, decimal -> hex e hex -> decimal. Faa voc mesmo
alguns exemplos de soma e teste suas respostas com a calculadora do
Windows.
3) voc _deve_ entender shifts. Se voc ainda tem problemas, faa algumas
expresses num papel e teste suas respostas num programa como:
Begin { Main }
WriteLn(45 SHL 6);
ReadLn;
End. { Main }
e/ou a calculadora do Windows.
4) D uma olhada na parte de VGA, e certifique-se de ter pego a teoria, porque na
prxima semana vamos entrar a fundo nisso.

Note que eu inclu um demo starfield neste tutorial dessa semana.


Voc pode rodar STARS.EXE, ou olhar STARS.PAS que o cdigo fonte.
s um simples demo, mas pode ser usado para fazer alguns efeitos
interessantes.
Agora, esta semana vamos inicialmente listar um sumrio de todas as instrues
que voc j deveria ter aprendido at agora, e umas novas. Ento vamos ver
como a VGA arrumada, e cobrir uma rotina simples de linha.

SUMRIO DO CONJUNTO DE INSTRUES

ADC <DEST>, <FONTE>


- Nome: Soma com Vai-Um
Tipo: 8086+
Descrio: Esta instruo soma <FONTE>
e <DEST> e soma o valor armazenado no
flag de vai-um, que ser um ou zero,

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+

- Nome: Testa Bit

Descrio: Esta instruo testa


<BIT NUMBER> de <DEST> que pode
ser um registrador de 16 ou 32 bits ou
posio da memria. Se <DEST> um
nmero de 16 bits ento o <BIT NUMBER> pode
ser de 0 a 15, seno, se <DEST> um
nmero de 32 bits, ento o <BIT NUMBER>
pode ter um valor de 0 a 31.
O valor em <BIT NUMBER> de <DEST>
ento copiado para o flag de vai-um.

EX.: BT AX, 3
JC EraIgualAUm
CALL <DEST>

- Nome: Chama Procedimento


Tipo: 8086+
Descrio: Esta instruo simplesmente
chama uma subrotina. Em termos mais
tcnicos, ela pe o endereo da prxima
instruo, IP, na pilha, e seta IP, o
registro de instrues, para o valor
especificado por <DEST>.
EX.: CALL MyProc

CBW

- Nome: Converte Byte para Word


Tipo: 8086+
Descrio: Esta instruo extende o
byte em AL para AX.
EX.: MOV AL, 01h
CBW
ADD BX, AX ; Faz algo com AX

CLC

- Nome: Limpa Flag de Vai-Um


Tipo: 8086+
Descrio: Esta instruo zera o flag
de vai-um no registrador de flags.
EX.: CLC

CLD

- Nome: Limpa Flag de Direo


Tipo: 8086+
Descrio: Esta instruo limpa o
flag de direo no registrador de flags
para 0. Quando o flag de direo 0,
qualquer instruo de strings incrementa
os registradores de ndice SI e DI.

EX.: CLD
CLI

- Nome: limpa Flag de Interrupo


Tipo: 8086+
Descrio: Esta instruo limpa o flag de
interrupo no registrador de flags para
0, assim desabilitando interrupes de
hardware.
EX.: CLI

CMC

- Nome: Complementa o Flag de Vai-Um


Tipo: 8086+
Descrio: Esta instruo checa o valor
atual no flag de vai-um.
Se for 0 - transforma em 1 e se for
1 - passa a ser 0.
EX.: BT AX, 1
JC EraUm
JMP Fim
EraUm:
CMC

; Testa o bit 1 de AX

; Retorna CF para 0

Fim:
CMP <VALOR1>, <VALOR2>
Tipo: 8086+

- Nome: Comparao Inteira

Descrio: Esta instruo compara


<VALOR1> e <VALOR2> e reflete a comparao
nos flags.
EX.: CMP AX, BX
Veja tambm as intrues Jcc.
CWD

- Nome: Converte Word para Doubleword


Tipo: 8086+

Descrio: Esta instruo extende a


word em AX para o par DX:AX.
EX.: CWD
DEC <VALOR>

- Nome: Decrementa
Tipo: 8086+
Descrio: Esta instruo subtrai
um do valor em <VALOR> e armazena
o resultado em <VALOR>.
EX.: DEC AX

DIV <VALOR>

- Nome: Diviso sem Sinal


Tipo: 8086+
Descrio: Esta instruo divide
<VALOR> por, ou AX para byte, DX:AX para
word ou EDX:EAX para doubleword.
Para byte, o quociente retornado em
AL e o resto em AH, para word o
quociente retornado em AX e o
resto em DX e para DWORD, o
quociente volta em EAX e o
resto em EDX.
EX.: MOV AX, 12
MOV BH, 5
DIV BH
MOV Quociente, AL
MOV Resto, AH

IN <ACUMULADOR>, <PORTA>
Tipo: 8086+

- Nome: Entrada de porta de E/S

Descrio: Esta instruo l um valor


de uma das 65536 portas de hardware
especificada no <ACUMULADOR>.
AX e AL so comumente usados para portas

de entrada, e DX comumente usado para


identificar a porta.
EX.: IN AX, 72h
MOV DX, 3C7h
IN AL, DX
INC <VALOR>

- 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: Pula (Jump) se Condio


Tipo: 8086+

No vou repetir eu mesmo todos os 32, d uma olhada no Tutorial Trs


a lista completa deles. Tenha em mente que seria uma boa idia chamar
CMP, OR, DEC ou algo semelhante antes de usar uma dessas instrues. :)
EX.: DEC AX
JZ AX_Chegou_A_Zero
JMP <DEST>

- 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

- Nome: Carega AH com Flags


Tipo: 8086+
Descrio: Esta instruo copia os
bytes mais baixos do registrador de flags
para AH. O contedo de AH vai parecer
com algo semeelhante depois que
a instruo for executada:

Flag SF ZF

AF

PF

CF

Bit 07 06 05

04 03 02 01 00

Voc pode agora testar os bits individualmente, ou realizar


uma instruo similar seguinte para pegar um flag apenas:
EG: LAHF
SHR AH, 6
AND AH, 1 ; AH contm o flag ZF.
LEA <DEST>, <FONTE>
- Nome: Carrega Endereo Efetivo
Tipo: 8086+
Descrio: Esta instruo carrega o
endreo de memria que <FONTE> significa,
em <DEST>.
EX.: eu uso LEA SI, Str numa das minhas
procedures que pes uma string na
tela bem rpido.
LOOP <LABEL>

- Nome: Decrementa CX e Repete

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

EX.: LES SI, Um_Ponteiro


MOV <DEST>, <FONTE>
Tipo: 8086+

- Nome: Move Dados

Descrio: Esta instruo copia

<FONTE> em <DEST>.
EX.: MOV AX, 3Eh
MOV SI, 12h
MUL <FONTE>

- Nome: Multiplicao sem Sinal


Tipo: 8086+
Descrio: Esta instruo multiplica
<FONTE> pelo acumulador, que depende
do tamanho de <FONTE>.
Se <FONTE> um byte ento:
* AL o multiplicando;
* AX o produto.
Se <FONTE> uma word ento:
* AX o multiplicando;
* DX:AX o produto.
Se <FONTE> uma doubleword ento:
* EAX o multiplicando;
* EDX:EAX o produto.
OBS.: Os flags so inalterados
excetos para OF e CF, que so
zerados se o byte alto, word ou
dword do produto for 0.
EX.: MOV AL, 3
MUL 10
MOV Resultado, AX

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

- Nome: Complemento Lgico


Tipo: 8086+
Descrio: Esta instruo inverte o
estado de cada bit no operando.
EX.: NOT CX

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+

- Nome: Sada para a Porta

Descrio: Esta instruo manda para a


sada o valor do acumulador para <PORTA>.
Usando o registrador DX para pasar a porta
OUT, voc pode acessar 65,536 portas.
EX.: MOV DX, 378h
OUT DX, AX
POP <REGISTRADOR>
- Nome: Carega Registrador da pilha
Tipo: 8086+
Descrio: Esta instruo pega o valor
atual do topo da pilha e coloca no
<REGISTRADOR>.
EX.: POP AX

POPA

- Nome: Pega Todos os Registradores Gerais


Tipo: 80186+
Descrio: Esta instruo pega todos os
registradores de uso geral de 16 bits
da pilha, exceto SP.
o mesmo que:
POP AX
POP BX
POP CX
...
EX.: POPA

POPF

- Nome: Pega o valor do topo para Flags


Tipo: 8086+
Descrio: Esta instruo pega o
byte baixo dos flags da pilha.
EX.: POPF

PUSH <REGISTRADOR>
Tipo: 8086+

- Nome: Pe na Pilha o Registrador

Descrio: Esta instruo pe


<REGISTRADOR> na pilha.
EX.: PUSH AX
PUSHA

- Nome: Pe todos os registradores na pilha


Tipo: 80186+
Descrio: Esta instruo pe todos os
registradores de uso geral de 16 bits
na pilha.
o mesmo que:

PUSH AX
PUSH BX
PUSH CX
...
EX.: PUSHA
PUSHF

- Nome: Pe flags na pilha


Tipo: 8086+
Descrio: Esta instruo pe o
byte baixo dos flags na pilha.
EX.: PUSHF

REP

- Nome: Sufixo de Repetio


Tipo: 8086+
Descrio: Esta instruo repetir
a intruo seguinte o nmero de vezes
especificado em CX.
EX.: MOV CX, 6
REP STOSB ; Armazena 6 bytes

RET

- Nome: Retorno de Subrotina (Near/prximo)


Tipo: 8086+
Descrio: Esta instruo retorna IP
ao valor que ele tinha antes da ltima
instruo CALL. RET, ou RETF para um
jump distante (far), deve ser chamado
quando se usa assembler puro.
EX.: RET

ROL <DEST>, <VALOR>


- Nome: Roda esquerda
Type: 8086+
Descrio: Esta instruo roda
<DEST> <VALOR> vezes. Uma rodada
realizada shiftando <DEST> uma vez, ento

transfere-se o bit que saiu para


a posio de mais baixa ordem de <DEST>.
EX.: ROL AX, 3
ROR <DEST>, <VALOR>
Tipo: 8086+

- Nome: Roda Direita

Descrio: Esta instruo roda


<DEST> <VALOR> vezes. Uma rodada
realizada shiftando <DEST> uma vez, ento
transfere-se o bit que saiu para
a posio de mais alta ordem de <DEST>.
EX.: ROR BX, 5
SAHF

- Nome: Armazena AH nos Flags


Tipo: 8086+
Descrio: Esta instruo carrega o
contedo do registrador AH nos bits
7, 6, 4, 2 e 0 do registrador de flags.
EX.: SAHF

SBB <DEST>, <FONTE>


- Nome: Subtrai com "pede-emprestado"
Tipo: 8086+
Descrio: Esta instruo subtrai
<FONTE> de <DEST>, e decrementa
<DEST> de uma unidade de o flag de vai-um
estiver setado, armazenando o resultado
em <DEST>.
Basicamemte, <DEST> = <DEST> - <FONTE> - CF
EX.: SBB AX, BX
SHL <DEST>, <VALOR>
- Nome: Shift esquerda
Tipo: 8086+
Descrio: Esta instruo desloca <DEST>

esquerda de <VALUE> unidades.


Eu no vou entrar em detalhes
sobre a teoria disso de novo.
Se voc no tem certeza do que esta
instruo faz, por favor, leia o Tutorial
Quatro.
EX.: SHL AX, 5
SHR <DEST>, <VALOR>
- Name: Shift direita
Type: 8086+
Descrio: Esta instruo desloca <DEST>
direita de <VALUE> unidades. Por favor
veja o Tutorial Quatro para a teoria dos
shifts.
EX.: SHR DX, 1
STC

- Nome: Seta o flag de vai-um (Carry Flag)


Tipo: 8086+
Descrio: Esta instruo seta o valor
do carry flag para um.
EX.: STC

STD

- Nome: Seta o flag de direo


Tipo: 8086+
Descrio: Esta instruo seta o
valor do flag de direo para um. Isto
instrui a todas operaes a decrementar
os registradores de ndice.
EX.: STD
REP STOSB ; DI est sendo
; decrementado

STI

- Nome: Seta Flag de Interrupo


Tipo: 8086+

Descrio: Esta instruo seta o


valor do flag de interrupo para um, assim
permitindo que interrupes de hardware
ocorram.
EX.: CLI
; Pra interrupes
...
; Realiza uma funo crucial
STI
; Habilita interrupes
STOS

- Nome: Armazena String


Tipo: 8086+
Descrio: Esta instruo existe nas
seguintes formas:
STOSB
STOSW
STOSD

- Armazena um byte
- AL
- Armazena uma word
- AX
- Armazena uma doubleword - EAX

As instrues escrevem o contedo atual


do acumulador para a posio de memria
apontada por ES:DI. Ento ela incrementa
ou decrementa DI de acordo com o operando
usado, e o valor do flag de direo.
Ex.: MOV AX, 0A000h
MOV ES, AX
MOV AL, 03h
MOV DI, 0
STOSB
; Armazena 03 em ES:DI,
; que o topo da tela
; em modo 13h
SUB <DEST>, <FONTE>
- Nome: Subtrao
Tipo: 8086+
Descrio: Esta instruo subtrai
<FONTE> de <DEST>, armazenando o resultado
em <DEST>.
EX.: SUB ECX, 12
TEST <DEST>, <FONTE>

- Nome: Testa Bits

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

Descrio: Esta instruo troca os


valores de <VALOR1> e <VALOR2>.
EX.: XCHG AX, BX
XOR <DEST>, <FONTE>
- Nome: OU Exclusivo Lgico
Tipo: 8086+
Descrio: Esta instruo realiza
um OU exclusivo bit-a-bit em
<FONTE> e <DEST>. A operao
definida como segue:
XOR
XOR
XOR
XOR

0, 0
0, 1
1, 0
1, 1

=0
=1
=1
=0

EX.: XOR AX, BX

No se preocupe com isso por enquanto. Vamos continuar normalmente, e


introduzir as novas instrues acima uma por uma, explicando-as quando o
fizermos. Se voc j as entende, isso um bnus. Voc vai notar que havia
muitas instrues acima, do 8086. H, na verdade, poucos casos em que
necessrio usar uma instruo do 386 ou 486, muito menos do Pentium.

De qualquer modo, antes de avanar com a VGA, eu vou s listar a velocidade


em que cada instruo acima executada, assim voc pode usar isso para
ver como as rotinas em Assembler so rpidas.

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

Obs.: m = Nmero de componentes na prxima instruo executada.

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;

Um pixel at 2, 0 mapeado no plano 2 no offset 0;


Um pixel at 3, 0 mapeado no plano 3 no offset 0;
Um pixel at 4, 0 mapeado no plano 0 no offset 1 ... e assim por diante...
Por causa de os pixels serem encadeados atravs dos 4 planos, impossvel
usar mltiplas pginas no modo 13h sem ter que to usar uma tela virtual,
ou algo do tipo.
O mapeamento automtico dos pixels feito todo pela placa de vdeo,
de modo que voc pode trabalhar de olhos fechados sem saber dos 4 planos de
bits se voc quiser.
Vamos ver como voc pode contornar essa situao, entrando num modo especial,
conhecido como Modo X, mais tarde, mas por enquanto, vamos s ver o que
podemos fazer no velho modo 13h.

DESENHANDO LINHAS

Passamos um pouco da tamanho do tamanho que eu tinha planejado para este


tutorial, e eu pretendo falar do Algoritmo de Retas de Bresenham, mas isso vai
ter que esperar a semana que vem. No entanto, vou cobrir como desenhar um
linha reta horzontal simples em Assembler.
Um Rotina em Assembler para Retas Horizontais:
-----------------------------------------------Primeiramente vamos precisar de apontar ES para a VGA.
Isso deve resolver:
MOV AX, 0A000h
MOV ES, AX
Agora, precisaremos de ler os valores de X1, X2 e Y nos registradores, ento
algo assim deveria funcionar:
MOV AX, X1 ; AX igual ao valor X1 agora
MOV BX, Y ; BX igual ao valor Y agora
MOV CX, X2 ; CX igual ao valor X2 agora
Ser preciso calcular o tamanho da linha, ento vamos usar CX para

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

mov al, color ; Pe a cor a plotar em AL


rep stosb
; Desenha a linha
}
}

A rotina acima no cegamente rpida, mas no de toda m. S de mudar o clculo da


parte de DI como na PutPixel que eu dei no Tutorial Dois, j dobraria a velocidade desta
rotina.
Minha prpria rotina de linha horizontal provavelmente cerca de 4 a 5 vezes
mais rpida que esta, assim, no futuro, eu vou lhe mostrar como otimizar essa
rotina por completo. Semana que vem vamos ver como pegar e acertar a palette,
e como podemos desenhar crculos. Sinto muito se no fiz isso nesse
tutorial, mas ele cresceu um pouco demais...
COISAS PARA FAZER:
-------------------1) Escreva um rotina para linhas verticais baseada na rotina acima. Dica:
Voc precisa incrementar DI de 320 em algum lugar.
2) Volte lista das instrues de Assembler, e aprenda quantas puder.
3) D uma olhda no Starfield, e tente corrigir os bugs dele.
Veja o que voc pode fazer com ele.

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

mov al, color ; Pe a cor a plotar em AL


rep stosb
; Desenha a linha
Agora, embora essa rotina seja muito mais rpida que as rotinas do BGI, (ou
seja l o que for que seu compilador tenha), ela poderia ser melhorada
pra caramba. Se entrarmos na rotina, com a lista de clocks que eu dei no
ltimo tutorial, voc vai ver que ela gasta bem pouco.
Eu vou deixar a otimizao com voc por enquanto, (vamos ver isso em outro
tutorial), mas se substituir STOSB por MOV ES:[DI], AL ou STOSW vai melhorar
muito as coisas. No se esquea que se voc decidir usar um loop,
para jogar words na VGA, voc ter que decrementar CX de uma unidade.
Agora, vamos ver uma linha vertical. Teremos que calcular o offset do primeiro
pixel como ns fizemos na rotina de linhas horizontais, ento, algo desse tipo
funcionaria:
mov ax, 0A000h ; Pe o segmento VGA em AX
mov es, ax
; Aponta ES para a VGA
mov ax, Y1
shl ax, 6
mov di, ax
shl ax, 2
add di, ax
add di, X

; Move o primeiro valor de Y para AX


; Y x 2^^6 (dois sexta potncia)
; Move o novo valor de Y para DI
; Agora temos Y = Y x 320
; Adiciona aquele valor a DI
; Soma o valor de X a DI

Agora umas coisas bsicas...


mov cx, Y2
mov al, Color
sub cx, Y1

; Guarda Y2 em CX
; Guarda a cor a plotar em AL
; CX = tamanho da linha

E agora o loop final...


Plota:
mov es:[di], al

; Pe um pixel no offset corrente

add di, 320


dec cx
jnz Plota

; Move para a prxima linha


; Decrementa CX de um
; Se CX <> 0, ento continua plotando

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

bx, [DeX] { Move DeX para BX


cx, bx
{ Move BX para CX
ax, 0A000h { Move o endereo da VGA para AX
es, ax
{ Aponta ES para a VGA
dl, [Color] { Move a cor a plotar para DL
si, [DeY] { Move DeY para SI

}
}

@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.

OS INS E OUTS DE IN E OUT

IN e OUT so uma parte muito importante de cdigo em Assembler. Elas permitem


voc a mandar/receber diretamente dados de qualquer uma das 65,536 portas de
hardware, ou registradores. A sintaxe bsica como segue:
IN <ACUMULADOR>, <PORTA>
Tipo: 8086+

- Nome: Entrada de porta de E/S

Descrio: Esta instruo l um valor


de uma das 65536 portas de hardware
para o acumulador especificado.

AX e AL so comumente usados para portas


de entrada, e DX mais usado para
identificar a porta.
EX.: IN AX, 72h
MOV DX, 3C7h
IN AL, DX
OUT <PORTA>, <ACUMULADOR>
Tipo: 8086+

- Nome: Sada para a Porta

Descrio: Esta instruo pe na sada o


valor no acumulador para <PORTA>. Usando
o registrador DX para passar a porta para
OUT, voc pode acessar at 65,536 portas.
EX.: MOV DX, 378h
OUT DX, AX
OK, isso no ajudou muito, j que no disse muito sobre como usar - muito
menos para que usar. Bem, se voc pretende trabalhar muito com a VGA, voc
ter que ser capaz de programar seus registradores internos. Semelhantes
aos registradores com que voc tem trabalhado at agora, voc pode pensar
em mud-los como interrupes, exceto que: 1) Voc passa os valores para a
porta, e isso a; e 2) muito perto de ser instantneo.
Como exemplo, vamos ver como setar e pegar a palette controlando diretamente
o hardware da VGA.
Agora, a VGA tem uma poro de registradores, mas as prximas trs bom que
voc conhea bem:
03C7h

- PEL Registrador de Endereos (Leitura)


Seta a palette em mode de leitura

03C8h

- PEL Registrador de Endereos (Escrita)


Seta a palette em modo de escrita

03C9h

- PEL Registrador de Dados (Leitura/Escrita)


L, ou escreve 3 valores RGB, a cada terceira escrita, o
ndice, ou cor que voc est setando, incrementado de um.

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

; Pe o valor conseguido da porta DX em AL


; Aponta DI para a varivel R - Isso vem do Pascal
; Guarda AL em R

in al, dx
les di, [G]
stosb

; Pe o valor conseguido da porta DX em AL


; Aponta DI para a varivel G
; Guarda AL em G

in al, dx
les di, [B]
stosb

; Pe o valor conseguido da porta DX em AL


; Aponta DI para a varivel B
; Guarda AL em B

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

hardware mais rpido e mais eficiente. Nas prximas semanas, eu posso


incluir uma lista das portas mais comuns, mas se voc tivesse uma cpia da
Ralf Brown's Interrupt List (disponvel no X2FTP), voc j teria uma cpia.
OBS.: Voc pode achar um link para a Ralf Brown's Interrupt List na minha
pgina.

Um pouco mais sobre o registrador de flags:


Agora, embora tenhamos usado o registrador de flags em quase todo nosso
cdigo at esse ponto, eu no entrei profundamente nesse assunto. Voc pode
trabalhar felizmente sem conhecer muito sobre os flags, e comparar coisas
sem saber o que est realmente acontecendo, mas se voc quiser avanar no
Assembler, voc precisa saber algo mais.
De volta ao Tutorial Trs, eu dei uma viso simplista do registrador de FLAGS.
Na realidade, os FLAGS, ou EFLAGS na verdade um registrador de 32-bit,
embora apenas s os bits de 0 a 18 sejam usados. Na realidade no precisamos
conhecer os flags acima do 11 por enquanto, mas bom saber que eles existem.
O registrador EFLAGS na verdade se parece com isso:
18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
AC VM RF -- NT IO/PL OF DF IF TF SF ZF -- AF -- PF -- CF
Agora, os flags so os seguintes:
AC - Alignment Check (80486) / Checagem de Alinhamento
VM - Virtual 8086 Mode / Modo Virtual 8086
RF - Resume Flag / Flag de Continuao
NT - Nested Task Flag / Flag de Tarefa Aninhada
IOPL - I/O Privilege Level / Nvel de Privilgio de E/S
Tem um valor de 0,1,2 ou 3 logo ocupa 2 bits
OF - Overflow Flag / Flag de Overflow
Este bit setado para UM se uma instruo aritmtica gerar um
resultado que muito grande ou muito pequeno para caber no
registrador destino.
DF - Direction Flag / Flag de Direo

Qaundo setado para ZERO, as instrues de string, como MOVS, LODS,


e STOS incrementaro o endereo de memria que elas esto
trabalhando de uma unidade. Isto significa que, digamos, DI ser
incrementado quando voc usar STOSB para colocar um pixel em
ES:DI. Setando o bit para UM decrementar o endereo de memria
aps cada chamada.
IF - Interrupt Enable Flag / Flag de Habilitao de Interrupes
Quando este bit est setado, o processador responder a
interrupes externas do hardware. Quando o bit for resetado,
interrupes de hardware so ignoradas.
TF - Trap Flag / Flag de Trap ("armadilha")
Quando este bit estiver setado, uma interrupo ocorrer
imediatamente depois que a prxima instruo executar. Isto
geralmente usado em depuraes.
SF - Sign Flag / Flag de Sinal
Este bit mudado aps operaes aritmticas. O bit recebe
o bit de mais alta ordem do resultado, e se setado para UM,
indica que o resultado da operao foi negativo.
ZF - Zero Flag / Flag de Zero
Este bit setado quando instrues aritmticas geram um
resultado zero.
AF - Auxiliary Carry Flag / Flag de Vai-Um Auxiliar
Este bit indica que um vai-um no nibble de baixa ordem de AL
ocorreu na instruo aritmtica.
PF - Parity Flag / Flag de Paridade
Este bit setado para um quando uma instruo aritmtica resulta
num nmero par de bits 1.
CF - Carry Flag / Flag de Vai-Um
Este bit setado quando o resultado de uma operao aritmtica
muito grande ou muito pequena para o registrador destino
ou endereo de memria.
Agora, de todos esses acima, voc no precisa mesmo se preocupar muito com
a maioria deles. Por enquanto, s conhecer CF, PF, ZF, IF, DF e OF ser
suficiente. Eu no dei comentrios para os primeiros j que eles so
puramente tcnicos, e so usados mais no modo protegido e situaes complexas.
Voc no deveria ter que conhec-los.

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!

OK, de volta ao modelo em Assembly puro do Tutorial Trs. Eu no me lembro


direito, mas eu acho que era alguma coisa assim:
DOSSEG
.MODEL SMALL
.STACK 200h

.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

SEGMENT WORD PUBLIC

DATA

ENDS

CODE SEGMENT WORD PUBLIC


ASSUME CS:CODE, DS:DATA
CODE

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.

Ento, como eu declaro procedures externas?


Ok, por exemplo, vamos usar umas poucas rotinas simples similares quelas
na biblioteca do modo 13H do PASCAL (disponvel na minha homepage).
Se voc se lembrar, a procedure se parece um pouco com isso:
Procedure PutPixel(X, Y : Integer; Color : Byte);
Procedure InitMCGA;
Procedure Init80x25;
Ajustando isso no nosso esqueleto, temos:
CODE SEGMENT WORD PUBLIC
ASSUME CS:CODE DS:DATA
PUBLIC PutPixel
PUBLIC InitMCGA
PUBLIC Init80x25
CODE

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.

FATO ENGRAADO: Lembre que a primeira cois a entrar na pilha a LTIMA A


SAIR. :)
Quando voc chamar a Putpixel, a pilha estar mudada. Como isso uma chamada
FAR, os primeiros 4 bytes so CS:IP. Os bytes da em diante so os nossos
parmetros.
Para encurtar a histria, digamos que a pilha se parea com isso:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...
Depois de chamar -- PutPixel(10, 20, 15); -- um tempo depois, ela pode se
parecer com isso:
4C EF 43 12 0F 00 14 00 0A 00 9E F4 3A 23 1E 21 ...
^^^^^^^^^^^ ^^^^^ ^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^
CS:IP Color Y X
Algum lixo
Agora, para complicar as coisas, a CPU armazena words na pilha com a PARTE
MENOS SIGNIFICATIVA PRIMEIRO. Isso no nos atrapalha muito, mas se voc
ficar andando por a com um debugger sem saber disso, voc vai ficar mesmo
confuso.
Note tambm que quando o Turbo Pascal pe um tipo de dado byte na pilha, ele
te chupa DOIS BYTES, NO UM. Voc no adora o modo como o PC organizado? ;)
Agora, tudo que eu disse at agora s se aos parmetros passados por valor PARMETROS QUE VOC NO PODE MUDAR. Quando voc estiver por a brincando
com
PARMETROS PASSADOS POR REFERNCIA, como -- MyProc(Var A, B, C : Word);
-cada parmetro agora usa QUATRO BYTES de pilha, dois para o segmento e dois
para o offset de onde a varivel est na memria.
Assim, se voc pegou uma varivel que est, digamos, na posio de memria
4AD8:000Eh, no interessa o valor dela, 4AD8:000Eh seria armazenado na pilha.
J que isso acontece, voc ia ver 0E 00 D8 4A na pilha, lembrando que o
nibble menos significativo armazenado primeiro.
FATO ENGRAADO: Parmetros de Valor pe na verdade o valor na pilha, e
Parmetros de Referncia armazenam o endereo. :)

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!

Agora vamos por a PutPixel, InitMCGS e Init80x25 em cdigo Assembler.


Voc obtm algo assim:
CODE SEGMENT WORD PUBLIC
ASSUME CS:CODE DS:DATA
PUBLIC PutPixel
PUBLIC InitMCGA
PUBLIC Init80x25
.386

; Declara as procedures pblicas

; Vamos usar alguns registradores de 386

Procedure PutPixel(X, Y : Integer; Color : Byte)

PutPixel PROC FAR


PUSH BP
MOV BP, SP

; Declara uma procedure FAR


; Arruma a pilha

MOV BX, [BP+10]


; BX = X
MOV DX, [BP+08]
; DX = Y
XCHG DH, DL
; Como Y sempre ter um valor menor quee 200,
MOV AL, [BP+06]
; isto 320x200, no se esquea, dizer XCHG DH,DL
MOV DI, DX
; um modo genial de dizer SHL DX, 8
SHR DI, 2
ADD DI, DX
ADD DI, BX
; Agora temos o offset, ento...
MOV FS:[DI], AL
; ...plote em FS:DI
POP BP
RETF 6
PutPixel ENDP

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

E s. Desculpe-me se eu fiz a coisa toda um pouco confusa, mas essa a


graa dos computadores! :)
Ah! A propsito, voc pode usar o cdigo acima em Pascal, assemblando-o com
TASM ou MASM. Depois, inclua no seu cdigo isso:
{$L SEJALDOQUEVOCCHAMOU.OBJ}
{$F+}
Procedure PutPixel(X, Y : Integer; Color : Byte); External;
Procedure InitMCGA;
External;

Procedure Init80x25;
{$F-}

External;

Begin
InitMCGA;
PutPixel(100, 100, 100);
ReadLn;
Init80x25;
End.

FUNES E OTIMIZAES POSTERIORES

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

Nota: Eu planejo mais pra frente desenvolver a biblioteca MODE13H,


adicionando fontes e outras coisas legais. Ela ser eventualmente
codificada s em Assembler, e poder ser chamada do C ou Pascal.
Cdigo puro em Assembler tambm tem um grande aumento de velocidade.
Hoje eu testei a rotina PutPixel da biblioteca MODE13H e uma pura
(praticamente idntica), e vi uma diferena espantosa.
Num 486SX-25 com 4Mb de RAm e uma placa VGA de 16 bits, levou 5
centssimos de segundo para a rotina pura desenhar 65536 pixels
no meio da tela, contra 31 centsimos de segundo da outra. Grande
diferena, no?

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

A AGI (Address Generation Interlock):


Que merda essa?, voc pergunta. Uma AGI ocorre quando uma registrador que
est correntemente sendo usado como base ou ndice foi o destino da ltima
instruo. AGI's so ruins, e chupam clocks.
EX.: MOV ECX, 3
MOV FS, ECX
Isso pode ser evitado executando-se uma outra instruo entre os dois MOV's,
pois AGI's podem ocorrer s entre instrues adjacentes (no 486). No Pentium,
uma AGI pode acontecer at entre 3 instrues!

Use Instrues/Registradores de 32 bits:


Usar registradores de 32 bits tende a ser mais rpido que usar seus
equivalentes de 16 (particularmente EAX, j que muitas instrues ficam
um byte menor quando ele usado. Usar DS ao invs de ES tambm mais
rpido pelo mesmo motivo).
Outras coisas para se tentar:
Evite LOOP's. tente usar um DEC, ou INC seguido de um JZ ou instruo
similar. Isso pode fazer uma diferena enorme.
Quando for zerar registradores, use XOR ao invs de MOV xx, 0. acredite
ou no, mesmo mais rpido.
Use o TEST quando for checar se um registrador igual a zero. Em se
fazer um AND dos operandos juntos no se gasta tempo com um registrador
destino. TEST EAX,EAX um bom mode de checar de EAX=0.
USE SHIFTS! No use multiplicao para calcular mesmo as mais simples
somas. A CPU pode mover uns poucos zeros para a esquerda ou para a
direita muito mais rpido que ela pode fazer uma multiplicao/diviso.
Faa uso da LEA. Uma instruo tudo o que leva para realizar uma
multiplicao inteira e armazenar o resultado num registrador. Esta
uma alternativa til para SHL/SHR (eu sei, eu sei... eu disse que a
multiplicao era ruim. Mas uma LEA s vezes pode ser til, j que pode
economizar vrias instrues.).
EX.: LEA ECX, [EDX+EDX*4] ; ECX = EDX x 5
Evite fazer MOV's para registradores de segmento muito frequentemente.
Se voc vai trabalhar com um valor que no muda, tal como A000h, ento
carregue-o em FS, por exemplo, e use FS da em diante.
Acredite ou no, instrues de string (LODSx, MOVSx, STOSx))so muito
mais rpidas num 386 que num 486. Se estiver trabalhando num 486 ou mais,
ento use outra instruo, mais simples.
Quando for mover pedaos de 32 bits, REP STOSD mais rpido que usar
um loop para fazer a mesma coisa.

Bem, agora voc j viu como escrever cdigo externo, declarar procedures
em Assembler e otimizar suas rotinas.

ESTRUTURAS DE DADOS EM ASSEMBLER


Bem, at agora voc deveria saber que voc pode usar o DB, (Declare Byte) e
DW, (Declare Word) para criar variveis. Porm, at agora ns os temos usado como
voc usaria a declarao de Const em Pascal. Quer dizer, temos usado isto para
dar a um byte ou a uma word um valor.
Ex.:
MyByte DB 10 -- que o mesmo que -- Const MyByte : Byte = 10;
Contudo, poderamos dizer:
MyByte DB ?
...e ento dizer depois:
MOV MyByte, 10
De fato DB realmente muito poderoso. H vrios tutoriais atrs, quando voc
estava aprendendo a escrever strings na tela, voc viu algo desse tipo:
MyString DB 10, 13 "This is a string$"
Agora, o mais curioso de vocs provavelmente teria dito a si prrpio: "Pera!...
aquele cara do tutorial disse que DB declara um BYTE. Como que o DB
pode declarar uma string, ento "? Bem, DB tem a habilidade de reservar espao
para valores de vrios bytes - de 1 a tantos bytes quanto voc precisa.
Voc tambm pode ter desejado saber o que os nmeros 10 e 13 antes do texto
representavam. Bem, d uma olhada na sua tabela ASCII e veja o que so o 10 e

o 13. Voc notar que 10 o Line Feed e o 13 o Carriage Return.


Basicamente, o mesmo que dizer:
MyString := #10 + #13 + 'This is a string';
em Pascal.

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

Alguns pontos importantes para se lembrar so os seguintes:


Voc pode declarar uma estrutura em qualquer lugar em seu cdigo, embora
que, para um bom design, voc deva coloc-los no segmento de dados, a menos que eles
s sejam usados por uma subrotina.
Definir uma estrutura no reserva qualquer byte de memria para a mesma. Isso
s acontece quando voc declara uma varivel dessa estrutura - a a memria alocada.

REFERENCIANDO ESTRUTURAS DE DADOS EM ASSEMBLER

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

Date_I_Passed_Physics Date <> ; Espero!


Neste momento, Date_I_Passed_Physics tem todos os seus trs campos preenchidos.
Dia setado para 19, Ms para 6 e Ano para 1996. Agora, o que so esses
parnteses,"<>", fazendo depois de data? - voc pergunta.
Os parnteses nos apresentam um outro modo de alterar os contedos
dos campos da varivel. Se eu tivesse escrito isto:
Date_I_Passed_Physics Date <10,10,1900>
...ento os campos teriam sido mudados para os valores nos parnteses.
Alternativamente, teria sido possvel fazer isto:
Date_I_Passed_Physics Date <,10,> ;
E s agora o campo de Ms foi mudado. Note que neste exemplo, a segunda
vrgula no era necessria, pois ns no mudamos outros campos posteriores.
sua escolha, (e do compilador!), se deixar a segunda vrgula ou no.
Agora tudo isso t indo muito bem, mas como voc se estes valores em seu cdigo?
Simplesmente basta dizer:
MOV AX, [Date_I_Passed_Physics.Month]
MOV [Date_I_Passed_Physics.Day], 5
CMP [Date_I_Passed_Physics.Year], 1996

; ou algo como

; ou at mesmo

Simples, n?

CRIANDO ARRAYS EM ASSEMBLER


Certo, arrays so bem fceis de se implementar. Por exemplo,
digamos que voc tivesse a seguintes estrutura de array em Pascal:
Var
MyArray: Array[0 ..19] of Word;
Para criar um array semelhante em Assembler, voc tem que usar o operador
DUP. DUP, ou DUPlique Varivel, tem a seguinte sintaxe:
<rtulo>

<diretiva> <contador> DUP (expresso)

Onde (expresso) um valor opcional para inicializar o array.


Basicamente, aquele array no Pascal se pareceria com isso:
MyArray DW 20 DUP (?)
Ou, se voc quisesse inicializar cada valor para zero, ento voc poderia
dizer isto:
MyArray DW 20 DUP (0)
E, como outro exemplo de como o Assembler flexvel, voc poderia dizer
algo desse tipo:
MyArray DB 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,,,
...para criar um array de 10 bytes, com todos os dez elementos inicializados em 1,
2, 3...

INDEXANDO ARRAYS EM ASSEMBLER

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

Porm, como eu disse antes, MUL no um modo muito eficiente de codificao,


assim, substituir o MUL por um SHL 2 e um ADD seria a ordem do dia.

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

Certo, de volta ao Tutorial Cinco, eu dei as trs tabelas verdade para E, OU

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

AND 0010 0000


= 0000 0000
^^^ Essa maiscula ^^^

AND 0010 0000


= 0010 0000
^^^ Essa minscula ^^^

Agora, e o OR? O OR geralmente usado depois de um AND, mas no tem


que ser. Voc pode usar OR para mudar bits individuais em um registrador ou
varivel sem mudar quaisquer um dos outros bits. Voc poderia usar OR para escrever
uma rotina para mudar um caracter para maisculo se j no for, ou talvez para minscula
se fosse maiscula.
Ex:

0101 0011
OR 0010 0000
= 0111 0011
^^^ S maisculo agora foi mudado para s minsculo ^^^

A combinao de AND/OR um dos truques mais frequentemente usados no mundo do


Assember, assim tenha certeza de que voc entendeu bem o conceito. Voc me ver
freqentemente usando-os, tirando proveito da velocidade das instrues.
Finalmente, e o XOR? Bem, o OU exclusivo pode ser s vezes muito til.
XOR pode ser de til para alternar bits individuaisentre 0 e 1 sem
ter que saber qual o contedo que cada bit tinha anteriormente. Lembre-se,
como com OU, uma mscara de zero permite ao bit original continuar com seu valor.
Ex:

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

Agora, os princpios de uma rotina de fogo so bastante simples. Voc basicamente


faz o seguinte:
Crie um buffer com o qual voc vai trabalhar
Este buffer pode ser quase de qualquer tamanho, entretanto quanto menor
voc o fizer, o mais rpido seu programa ser, e quanto maior voc o fizer, o mais
bem definido o fogo ser. Voc precisa acertar um equilbrio entre claridade e
velocidade.
Minha rotina est um pouco lenta, e isto devido em parte claridade
do fogo. Eu escolhi 320 x 104 como tamanho do meu buffer, assim eu fiz um
compromisso. A resoluo horizontal boa - 1 pixel por elemento de array,
mas a resoluo vertical um pouco baixa - 2 pixels por elemento de array.
Contudo, eu j vi rotinas onde um buffer de 80 x 50 usado, significando
que h 4 pixels por elemento para o eixo horizontal e vertical. rpido, mas
com baixssima definio.
Faa uma palette agradvel
Seria idia boa para ter cor 0 como preto, (0, 0, 0) e a cor 255 como
branco - (63, 63, 63). Tudo entre isso deveria ser uma mistura de
amarelo-avermelhado flamejante. Eu suponho voc poderia ter chamas verdes se voc
quisesse, mas ns vamos usar as chamas que ns conhecemos agora. :)
Agora o loop principal comea. No loop voc deve:
Criar uma linha de fundo ramdmica, ou duas linhas de fundo

Basicamente, voc tem um loop como:


For X := 1 To Xmax Do
Begin
Temp := Random(256);
Buffer[X, Ymax - 1] := Temp;
Buffer[X, Ymax] := Temp;
End;
Codifique isso na linguagem de sua escolha, e voc est no negcio.
Suavize o array:
Agora este o nico pedao com macete. O que voc tem que fazer, como
segue:
* Comece da segunda linha pra baixo do buffer.
* Mover para baixo, e para cada pixel:
* Some os valores dos quatro pixels que cercam o pixel.
* Divida o total por quatro conseguir uma mdia.
* Tire um da mdia.
* Ponha a mdia - 1 no array DIRETAMENTE ACIMA onde o pixel velho estava.
(Voc pode alterar isto, e digamos, pr acima e direita, e ento
parecer que a chama est sendo soprada pelo vento.)
* Faa isso at voc chegar ltima linha.
Copie o array para a tela
Se seu array de 320 x 200, ento voc pode copiar elemento-para-pixel. Se
no , ento coisas so mais difceis. O que eu tive que fazer era copiar uma linha
do array para a tela, abaixar uma linha da tela, copiar a mesma linha do array
para a tela, e ento entrar numa linha diferente no array e na tela.
Deste modo, eu espalhei o fogo um pouco.
Voc vai, , querer saber exatamente por que meu array de 320 x 104 e
no de 320 x 100. Bem, a razo para isto bastante simples. Se eu tivesse
usado 320 x 100 como minhas dimenses de array, e ento copiasse isso para a
tela, as ltimas quatro linhas teriam parecido bem estranhas. Elas
no teriam sido suavizados corretamente, e o resultado final no estaria de
todo flamejante. Assim, eu apenas copiei at a linha 100 para a tela, e deixei o
resto pra l.

Como uma experincia, tente mudar a terceira linha abaixo no


procedimento de DrawScreen para MOV BX, BufferY e mudar as dimenses para
320x100 e veja o que acontece.
MOV SI, OFFSET Buffer
; Aponta SI para o incio do buffer
XOR DI, DI
; Comea a desenhar em 0, 0
MOV BX, BufferY - 4
; Perde as 4 ltimas linhas do
; buffer. Estas linhas no vo se parecer
; com fogo de jeito nehum.
Volta para o incio.

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

O segmento de dados comea, e eu dou para CR e para LF os valores de "carriage return" e


"line feed" (retorno de carro e alimentao de linha, i.e, volta pro incio e desce uma
linha).
BufferX EQU 320
BufferY EQU 104

; Largura do buffer de tela


; Altura do buffer de tela

AllDone DB CR, LF, "That was:"


DB CR, LF
DB CR, LF, "
FFFFFFFFF IIIIIII RRRRRRRRR ..."
DB CR, LF, "
FFF
III
RRR RRR ..."
DB CR, LF, "
FFF
III
RRR RRR ..."
DB CR, LF, "
FFF
III
RRRRRRRR ..."
DB CR, LF, "
FFFFFFF
III
RRRRRRRR ..."
DB CR, LF, "
FFF
III
RRR RRR ..."
DB CR, LF, "
FFF
III
RRR RRR ..."
DB CR, LF, "
FFF
III
RRR RRR ..."
DB CR, LF, "
FFFFF
IIIIIII RRRR RRRR ..."
DB CR, LF
DB CR, LF
DB CR, LF, " The demo program from Assembler Tutorial 8. ..."
DB CR, LF, " author, Adam Hyde, at: ", CR, LF
DB CR, LF, " blackcat@faroc.com.au"
DB CR, LF, " http://www.faroc.com.au/~blackcat", CR, LF, "$"
Buffer DB BufferX * BufferY DUP (?) ; O buffer de tela
Seed

DW 3749h

; O valor de seed, e metado do meu nmero de


; telefone - no em hexa. :)

INCLUDE PALETTE.DAT
; A palette, gerada com
; Autodesk Animator, e um programa simples em
; Pascal.

Agora, no fim, eu declaro o array e declaro um VALOR DE SEED (semente) para o


procedimento Random que segue. A seed s um nmero que necessrio para
comear o procedimento Random, e pode ser qualquer coisa que voc quiser.
Eu tambm economizei algum espao e pus os dados para a palette em um arquivo
externo que includo no cdigo assembly. D uma olhada no arquivo.
Usar INCLUDE pode economizar muito espao e confuso.
Eu pulei alguns procedimentos que so bastante auto-explicativos, e fui direto
para a procedure DrawScreen.
DrawScreen PROC
MOV SI, OFFSET Buffer
; Aponta SI para o incio do buffer
XOR DI, DI
; Comea a desenhar em 0, 0

MOV BX, BufferY - 4


; Perde as ltimas 4 linhas do buffer
; Essas linhas no se parecem
; com fogo, de jeito nenhum
Row:
MOV CX, BufferX SHR 1
; 160 WORDS
REP MOVSW
; Move-as
SUB SI, 320
; Volta pro incio da linha do array
MOV CX, BufferX SHR 1
; 160 WORDS
REP MOVSW
; Move-as
DEC BX
; Decrementa o nmero de linhas VGA restantes
JNZ Row
; Terminamos?
RET
DrawScreen ENDP
Isto tambm fcil seguir, e tira proveito de MOVSW, usando-a para mover dados entre
DS:SI
e ES:DI.
AveragePixels PROC
MOV CX, BufferX * BufferY - BufferX * 2 ; Altera todo o buffer,
; exceto a primeira linha e a ltima
MOV SI, OFFSET Buffer + 320
; Comea da segunda linha
Alter:
XOR
MOV
ADD
ADC
ADD
ADC
ADD
ADC
SHR

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

^^^ Este programa returna 0

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:

MOV AX, @DATA


MOV DS, AX

; DS agora aponta para o segmento de dados.

Ns apontamos DS primeiramente para o segmento de dados, de modo que possamos ter


acesso a todas nossas variveis.
CALL InitializeMCGA
CALL SetUpPalette
MainLoop:
CALL AveragePixels
MOV SI, OFFSET Buffer + BufferX * BufferY - BufferX SHL 1
; SI agora aponta para o incio da segunda ltima linha (?????? - by Krull)
MOV CX, BufferX SHL 1
; Prepara para pegar BufferX x 2 nmeros
randmicos
BottomLine:
CALL Random
MOV DS:[SI], DL
INC SI
DEC CX
JNZ BottomLine

; Pega um nmero randmico


; Usa apenas o byte baixo de DX, i.e.,
; o nmero vai ser de 0 --> 255
; Um pixel a menos para fazer
; J acabamos?

Aqui, uma nova linha do fundo calculada. O procedimento Random - muitas


graas ao autor desconhecido da USENET - retorna um valor muito alto
em DX:AX. Porm, ns s requeremos um nmero de 0 a 255, assim, usando s DL,
ns temos tal nmero.
CALL DrawScreen
MOV AH, 01H
INT 16H
JZ MainLoop
MOV AH, 00H
INT 16H
CALL TextMode
MOV AH, 4CH
MOV AL, 00H

; Copia o buffer para a VGA


; Checa se foi pressionada alguma tecla
; H alguma tecla esperando no buffer?
; No, segue em frente
; Sim, ento pega a tecla

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.

De qualquer maneira, vamos l com:

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

; Aconteceu algo de errado?

; Aqui voc deveria ter o handle AX, e fazer alguma coisa


JMP Done

; 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

OK... simples bastante, espero. Agora, suponha que queiramos criar um


arquivo novo? apenas uma outra subfuno simples da interrupo 21H.
assim que se faz:
Criando UM Arquivo Novo:

(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.

Finalmente, cdigos de erro. Apenas checando o CF para ver se qualquer


coisa saiu errado, nos deixar saber certamente se algo est faltando,
mas ns realmente gostamos de mais detalhes. Examinar o AX depois de um erro
ser descoberto o caminho a seguir, e AX poderia conter qualquer um dos
cdigos seguintes:

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

Disco protegido contra escrita


unidade Ruim
Drive no pronto
comando Desconhecido
erro de CRC
tamanho de estrutura ruim
erro de procura
mdia invlida
Setor no achado
Impressora desligada **
erro de escrita
erro de leitura
falha geral

Tudo cortesia da boa e velha referncia tcnica do DOS. Algum deles l


encima so bem obscuros - na verdade s h alguns que voc precisa de se lembrar.
Algum de meu *favoritos * : Setor no achado, Erro de procura e Erro de CRC
no meio de uma pilha de disquetes relaxados arjeados. o tipo de porcaria
que traz recordaes. :)

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

40H. Um cdigo para escrever um byte se pareceria com isso:


MOV AH, 40H
; Escreve byte(s)
MOV BX, Handle
; arquivo para se escrever nele
MOV CX, BytesToWrite
; quanto escrever
MOV DX, OFFSET WhereToWriteFrom ; de onde os dados esto vindo
INT 21H
JC DidSomethingGoWrong
; algum erro?
Bem, aquilo quase conclui E/S de arquivos para este tutorial. Embora no seja um
componente principal da peogramao da linguuagem Assembly, E/S de arquivos
todavia,
um conceito importante para se pegar.

CHAMANDO O ASSEMBLER NO C/C++

Eu suponho que j passou da hora de falar sobre como linkar o Assembler no C.


Pessoalmente, eu prefiro codificar VGA numa combinao de Assembler/Pascal.
Porm, C tem seu lugar, e linkar com C um assunto importante que ns
deveramos cobrir.
Voc deve ter percebido que voc pode entrar cdigo Assembly em seu programa de
C desse jeito:
/ * Seu cdigo em C vai aqui * /
asm {
/**/
/ * Seu cdigo em Assembler vai aqui * /
/**/
}
/ * Seu cdigo em C continua daqui * /

Agora, considerando que ns podemos inserir o Assembly diretamente no cdigo em


C, por que nos preocuparamos em escrever cdigo externo? A resposta
bastante simples. Usando rotinas externas, temos cdigo que mais
rpido de se executar, mais rpido de compilar, que pode usar algumas das
caractersticas especiais de Turbo Assembler - como modo ideal, e pode ser at mesmo
portvel a outras linguagens.
Escrever cdigo externo para C bem simples, e gratificantemente mais fcil
que escrever cdigo externo para Pascal. (Veja o Tutorial Sete). Como voc
pde observar no Tutorial Sete, ns tnhamos que declarar o segmento de cdigo e
o de dados usando o a meio confusa diretiva SEGMENT. Isto devido ao modo
como o Pascal gosta de organizar a memria, e s h um modo de contornar
problema - ns podemos usar o modelo TPASCAL. Infelizmente, TPASCAL um
modo antiquado de fazer as coisas, assim ns temos que pr um pouco de trabalho
nisso. Eu no vou falar novamente em TPASCAL, assim ns podemos nos esquecer
seguramente de detalhes chatos.
Note que nada disto aplica a ns em C - ns podemos usar felizmente nossos
simples e agradveis esqueletos de Assembler. H algumas
restries colocadas, entretanto, a ns pela maioria dos compiladores:
O compilador usa SI e DI para armazenar variveis registradoras. Se voc usou
variveis registradoras em seu cdigo, lembre-se de dar um push e pop em SI e DI em
seu cdigo externo.
O compilador provavelmente no vai dar push e pop em CS, DS, SS e BP, ento
tenha certeza de ter cuidado se for alterar algum desses registradores.
Alm desses pequenos detalhes, h pouco que ns precisamos ter em mente.
Vamos l!

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

Message DB "Well looky here - we got ourselves some text$"


.CODE
PUBLIC

_sample

; --------------------------------------------------------------------------;
; void sample();
;
_sample

PROC NEAR

MOV AH, 00H


MOV AL, 03H
INT 10H

; Declara uma procedure near


; Acerta o modo de video
; Modo 03H

MOV AH, 09H


; Imprime uma string
MOV DX, OFFSET Message ; DS:DX <-- Mensagem
INT 21H
RET
_sample

; 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;

E para compilar o lote, a linha abaixo far o trabalho.


C:\> TCC EXAMPLE.C LIBRARY.ASM
Claro que, se voc est usando ento que outro "sabor" de C, substitua TCC com
qualquer outro interpretador de linha de comando que voc tiver. Tambm possvel
fazer o C reconhecer variveis declaradas em Assembler, e o seguinte
esqueleto explica como isso feito:
======================= LIBRARY.ASM ==========================
.MODEL SMALL
.DATA
PUBLIC _YourVariable

; Declara uma varivel externa

_YourVariable DW 9999

; Faz a varivel ser uma word valendo 9999

.CODE
END

========================= EXAMPLE.C ==========================


extern int YourVariable;
int main()
{
printf("The Assembler external variable is: %d", YourVariable);
return(0);
}
Novamente, compile isto com: TCC EXAMPLE.C LIBRARY.ASM

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

; Declara a procedure externa

; --------------------------------------------------------------------------;
; 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]

; Parmetros podem ser acessados facilmente agora

MOV ES:[DI], AL
POP BP

; BP precisa ser restaurado!

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);
}

No to macetoso, hein? Porm, se voc escolher escrever rotinas externas


porque voc quer a velocidade que o Assembler pode lhe dar, ento acesse
a pilha do modo difcil. Esses extras push's e pop's realmente podem crescer
se sua rotina de putpixel for chamada 320x200 vezes!

UMA INTRODUO S MACROS

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.

Embora aquela macro simples realmente no fosse nada de especial, macros tm


muitas outras utilidades. Eu no vou dizer mais nada sobre macros agora,
mas eu as usarei de vez em quando em programas de demonstrao no futuro, e
voc aprender outras tcnicas que voc pode pr em bom uso.
De qualquer maneira, vamos ao que eu queria fazer:

O PROGRAMA DEMONSTRATIVO

No princpio eu ia lanar este tutorial sem um programa demo, mas


vendo como eu fui um pouco preguioso esse tempo todo, (e tambm porque
um amigo meu h pouco tempo fez uma demonstrao como essa), eu decidi
incluir um demo de plasma.
Plasmas podem ser um pouco engenhosas em umas partes -- me levou um bom tempo
para fazer a coisa funcionar direito por causa de um problema que eu tive com
minha tabela de lookup. Mas se voc seguir o algoritmo abaixo, voc
no deve ter nenhum problema.
*** Antes de comear, voc precisar de QUATRO variveis temporrias em
***
seu cdigo. Em Assembler isto pode se pr um pouco trabalhoso porque voc

se achar freqentemente com falta de registradores. Voc poderia declarar


alguns bytes no segmento de dados, mas mais rpido usar registradores.
Estas quatro variveis temporrias armazenaro s nmeros entre 0 e 255,
assim elas s precisam ser BYTES.
No algoritmo, eu me refiro a estas variveis temporrias como Temp1,
*** Temp2, Temp3 e Temp4.
***
O algoritmo se parece com isso:
Crie uma tabela de lookup
Isto basicamente s uma senide longa. Voc pode experimentar
usar uma onda de co-seno, ou alterar a amplitude da funo que voc
est usando. Eu criei minha tabela de lookup usando a seguinte expresso:
For W := 1 To 512 Do
SinTable[W] := Round(Sin(W / 255 * Pi * 2) * 128);
(SinTable um array de 512 BYTES)
Inicialize a palette
Eu pessoalmente gosto de fazer minhas palettes depois de ver o demonstrativo
rodando com a palette padro. Desse modo, fazendo certas cores escuras e outras
muito claras, o resultado exatamente do jeito que eu quero.
Eu descobri que o melhor modo de fazer isto capturar a tela quando a
demonstrao
est rodando, com um programa como Screen Thief, ento carregar aquela tela
em um programa de pintura que deixe alterar a palette.
Depois de conseguir a palette do jeito que voc quer, salve-a para o disco como
um arquivo COL (se possvel) e ento escreve um pequeno programa para ler no
arquivo COL e escrever um arquivo tipo o PLASMA.DAT.
Se lembre, Screen Thief shareware, assim se voc for us-lo, envie para
o autor algum dinheiro, hein?
Loop (1): Antes de comear a plotar a primeira linha, voc deve:
* Zerar Temp4;
* Decrementar Temp3 de dois;

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.

(Para aqueles que querem saber para qu so exatamente todos os bits,


ache que deveriam obter uma cpia da Ralf Brown's Interrupt List. Isto
est disponvel na minha homepage e ela contm uma lista completa de todas
as interrupes, registradores e muito mais.)
De qualquer modo, ns estamos s interessados no quarto bit do 03DAH que nos
deixa saber se um retrace est acontecendo. Se pudermos acessar o VGA enquanto
o canho de eltrons do monitor est voltando (retrace) ao topo da tela -- ns
podemos obter um rpido acesso, livre de flicks (tremidas..., sacou? - by Renato)
O que demais, j que o retrace acontece a cada 1/80 de segundo EM TODOS OS
COMPUTADORES, agora ns temos um mtodo de fazer que nosso demo rode
numa
velocidade especfica em todas as mquinas.
Para conferir se o retrace est acontecendo, ns examinamos simplesmente
o bit quatro. Se o bit quatro est setado, um retrace est em execuo.
Porm, ns no sabemos o quanto de um retrace j aconteceu, e no sabemos
quanto tempo de acesso live de flicks ns temos. A soluo checar de novo
por um retrace, de modo que podemos estar seguros de que estamos no
COMEO de um.
Eu usei retrace no cdigo para ter certeza de que o demo ia
rodar mesma velocidade em todas as mquinas. (Mais ou menos).
Tambm note que meu plasma mais uma variante de plasma. Voc pode,
e encorajado a alterar o cdigo -- (tente incrementar os valores de temp
em vez de decrementar e mudar a quanto o valor de temp decrementado ou
mudar o modo como o valor das corer achado. Tambm tente mudar a palette,
porque isto pode fazer o plasma parecer completamente diferente).
possvel criar todos os tipos de efeitos s fazendo mudanas simples,
assim, experimente... seja criativo!

Você também pode gostar