VGA – Tutorial 19 – Denthor

--==[ PARTE 19 ]==--

Autor: DENTHOR do ASPHYXIA
Atualizado por Snowman
Traduzido por Krull >>> tradução revisada e atualizada em 2020

Para obter os programas mencionados neste tutorial, por favor baixe o zip com a versão antiga do tutorial, neste link.

Introdução

Olá. Como prometido no tutorial 18, esse treinamento é sobre assembly. Para as pessoas que já sabem assembly muito bem, esse tutorial é também sobre um efeito de chamas.

Tutorial 20? Que tal shading em 3D, remoção de superfície escondida, etc? Escreva para mim 🙂

Se você gostaria de me contactar, ou ao time, há muitos modos que você pode fazê-lo: 1) Escrever uma mensagem para Grant Smith/Denthor/Asphyxia em email privado na ASPHYXIA BBS.
2) Escrever para:
Grant Smith
P.O.Box 270 Kloof
3640
Natal
África do Sul
3) Ligar para mim (Grant Smith) no número (031) 73 2129 (deixe uma mensagem se você ligar quando eu estiver na faculdade). Ligue para +27-31-73-2129 se você está ligando de fora da África do Sul (A conta é sua ;-))
4) Escrever para denthor@beastie.cs.und.ac.za
5) Escrever para asphyxia@beastie.cs.und.ac.za para falar com todos nós de uma vez.

OBS1 : Se você é um representante de uma companhia ou BBS e quer que a ASPHYXIA faça um demo para você, mande um email pra mim; podemos discutir. OBS2 : Se você fez/tentou fazer um demo, MANDE PARA MIM! Estamos nos sentindo muito solitários e queremos encontrar/ajudar/trocar código com outros grupos de demos. O que você tem a perder? Mande uma mensagem aqui e podemos ver como transferir. Nós realmente queremos ouvir de você.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Assembly – a versão resumida

OK, há muitos tutoriais de assembly por aí, muitos dos quais provavelmente melhores que esse. Vou focar nas áreas de assembly que eu achar importante… se você quiser mais, compre um livro (procure os do Michael Abrash ), ou vasculhe a Internet para outras informações.

Primeiramente, vamos começar com a configuração básica de um programa em assembly.

DOSSEG

Isso diz ao seu programa assembly para ordenar seus segmentos da mesma maneira que as linguagens de alto nível fazem.

.MODEL <MODEL>

<MODEL> pode ser: Tiny Código + Dados < 64k (Pode-se fazer um arquivo COM) 
                  Small Código < 64k Dados < 64k 
                  Medium Código > 64k Dados < 64k 
                  Compact Código < 64k Dados > 64k 
                  Large Código > 64k Dados > 64k
                  Huge Arrays > 64k
.286

Habilita as instruções do 286 … pode ser .386 ; .386P etc.

.STACK <size>

<size> será o tamanho da sua pilha. Geralmente uso 200h

.DATA

Avisa ao programa que os dados vêm a seguir. (Tudo depois disso será colocado no segmento de dados)

.CODE

Diz ao programa que o código segue abaixo. (Tudo após isso será colocado no segmento de código)

START :

Diz ao programa que esse é o lugar onde o código começa.

END START

Diz ao programa que aqui é onde o código termina.

Para compilar e rodar um arquivo assembly, rodamos
tasm bob
tlink bob

Pessoalmente, eu uso o TASM, você vai ter que descobrir como o seu assembler funciona.

Agora, se rodássemos o arquivo acima, como se segue:

DOSSEG
.MODEL SMALL
.286
.STACK 200h
.DATA
.CODE

START
END START

Você acharia que ele apenas sairia para o DOS imediatamente, certo? Errado. Você teria especificamente que dar o controle de volta ao DOS, fazendo o seguinte:

START
    mov ax,4c00h
    int 21h
END START

Agora, se você o compilasse, ele rodaria sem fazer nada.

OK, vamos brincar com os registradores.

Primeiramente: Um bit é um valor que é 1 ou 0.

Isso é obviamente bastante limitado, mas se começarmos a contar neles, podemos obter números maiores. Contar com zeros e uns é conhecido como binário, e chamamos de base 2. Contar em decimal normal é chamado de base 10, e contar em hexadecimal é conhecido como base 16.

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

Como você pode ver, você precisa de quatro bits para contar até 15, e chamamos isso de um nibble. Com oito bits, contamos até 255, e podemos chamar isso de byte. Com 16 bits, podemos contar até 65535, e podemos chamar isso de uma word. Com 32 bits podemos contar até “muito”, e podemos chamar isso de uma double word 🙂

Uma nota rápida: Converter de binário para hexa é muito fácil. Você quebra o binário em grupos de quatro bits, a partir da direita, e converte esses grupos de quatro em hexa.

  1010 0010 1111 0001
=    A    2    F    1

Converter para decimal é um pouco mais difícil. O que se faz é multiplicar cada número por sua base elevado a seu índice…

   A2F1 hexa
= (A*16^3) + (2*16^2) + (F*16^1) + (1*16^0)
= (10*4096) + (2*256) + (15*16) + (1)
= 40960 + 512 + 240 + 1
= 41713 decimal

O mesmo sistema pode ser usado para binário.

Para converter de decimal para outra base, você divide o valor decimal pela base desejada, anotando os restos, e então lendo os resultados de trás pra frente.

           16   |   41713
           16   |   2607    resto   1       (41713 / 16 = 2607 resto 1)
           16   |   162     resto   F       (2607 / 16 = 162 resto 15)
           16   |   10      resto   2       (162 / 16 = 10 resto 2)
                |   0       resto   A       (10 / 16 = 0 resto 10)

Lendo os restos de trás pra frente, nosso número é: A2F1 em hexa. Novamente, o mesmo método pode ser usado para binário.

A razão pela qual o hexadecimal é popular é óbvia… usando bits, é impossível ter um um sistema razoável em base 10 (decimal) rodando, e binário fica difícil de se lidar com valores altos. Não se preocupe muito: a maioria dos assembladores (como o TASM) converterão todos os seus valores decimais em hexa para você.

Você tem quatro registradores de uso geral: AX, BX, CX e DX; Pense neles como variáveis que você sempre terá. Num 286, esses registradores têm 16 bytes de tamanho, ou uma word.

Como você sabe, uma word consiste de dois bytes, e em assembly você pode acessar esses bytes individualmente. Eles são separados em bytes altos e bytes baixos, por word.

Byte Alto | Byte Baixo
0000 0000 | 0000 0000 bits
[--------Word-------]

O método de acessar é fácil. O byte alto de AX é AH, e o baixo é AL… Você pode também acessar BH, BL, CH, CL, DH e DL.

Um 386 tem registradores extendidos: EAX, EBX, ECX, EDX… você pode acessar a word baixa normalmente (como AX, com bytes AH e AL), mas não pode acessar as words altas diretamente… você deve fazer um “ror EAX,16” (rotacionar o valor binário em 16 bits), depois disso a word alta e a baixa ficam trocadas… faça de novo para voltá-las aos seus lugares. Acessar EAX como um todo não é um problema… mov eax,10 ; add eax,ebx … são todos válidos.

A seguir vêm os segmentos. Como você já deve ter ouvido, a memória do computador é dividida em vários segmentos de 64k (note: 64k = 65536 bytes, parece familiar?) Um registrador de segmento aponta para que segmento você está olhando. Um modo de olhar para ele é como olhar um array 2D… Os segmentos são suas colunas e seus offsets suas linhas. Segmentos e offsets (deslocamentos) são mostrados como Segmento:Offset… assim, $a000:50 significaria o quinquagésimo byte no segmento $a000.

Os registradores de segmento são ES, DS, SS e CS. Um 386 também tem FS e GS. Esses valores são words (0-65535), e você não pode acessar os bytes altos e baixos separadamente. CS aponta para seu segmento de códigos, e geralmente se você mexer nele, seu programa explode. SS aponta para seu segmento de pilha, e de novo, esse bebê é perigoso. DS aponta para seu segmento de dados, e pode ser alterado, se você o colocar de volta onde estava após usá-lo, e não usar nenhuma variável global enquanto ele estiver alterado. ES é seu segmento extra, e você pode fazer o que quiser com ele.

Os registradores de deslocamento são DI, SI, IP, SP, BP. Registradores de deslocamento são geralmente associados com segmentos específicos, como se segue:

ES:DI     DS:SI     CS:IP     SS:SP …  

Num 286, BX pode ser usado ao invés dos registradores de offset acima, e num 386, qualquer registrador pode ser sado.

DS:BX é, então, válido.

Se você criar uma variável global (digamos, bob), quando você acessar aquela variável, o compilador vai na verdade procurar por ela no segmento de dados. Isso significa que o comando:

ax = bob
poderia ser
ax = ds:[15]

Nota rápida: Um valor pode ser com ou sem sinal. Uma word sem sinal vale de 0 a 65535. Uma word com sinal é chamada de inteiro, e tem valores de -32768 a 32767. Com um valor com sinal, se o bit mais à esquerda for igual a 1, o valor é negativo.

A seguir, vamos dar uma olhada na pilha. Digamos que você queira salvar o valor em AX, usar AX para fazer outras coisas, e então restaurá-lo ao valor original no fim de tudo. Isso é feito utilizando-se a pilha. Dê uma olhada no seguinte código:

mov ax, 50    ; ax é igual a 50
push ax       ; bota ax na pilha
mov ax, 27    ; ax é igual a 27
pop ax        ; tira ax da pilha
Nesse ponto, AX é igual a 50.

Lembra-se que definimos a pilha para ser 200h lá em cima? Isso é parte da razão para termos aquilo. Quando você bota alguma coisa na pilha, aquele valor é gravado no topo da pilha (referenciado por SS:SP, SP é incrementado) Quando você tira um valor da pilha, o valor é colocado na variável para a qual você pediu para POPar, SP é decrementado e assim em diante. Note que o computador não se importa com que registrador você usa para popar o valor da pilha…

mov ax, 50
push ax
pop bx

Isso setaria tanto os valores de Ax quanto de BX para 50. (há modos muitos mais rápidos de se fazer isso, se bem que fazer push e pop é bem rápido)

push ax
push bx
pop ax
pop bx

Isso trocaria os valores de AX e BX. Como você pode ver, para “popar” valores de volta para suas variáveis originais, você deve fazê-lo na ordem inversa que você usou para “PUSHar”.

push ax
push bx
push cx
pop cx
pop bx
pop ax

Isso não resultaria em mudança alguma nos registradores.

Quando uma procedure é chamada, todos os parâmetros para ela são colocados na pilha. E elas podem ser trazidas pra fora da pilha, se você quiser.

Como você já viu, o comando mov move um valor…

mov <dest>, <source>

Note que dest e source devem ter o mesmo número de bits…

mov ax, dl

não funcionaria, e nem

mov cl,bx

Porém,

mov cx,dx
mov ax,50
mov es,ax


são todos válidos.

SHL eu já expliquei antes, é quando todos os bits de um registrador são deslocados uma posição para a esquerda e um zero é adicionado na direita. Isso é o equivalente a multiplicar o número por dois. SHR funciona no sentido oposto.

ROL faz o mesmo, só que o bit que é removido à esquerda, volta para a direita. ROR funciona na direção oposta.

DIV <value> divide o valor em AX pelo e retorna o resultado em AL se value for um byte, colocando o resto em AH. Se value for uma word, a double word DX:AX é dividida pelo valor, o resultado é colocado em AX e o resto em DX. Note que isso só funciona para valores sem sinal.

idiv <value> faz o mesmo de cima, só que para valores com sinal.

mul <value> Se value é um byte, AL é multiplicado por ele, e o resultado é armazenado em AX. Se o valor for uma word, AX é multiplicado por ele, e o resultado fica na double word DX:AX

imul <value> faz o mesmo, para variáveis com sinal.

Os comandos j* são bem simples: se uma condição for encontrada, pule para um certo label.

jz <label> Pula se zero
ja <label> Pula se acima (sem sinal)
jg <label> Pula se maior (com sinal)

e assim por diante.

Um exemplo…

cmp ax,50    ; Compara ax a 50
je @Equal    ; Se são iguais, pula para o label @equal

call MyProc
Roda a rotina MyProc e então retorna para a próxima linha de código.

Procedures são declaradas desse modo:

MyProc proc near
       ret ; deve estar aqui para retornar para onde ela foi chamada
MyProc endp

Variáveis também são fáceis:

bob db 50

cria uma variável bob, um byte, com valor inicial 50.

bob2 dw 50

cria uma variável bob2, um word, com valor inicial 50.

bob3 db 1,2,3,4,5,65,23

cria bob3, um array de 7 bytes.

bob4 db 100 dup (?)

cria bob4, um array de 100 bytes sem valores iniciais.

Volte e dê uma olhada no tutorial 7 para muitos mais comandos de assembly, e pegar alguma referência para te ajudar com outros. Eu pessoalmente uso o arquivo de ajuda do Norton Guides para programar em assembly.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Rotinas de Fogo

Para demonstrar como escrever um programa assembly, vamos escrever uma rotina de fogo em assembly 100%. A teoria é simples…

Sete a paleta para ir de branco para amarelo para vermelho para azul para preto.
Crie um array 2D representando a tela do computador.
Coloque valores altos no fundo do array (tela)
Para cada elemento, faça o seguinte:
    Calcule a média dos quatro elementos abaixo dele
            * Elemento atual
              123
            4 Outros elementos
    Pegue a média dos quatro elementos e coloque o resultado no elemento atual.
Repetir

Fácil, né? A primeira vez que vi uma rotina de fogo foi no demo Iguana, e eu tinha que fazer uma 😉 … ela parece bem efetiva.

Com o arquivo de amostra, eu criei um arquivo batch, make.bat… ele basicamente diz:

    tasm fire
    tlink fire

Então, para construir o programa de fogo, digite:

make
fire

O arquivo de fonte está bem documentado, então não deveria haver problemas.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Fechando

Como você pode ver, o programa de amostra é 100% em assembly. Para o próximo tutorial voltarei ao Pascal, e espero que nossas novas habilidades em assembly o ajudem lá também.

Tchauuuuu….

  • Denthor
  • Krull

A Nova Krull's HomePage