Assembler Intel Introdução

Este é um curso de Assembler, para intel, retirado de um site, que está citado no fim da pagina, por isso não tenho tanto conhecimento a respeito do assunto, mas espero realmente que ajude a tirar dúvidas a respeito.

O que é Assembler?

Assembler conseguiu ser uma das minhas linguagens favoritas para trabalhar.
Não que seja uma linhguagem fácil no início, mas quando você fica familiar com ela, você entende o quão lógica ela é.

Assembler é uma linguagem de baixo nível, que você pode usar em seus programas para acelerar tarefas lentas. Basicamente ela consite de sentenças que representam instruções em linguagem de máquina, e, como ela está próxima ao código de máquina, ela é rápida.

Há muito tempo atrás, quando o 8086 apareceu (sim, existiam humanos na Terra nessa época:), programar não era uma tarefa fácil. Quando os primeiros computadores foram desenvolvidos, a programação tinha que ser feita em código de máquina, que _não_ era uma tarefa fácil, e assim o Assembler nasceu.


Por que usá-lo?

Como eu disse antes, Assembler é veloz. Ele também permite a você falar com a máquina a nível de hardware, e lhe dá muito maior controle e flexibilidade sobre o PC. Uma das outras vantagens do Assembler é que ele permite a você impressionar seus amigos com páginas de código aparentemente incompreensível.
Não está vendo eles aglomerados em volta de você e impressionados/rindo de sua nerdeza? :)


LIÇÃO 1 - Registradores

Quando você está trabalhando com Assembler, você tem que usar registradores.
Você pode imaginá-los como sendo vari veis já definidas para você. Os mais comuns estão listados abaixo:

AX - o acumulador. Compreende AH e AL, os bytes alto e baixo de AX. Comumente usado em operações matemáticas 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 espírito da coisa agora.

Estes registradores são definidos como registradores de uso geral pois podemos realmente armazenar qualquer coisa que quisermos neles. São também 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 confusão no passado, logo, tentarei dar alguma explicação 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, não se preocupe. O próximo tutorial vai falar sobre ele.)

Agora, se nós 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 próximo tutorial.

Os registradores de segmento: - ta da!

Estes são outros registradores que nós não vamos ver nos primeiros tutorias, mas vamos vê-los em maior profundidade mais tarde. Eles são imensamente úteis, mas podem ser também perigosos.

*CS** - o segmento de código. O bloco de mem¢ria onde o código ‚ armazenado.
NÃO brinque com esse, a menos que saiba o que está fazendo.

DS - o segmento de dados. A área na memória onde os dados são armazenados.
Durante operações de bloco, quando grandes blocos de dados são 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 vídeo.

SS - não, não é o exército alemão. É o segmento de pilha, em que a CPU armazena endere‡os de retorno de subrotinas. Tome cuidado com ele. :)

Alguns outros que você vai comumente usar:

SI - o índice de fonte. Frequentemente usado para movimentações de blocos de instruções. 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. Nós não vamos usá-lo muito.

SP - o apontador da pilha, comumente usado com o segmento de pilha. NÃO brinque com isso de jeito nenhum. :|

Por enquanto você deveria saber o que são registradores. Há outros registradores também, e coisas conhecidas como flags, mas nós não iremos a eles agora.

—-

LIÇÃO 2 - O conjunto de instruções do 8086:

Okay, então você já aprendeu sobre registradores, mas, como usá-los, e como se codifica em Assembler? Bem, primeiro você precisa de algumas instruções. As seguintes instruções podem ser usadas em todas as CPU's do 8086 para cima.

MOV <dest>, <valor> - MOVE. Esta instrução permite MOVER um valor para uma posição na mem¢ria.

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, NÃO FAZ NADA MAIS.

Ex.: (Em Pascal) AX := $13;

INT <número> - INTERRUPÇÃO. Esta instrução gera uma interupção.
Você pode pensar nisso como sendo quase uma procedure.

Ex.: INT 10h

Geraria a interrupção 10h (16 em decimal). Agora, o que isso faria depende do conteúdo do registrador AH, entre outras coisas. Por exemplo, se AX = 13h e a interrupção 10h foi gerada, o vídeo seria colocado no modo 320x200x256.

Mais precisamente:

AH seria igual a 00 - seleciona a subfunção do modo, e AL seria igual a 13h - modo gráfico 320x200x256.

Contudo, se AH = 2h, e a interrupção 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 interrupção 10h foi gerada, então a CPU moveria o cursor para a posição X em DL e posição Y DH.

NÃO SE PREOCUPE COM ISSO POR ENQUANTO! NÓS FALAREMOS NISSO MAIS TARDE, COM MAIS DETALHES.

ADD <dest> <valor> - ADICIONA. Esta instrução soma um número 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

Bem simples, não?

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 <posição> - PULA para uma posição.

EG: JMP 020Ah ; Pula para a instrução em 020Ah
JMP @MyLabel ; Pula para @MyLabel.

NÃO SE PREOCUPE SE ISTO É UM POUCO CONFUSO - VAI FICAR PIOR! HÁ OUTRAS 28 INSTRUÇÕES JUMP PARA APRENDER, TALVEZ MAIS. FALAREMOS NELAS MAIS TARDE.

CALL <procedimento> - CHAMA uma subfunção.

EG: Procedure MyProc;

Begin { MyProc }
{ … }
End; { MyProc }

Begin { Main }
Asm
CALL MyProc ; Adivinha o que isso faz!
End;
End.

Ou: CALL F6E0h ; Chama subfunção em F6E0h

LOOP <rótulo/label> - Faz LOOPS (repetição) 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. NÆo decremente-o
; você mesmo (DEC CX).

; ISSO DEVERIA SE REPETIR 16 vezes - i.e., 10 em hexadecimal.

LODSB - Carrega um byte
LODSW - Carrega uma word
STOSB - Armazena um byte
STOSW - Armazena uma word

Estas instruções são usadas para pôr ou conseguir algo numa posição na memória. O registrador DS:SI, (lembra que nós falamos sobre isso antes, sobre SI ser o índice de fonte?), aponta para a localização de onde queremos obter os dados, e DS:DI aponta para onde colocaremos informações.

É claro, não 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 configuração na mem¢ria:

Posição na memória ³ 06 ³ 07 ³ 08 ³ 09 ³ 10 ³ 11 ³ 12
Valor ³ 50 ³ 32 ³ 38 ³ 03 ³ 23 ³ 01 ³ 12

Quando nós usamos LODSB ou STOSB, ele retorna ou pega um número de AL.
Assim, se DS:SI apontava para 07 e executássemos uma instrução LODSB, AL seria agora igual a 32.

Agora, se nós apontássemos DS:DI para 11, colocando, diria, 50 no registrador AL, e executasse STOSB, então teríamos o seguinte resultado:

Posição na Memória ³ 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 número de 8 bits (um byte), apenas. Podemos
armazenar um número de 8 bits em AL, AH, ou AX, mas não podemos armazenar um número de 16 bits em AH ou AL porque eles são
REGISTRADORES DE 8 BITS.

Como resultado, quando usarmos LODSW ou STOSW, nós devemos usar AX e não AL, já que estaremos pegando/colocando um número 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:

Posição de Memória ³ 06 ³ 07 ³ 08 ³ 09 ³ 10 ³ 11 ³ 12
Valor ³ 50 ³ 32 ³ 38 ³ 03 ³ 23 ³ 50 ³ 12

Em ES:DI:

Posição de Memória ³ 06 ³ 07 ³ 08 ³ 09 ³ 10 ³ 11 ³ 12
Valor ³ 10 ³ 11 ³ 20 ³ 02 ³ 67 ³ 00 ³ 12

Se apontarmos DS:SI para a posição 07, apontarmos ES:SI para a posição 11 e executarmos MOVSB, o resultado em ES:DI pareceria com:

Em ES:DI:

Posição de Memória ³ 06 ³ 07 ³ 08 ³ 09 ³ 10 ³ 11 ³ 12
Valor ³ 10 ³ 11 ³ 20 ³ 02 ³ 67 ³ 32 ³ 12

ESPERO QUE VOCÊ PEGUE A IDÉIA GERAL. CONTUDO, É CLARO, NÃO É TÃO SIMPLES.
POSIÇÕES DE MEMÓRIA NÃO SÃO ARRUMADOAS EM FORMA DE ARRAY, EMBORA EU DESEJASSE MUITO QUE FOSSEM. QUANDO FOR MOVER/PEGAR/COLOCAR, VOCE ESTARÁ MEXENDO COM UMA POSIÇÃO TAL COMO: 100:102H. AINDA ASSIM, VOCÊ DEVERIA PEGAR A IDÉIA.

REP - REPETE o número de vezes especificado no registrador CX.
Um REP na frente de um MOVSB/LODSB/STOSB causaria a repetição da instrução. Logo:

Se CX = 5, e se ES:DI apontava para 1000:1000h, então REP STOSB armazenaria o que estava no registrador AL na posição 1000:1000h 5 vezes.

De qualquer modo, no último número eu disse que estaria discutindo sobre hexadecimal, segmentos + offsets, mais algumas intruções e algumas procedures contendo assembler que você poderia realmente usar.

Então, lá vamos nós, com segmentos e offsets!


LIÇÃO 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 dígitos podemos fazer números em BINÁRIO ou BASE 2.

EX.: 0000 = 0 0100 = 4 1000 = 8 1100 = 12 10000 = 16
0001 = 1 0101 = 5 1001 = 9 1101 = 13
0010 = 2 0110 = 6 1010 = 10 1110 = 14
0011 = 3 0111 = 7 1011 = 11 1111 = 15

O NIBBLE, ou quatro bits. Um nibble pode ter um valor máximo de 1111 que é 15 em decimal. É aqui que o hexadecimal entra. Hex é baseado naqueles 16 números, (0-15), e quando escrevemos em hex, usamos os 'dígitos' abaixo:

0 1 2 3 4 5 6 7 8 9 A B C D E F

Hexadecimal é na verdade muito fácil de se usar, e, apenas como curiosidade, eu acho que os Babilônios usava um sistema de numeração em BASE 16.

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 máximo de 255 em decimal, 11111111 em binário, ou FFh em hexadecimal.

A WORD - outra unidade comumente usada. Uma word é um número de 16 bits, e é capaz de armazenar um número até 65535. Isso é 1111111111111111 em binário, e FFFFh em hex.

Obs.: Por causa de uma word ser quatro nibbles, é também representada por quatro dígitos hexadecimais.

Obs.: Isto é um número 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ê não vai usar muito as double words nestes tutoriais, mas vamos mencioná-las mais tarde quando falarmos de PROGRAMAÇÃO 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á atrás.

A DWORD também é o tamanho dos registradores extendiddos de 32 BITS, ou seja, EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP e EIP.

O KILOBYTE, é 1024 bytes, NÃO 1000 bytes. O kilobyte é igual a 256 double-words, 512 words, 1024 bytes, 2048 nibbles ou 8192 BITS.

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 são estruturados. Nós dissemos que AL e AH eram registradores de 8 bits, logo, eles não deveriam se parecer com algo assim?

AH AL

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' não AX = AH MAIS AL.

Assim, se AH era igual a 00000011 e AL era igual a 0000100, para calcular AX nós 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. Não é tão macetoso assim.

Okay, agora vamos ver os registradores de 16 bits:

AX
³ ³

AH AL

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 ³ ³
³ AH ³ AL ³ ³

Não ‚ muito difícil, espero. E se entendeu isso, você está pronto para SEGMENTOS e OFFSETS.


Uma Arquitetura Segmentada

Há muito, muito tempo atrás, quando a IBM construiu o primeiro PC, não 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 não consideravam aplicações enormes, decidiram dividir a memória em SEGMENTOS, pequenas áreas de mem¢ria RAM que você pode colocar APENAS uma tela virtual
para gráficos 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 programação em 32 bits dé pra acessas até 4GB de RAM sem usar segmentos, mas isso é uma outra hist¢ria.

Segmentos e offsets são apenas um método de especificar uma posição na memória.

EG: 3CE5:502A


SEG OFS

Okay, aqui está a especificação:

Um OFFSET = SEGMENT X 16
Um SEGMENT = OFFSET / 16

Algums registradores de segmento são:

CS, DS, ES, SS e FS, GF - Obs.: Os últimos 2 são registradores que só existem em 386 ou superiores.

Alguns registradores de offset são:

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 são:

CS:IP - Endereço do c¢digo executando no momento.
SS:SP - Endereço da posição atual da pilha.

OBS.: NÃO 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 ENGRAÇADO A RAM da VGA começa em A000h

Ufa! Isso foi muito para o segundo tutorial. Contudo, ainda não terminamos. Esse negócio de AX, AH, AL é um conceito que você pode não ter sacado ainda, então lá vamos nós:

MOV AX, 0 ; AX = 0
MOV AL, 0 ; AL = 0
MOV AH, 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

-

A Pilha

A pilha é uma característica muito útil de que podemos tirar vantagem. Pense nela como uma pilha de papéis numa bandeja de ENTRADA. Se você põe 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.

E na prática:

MOV AX, 03h ; AX = 03h
PUSH AX ; PUSH AX na pilha (coloca no topo)

MOV AX, 04Eh ; AX = 04Eh

; Faça alguma coisa… uma soma?

POP AX ; AX = 03h

Ou:

MOV AX, 03h ; AX = 03h
PUSH AX ; Adiciona AX à pilha

MOV AX, 04Eh ; AX = 04Eh

; Faça alguma coisa… uma soma?

POP BX ; BX = 03h

Você acabou de aprender duas instruções:

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 comentários foram DELIBERADAMENTE REMOVIDOS. É seu dever tentar comentá-los. Note também, que algumas novas instruções são 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 }


O que são flags?

As proceures acima com comentários.

Um programa só em assembler. Você vai precisar pelo menos do DEBUG, embora TASM e TLINK sejam uma boa idéia.

Bem-vindos ao terceiro tutorial da série. No último tutorial eu disse que estaríamos discutindo mais algumas instruções, flags e um verdadeiro programa em Assembly.

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ê não pode programar em Assembler sem saber pra que são todas as interrupções
e o que são todas as subfunções.

Eu lhe recomendo conseguir uma cópia desses livros assim que possível.


Um Programa Assembly

Eu geralmente não escrevo código 100% em Assembly. É muito mais conveniente usar uma linguagem de alto nível como C ou Pascal, e usar Assembly para acelerar os bits lentos. Contudo, você pode querer se torturar e escrever umaaplicação completamente em Assembly, então aqui vai a configuração básica:

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 código

START - o início do seu código

END START - o fim do seu código

Okay, agora vamos dar uma olhada no programa de exemplo em que eu não farei absolutamente nada!

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 Código;
Segmentos de Dados;
Segmentos de Pilha.

Não 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 código quanto dados se encaixam no mesmo segmento de 64K.

2) SMALL - código e dados estão em segmentos diferentes, embora cada um tenha menos de.

3) MEDIUM - código pode ser maior que 64K, mas os dados têm que ter menos que 64K.

4) COMPACT - códido é menos de 64K, mas dados podem ter mais que 64K.

5) LARGE - código e dados podem ter mais que 64K, embora arrays não possam ser maiores que 64K.

6) HUGE - código, dados e arrays podem ter mais de 64K.

STACK - isso instrui ao PC para arrumar uma pilha tão 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 có digo. 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 começa.

MOV AX, 4C00h ; AH = 4Ch, AL = 00h

Isso move 4Ch para ah, que coincidentemente nos traz de volta ao DOS.
Quando a interrupção 21h é chamada e AH = 4Ch, de volta ao DOS lá vamos nós.

INT 21h

END START - Você não tem imaginação?

Okay, Espero que você tenha entendido tudo isso, porque agora nós vamos mesmo fazer algo.

Neste examplo nós vamos usar a interrupção 21h, (a interrupção do DOS), para imprimir uma string. Para ser preciso, vamos usar a subfunção 9h, e ela se parece com isso:

INTERRUPÇÃO 21h
SUBFUNÇÃO 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. "
DB "Você não tem imaginação? Coloque algo interessante aqui!$"

.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 ; Subfunção de imprimir strings
INT 21h ; Gera a interrupção 21h

MOV AX, 4C00h ; Subfunção de saída para o DOS
INT 21h ; Gera a interrupção 21h
END START

Se você assemblar isso com TASM - TASM SEJALADOQUEVOCECHAMOUELE.ASM então 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 modificações, 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 código 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 ; Subfunção de escrita de strings
INT 21h ; Gera a interrupção 21h

Você vai notar que tivemos que usar AX para por o endereço do segmento de OurString em DS. Você vai descobrir que não dá pra referenciar um registrador de segmento diretamente em Assembler. Na procedure PutPixel do último tutorial, eu movi o endereço da VGA para AX, e então para ES.

A instrução SEG é também introduzida. SEG retorna o segmento onde a string OurString está localizada, e OFFSET retorna, adivinha o quê?, o offset do início do segmento para onde a string termina.

Note também que nós usamos DB. DB não é nada de especial, e significa Declare Byte, que é o que tudo o que ela faz. DW, Declare Word e DD, Declare Double Word também existem.

Você poderia ter também colocado OurString segmento de código, a vantagem é que estaria CS apontando para o mesmo segmento que OurSting, de modo que você não tem que se preocupar em procurar o segmento em que OurString está.

O programa acima no segmento de código 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, não?

Nós não vamos ver muitos programas só em Assembly de novo, mas a maioria das técnicas que usaremos podem ser implementadas em programas só em Assembler.


Então, o que são flags?

Esta parte é para meu companheiro Clive que tem me perguntado sobre flags, então lá vamos nós Clive, com FLAGS.

Eu não me lembro se já introduzimos a instrução CMP ou não, CMP - (COMPARE), mas CMP compara dois números e reflete a comparação nos FLAGS. Para usá-la você faria algo desse tipo:

CMP AX, BX

então seguir com uma instrução como essas abaixo:

COMPARAÇÕES SEM SINAL:

JA - pula (jump) se AX foi MAIOR que BX;
JAE - pula se AX foi MAIOR ou IGUAL a BX;
JB - pula se AX foi MENOR que BX;
JBE - pula se AX foi MENOR ou IGUAL a BX;
JNA - pula se AX foi NÃO MAIOR que BX;
JNAE - pula se AX foi NÃO MAIOR ou IGUAL a BX;
JNB - pula se AX foi NÃO MENOR que BX;
JNBE - pula se AX foi NÃO 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 NÃO está setado - o mesmo que JNE;
JNE - pula se AX NÃO for IGUAL a BX;

COMPARAÇÕES 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 NÃO MAIOR que BX
JNGE - pula se AX foi NÃO MAIOR ou IGUAL a BX;
JNL - pula se AX foi NÃO MENOR que BX;
JNLE - pula se AX foi NÃO 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 NÃO está setado - o mesmo que JNE;
JNE - pula se AX NÃO for IGUAL a BX;

NÃO TÃO COMUNS:

JC - pula se o flag de CARRY está setado;
JNC - pula se o flag de CARRY NÃO está setado;
JO - pula se o flag de OVERFLOW está setado;
JNO - pula se o flag de OVERFLOW NÃO está setado;
JP - pula se o flag de PARIDADE está setado;
JNP - pula se o flag de PARIDADE NÃO está setado;
JPE - pula se a PARIDADE for PAR - o mesmo que JP;
JPO - pula se a PARIDADE for ÖMPAR - o mesmo que JNP;
JS - pula se o flag de SINAL NÃO está setado;
JNS - pula se o flag de SINAL está setado.

Ufa! Meus olhos quase secaram depois de olhar pra essa tela por tanto tempo!

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 serão vistos num Tutorial mais à frente.

Okay, no último tutorial eu lhe dei algumas procedures, e pedi para você comentá-las. Eu não queria uma explicação detalhada do que eles faziam - não se espera que você saiba isso ainda - apenas um sumário do que cada comando faz.

Ex.:

MOV AX, 0003h ; AX agora é igual a 03h;
ADD AX, 0004h ; AX agora é igual a 07h;

Então, aqui vai o conjunto completo das procedures com comentários:

{ Esta procedure limpa a tela em modo texto }

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

Asm { ClearScreen }
mov ax, 0B800h { Move o endereço de vídeo para AX }
mov es, ax { Aponta ES para o segmento de vídeo }
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 }

Explicação:

Nós zeramos DI, logo é igual a 0 - o canto esquerdo da tela. Isto é de onde vamos começar a encher a tela.

Movemos 2000 para CX porque vamos colocar 2000 caracteres na tela.

{ Esta procedure move o cursor para a posição X, Y }

Procedure CursorXY(X, Y : Word); Assembler;

Asm { CursorXY }
mov ax, Y { Move o valor Y para AX }
mov dh, al { Y vai para DH }
dec dh { rotina baseada em ajustar para zero }
mov ax, X { Move o valor de X para AX }
mov dl, al { X vai para DL }
dec dl { rotina baseada em ajustar para zero }
mov ah, 2 { Chama a função correspondente }
xor bh, bh { Zera BH }
int 10h { faz isso (põe o cursor na posição) }
End; { CursorXY }

Explicação:

A ' rotina baseada em ajustar para zero' é realizada porque a BIOS refere-se à posição (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 endereço 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 põe este valor em DI. Vamos }
shr di, 2 { ver isso mais tarde - próximo 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 rápida que essa. Eu já vi uma inline que faz isso em metade do tempo, mas mesmo assim, essa é muito quente.

{ Esta procedure é uma função de delay independente de CPU }

Procedure Delay(ms : Word); Assembler;

Asm { Delay }
mov ax, 1000 { Move o número de ms em um segundo para AX }
mul ms { Faz AX = número de ms a esperar }
mov cx, dx { Prepara para o delay - põe número de ms }
mov dx, ax { onde necessário }
mov ah, 86h { Cria o delay }
int 15h
End; { Delay }

Quase todo o fluido saiu do meus olhos agora - é quase meia-noite - então eu acho melhor parar. Desculpe se os comentários são um pouco curtos, mas eu preciso dormir!

No próximo tutorial vamos ver:

Shifts - o que são eles?
Alguns exemplos de CMP/JMP.
Como a memória VGA é arrumada, e como acessá-la.


ShiftS

Primeiramente porém, devemos terminar aquela coisa de CMP/JMP, e falar de shifts. Quando se está programando em Assembler, a gente acha que comparações, shifts e testar bits são operações muito comuns.

Um Exemplo de Comparação

Eu não vou perder tempo explicando minuciosamente o seguinte exemplo - ele é muito fácil de entender e você deve pegar a idéia 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, "NÃO? NÃO? O que você quer dizer, NÃO?$"
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 ; Novo modo de dizer:
MOV DS, AX ; 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 ; AL - código ASCII, AH - "scan code"
; Ela não ecoa na tela, contudo,
; nós mesmos temos que fazer isso.

PUSH AX ; Aqui nós mostramos na tela o caracter
MOV DL, AL ; note que nós salvamos AX. Obviamente,
MOV AH, 2 ; usando-se AH para imprimir uma string
INT 21h ; destrói-se AX
POP AX

CMP AL, "Y" ; Checa se foi teclado 'Y'
JNE HatesTute ; Se foi, continua

MOV AH, 9 ; Mostra a mensagem "Excelente…"
MOV DX, OFFSET ThirdString
INT 21h
JMP KeepOnGoing ; Volta ao início e começa de novo

HatesTute:
CMP AL, "N" ; Certifica que foi teclado 'N'
JE DontLikeYou ; Infelizmente, sim.

MOV DX, OFFSET FourthString ; Pede ao usuário para tentar de novo
MOV AH, 9
INT 21h
JMP KeepOnGoing ; Deixa ele tentar

DontLikeYou:
MOV DX, OFFSET SecondString ; Mostra a string "NÃO? NÃO? O que…"
MOV AH, 9
INT 21h

MOV DX, OFFSET ExitString ; Mostra a string "Bem, deixa pra lá!"
MOV AH, 9
INT 21h

MOV AX, 4C00h ; Volta para o DOS
INT 21h
END START

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 subfunções do teclado, e veja quais outras combinações de GetKey existem, ou melhor ainda, brinque com a interrupção 10h e entre em algum modo de vídeo 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 própria maneira desconjuntada de fazer as coisas.

Primeiro você vai precisar de entender um pouco de aritmética hexadecimal e binária - um assunto que eu _deveria_ ter coberto antes. Eu geralmente uso uma calculadora científica - ei, eu sempre uso uma calculadora, eu não sou estúpido! - mas é bom ser capaz de saber como multiplicar, somar e converter entre as várias bases.

CONVERTENDO DE BINÁRIO PARA DECIMAL:

De Volta ao Tutorial Um, nós vimos como números binários se parecem, então imagine que eu tenha um número binário de oito dígitos, como:

11001101

O que é isso em decimal??? Há várias formas de converter tal número, e eu uso a seguinte, que acredito se provavelmente a mais fácil:

º Número Binário ³ 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 idéia? 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

128 + 64 + 0 + 0 + 8 + 4 + 0 + 1

205

Desculpe se isto é um pouco confuso, mas é difícil explicar sem demonstrar.
Aqui vai outro exemplo:

º Número Binário ³ 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 técnica com palavras de 16 ou 32 bits também, apenas faça do jeito certo. Ex: Depois de 128, você escreveria 256, depois 512, 1024 e assim por diante.

Você pode dizer se o equivalente decimal ser par ou ímpar pelo primeiro bit. Ex.: No exemplo acima, o primeiro bit = 0, então o número é PAR.
No primeiro exemplo, o primeiro bit é 1, então o número é ÍMPAR.

FATO ENGRAÇADO: Caso você não saiba ainda, bit vem de Binary digIT.

CONVERTENDO DE DECIMAL PARA BINÁRIO:

Isso é provavelmente mais fácil que da base-2 para base-10. Para calcular o que 321 seria em binário, 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 número binário - 101000001. Fácil, 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 X 0
6 = 4 X 1
6 - 4 = 2 = 2 X 1
2 - 2 = 0 = 1 X 0

E isto nos dá - 11000110. Note como você pode checar o primeiro dígito para ver se você conseguiu sua conversão 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 - não muito bom para um número ímpar.
Eu entendi o erro e corrigi o exemplo.

CONVERTENDO DE HEXADECIMAL PARA DECIMAL:

Antes de começar, você deveria saber que o sistema numérico hexadecimal usa os 'dígitos':

0 = 0 (decimal) = 0 (binário)
1 = 1 (decimal) = 1 (binário)
2 = 2 (decimal) = 10 (binário)
3 = 3 (decimal) = 11 (binário)
4 = 4 (decimal) = 100 (binário)
5 = 5 (decimal) = 101 (binário)
6 = 6 (decimal) = 110 (binário)
7 = 7 (decimal) = 111 (binário)
8 = 8 (decimal) = 1000 (binário)
9 = 9 (decimal) = 1001 (binário)
A = 10 (decimal) = 1010 (binário)
B = 11 (decimal) = 1011 (binário)
C = 12 (decimal) = 1100 (binário)
D = 13 (decimal) = 1101 (binário)
E = 14 (decimal) = 1110 (binário)
F = 15 (decimal) = 1111 (binário)

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 não é tão difícil como pode parecer, e converter pra lá ou pra cá é bem fácil. Como exemplo, vamos converter B800h para decimal:

FATO ENGRAÇADO: B800h é o endereço inicial do vídeo 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
0 = 1 x 0 = 1 x 0 = 0

Logo B800h = 45056 + 2048 + 0 + 0

47104

Obs.: Para números em hexadecimal maiores que FFFFh (65535 em decimal), você somente segue o mesmo procedimento como para
binário, logo, para o quinto dígito hexadecimal, você multiplicaria por 65535.

Tecle 16 X X na sua calculadora, e fique apertando =.
Você verá os números que precisaria usar. O mesmo aplica-se para binário. Ex.: 2 X X e = lhe daria 1, 2, 4, 8, 16… etc.

OK, isso pareceu bem fácil. Eu acho que nem precisamos de um segundo exemplo.
Vamos dar uma olhada em:

CONVERTENDO DE DECIMAL PARA HEXADECIMAL:

Mais uma vez, o mesmo tipo de procedimento como usamos para binário. 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 = 15 (decimal) = Fh

241 - (16 x 15) = 1

1 / 1 = 1 (decimal) = 1h

Assim, eventualmente temos 7FF1h como resposta. Este não é particularmente um bom processo e requer alguma explicação.

1) Quando você divide 32753 por 4096 você consegue 7.9963379… Não estamos interessados no lixo .9963379, só pegamos o 7, já que 7 é o maior número inteiro que podemos usar.

2) O resto da operação acima é 4081. Devemos agora realizar a mesma operação 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Ê NÃO DEVERIA CONSEGUIR UMA RESPOSTA COM MUITAS CASAS DECIMAIS AQUI. SE VOCÊ TEM - VOCÊ CALCULOU ERRADO.

É um processo muito imundo, mas funciona. Eu não uso isso, exceto quando eu tenho que usar - eu não sou maluco. Eu uso uma calculadora científica, ou a calculadora do Windows <brrrrr> se eu precisar.

OK, agora que já lidamos com os cálculos horripilantes, você já está pronto para os shifts. Há geralmente 2 formas da instrução shift - SHL (shift left/esquerda) e SHR (shift right/direita). Basicamente, tudo o que essas instruções fazem é deslocar uma expressão para a esquerda ou direita um certo número de bits. Sua principal vantagem é a habilidade de lhe deixar substituir multiplicações lentas com shifts mais rápidos. Você vai achar que isso acelerará pra caramba os algoritmos de pixel/linhas/círculo.

Os PC's estão ficando cada vez mais rápidos a cada dia - um pouco rápido demais pro meu gosto. De volta aos dias do XT - a multiplicação era realmente lenta - talvez levando atá 4 segundos para certas operações. Hoje em dia isso não acontece assim, mas é uma boa idéia otimizar seu código.

Quando nós plotamos um pixel na tela, temos que encontar o offset do pixel a plotar. Basicamente, o que fazemos é multiplicar a posição Y por 320, somar a posição X, e somar isso ao endereço A000h.

Assim basicamente, temos: A000:Yx320+X

Agora, seja lá quão rápido seu maravilhoso 486 ou Pentium é, isso poderia se feito um pouco mais rápido. Vamos reescrever aquela equação
acima, assim, vamos usar alguns números diferentes:

8 6
Offset = Y x 2 + Y x 2 + X
Ou:
Offset = Y x 256 + y x 64 + X

Reconhece esses números? Eles parecem terrivelmente com aqueles que nós vimos naquela tabela de conversão binário-decimal.
Contudo, nós ainda estamos usando multiplicação.
Como podemos incorporar shifts?

Que tal:

Offset = Y SHL 8 + Y SHL 6 + X

Agora, isso é _muito_ mais rápido, já que tudo o que o computador tem que fazer é um shift à esquerda com o número - muito melhor.
Note que o shift à esquerda AUMENTA o número, e o shift à direita DIMINUI o número.

Aqui está um exemplo que pode te ajudar se você ainda está em dúvida no que está acontecendo. Digamos que estamos trabalhando em base-10 - decimal. Agora Peguemos o número 36 como exemplo. "Shiftando" este número à 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 núeros 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 padrão, seria justo assumir que 36 SHL 4 equivaler a 36 x 16.

Note porém, o que está realmente acontecendo. Se você fosse trabalhar com o valor binário de 36, que é mais ou menos isso: 100100, e então 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 posição de memória.

Como outro exemplo, pegue o número binário 1000101. Se fizermos um shift à esquerda de 3, terminaríamos com:

1 0 0 0 1 0 1
<-- SHL 3
1 0 0 0 1 0 1 0 0 0

Agora vamos deslocar o número 45 à DIREITA de 2 unidades. Em binário isso é 101101. De onde:

1 0 1 1 0 1
SHR 2 ——>
1 0 1 1

Notou o que ocorreu? É muito mais fácil para a CPU apenas mover alguns bits (aproximadamente 2 unidades de clock), do que multiplicar um número. (Pode demorar até 133 unidades de clock).

Nós vamos usar bastante shifts quando estivermos programando a VGA, assim, tenha certeza de que você entendeu os conceitos por trás disso.


PROGRAMANDO A VGA EM ASSEMBLER

Quando nós falamos sobre programar a VGA, nós estamos geralmente falando do modo 13h, ou um de seus parentes. Em VGA padrão este é o _único_ modo de usar 256 cores, e é provavelmente um dos modos mais fáceis também.
Se você já tentou experiências 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 padrão 13h é que você sabe que todas as placas VGA que existem vão suportá-lo. As pessoas hoje frequentemente ignoram o modo 13h, achando a resolução muito granulada para os padrões de hoje, mas não se esqueça
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ê desconheça, é que acessar a VGA RAM é incrivelmente fácil. Como 320 x 200 é igual a 64,000, é possível encaixar a tela inteira em um segmento de 64K.

As más notícias são que o modo padrão 13h realmente só te dá uma página para usar, seriamente embaraçante para scroll e page-flipping. nós vamos cobrir mais tarde estes assuntos, como entrar em seus próprios modos - e modo X que evitará esses problemas.

Então, como entrar no modo padrão 13h?

A resposta é simples. Usamos a interrupção 10h - interrupção de vídeo, e chamamos a subfunção 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 vídeo }
mov al, 13h { Usa o modo 13h }
int 10h { Faz isso }
End; { Init300x200 }

você também pode ver:

mov ax, 13h
int 10h

Isso é perfeitamente correto, e provavelmente economiza um tempo de clock por não colocar 00h em AH e então 13h em AL, mas é mais correto usar o primeiro exemplo.

OK, então estamos no modo 13h, mas o que podemos realmente fazer nele, além de olhar para uma tela em branco? Poderíamos voltar ao modo texto usando:

mov ah, 00h
mov al, 03h
int 10h

Mas isso é um pouco idiota. Porque não pintar um pixel?

Há inúmeros modos de colocar um pixel na tela. O modo mais fácil em Assembler é usar interrupções. Você faria mais ou menos assim em Pascal:

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

Asm { PutPixel }
mov ah, 0Ch { subfunção 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, p gina 1 }
int 10h { Plota }
End; { PutPixel }

Contudo, mesmo isso sendo em Assembler, não é particularmente rápido. Por quê?, você pergunta. Porque isso usa interrupção. Interrupções são ótimas para entar e sair de modos de vídeo, ligar e desligar o cursor, etc… mas não para gráficos.

Você pode imaginar interrupções como uma secretária eletrônica. "A CPU está ocupada neste momento, mas se você deixar sua subfunção após o sinal - nós entraremos em contato."

Não é bom. Vamos usar a técnica 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 endereço 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] { Põe a cor a plotar em AL }
stosb { Põe o byte, AL, em ES:DI }
End; { PutPixel }

Esta procedure é rápida o suficiente para começar, embora eu tenha dado uma muito mais rápida uns tutoriais atrás que usa uma técnica genial para pegar DI.

Tenha certeza que entendeu aquela coisa de binário -> decimal, decimal -> binário, decimal -> hex e hex -> decimal. Faça você mesmo
alguns exemplos de soma e teste suas respostas com a calculadora do Windows.

Você deve entender shifts. Se você ainda tem problemas, faça algumas expressões num papel e teste suas respostas num programa como:

Begin { Main }
WriteLn(45 SHL 6);
ReadLn;
End. { Main }

e/ou a calculadora do Windows.

Dê uma olhada na parte de VGA, e certifique-se de ter pego a teoria por trás disso, porque na próxima semana vamos entrar a fundo nisso.



Fonte : http://www.saladoprogramador.hpg.ig.com.br/assembler1.htm

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License