ZINES — underground e-zine archive source
text size: CRT glow:
~/BRAZILIAN/Cogumelo Binário/edição 1/Assembly7
            ____
        _.-'111 `"`--._
    ,00010.  .01011,   ''-..
  ,10101010  `111000. _ ____ ;
 /_..__..-------- '''    __.'                                                          /
 `-._       /""| _..-'''     ___  __   __             ___       __      __  .       __'  ___ .  __
     "`-----\  `\           |    |  | | __ |  | |\/| |___ |    |  |    |__] | |\ | |__| |__/ | |  |
             |   ;.-""--..  |___ |__| |__] |__| |  | |___ |___ |__|    |__] | | \| |  | |  \ | |__|
             | ,10.  101. `.========================================  ==============================
             `;1010  `0110  :                       1º Edição
       .1""-.|`-._          ;
      010 _.-|    +---+----'
      `--'\` |    /  /                        ...:::binariae:fungus:::...
 ~~~~~~~~~| /    |  |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          \|    /   |
           `----`---'
~info do material obrigatório

language:Portuguese
Autor: Frater_Loki | aka Luiz Vieira
contato: luizwt@gmail.com
Data: 20/11/2011
Tipo: Paper
Título: Uma pequena introdução ao Assembly

Muitas pessoas questionam a necessidade de aprender e conhecer de Assembly, que
é uma da linguagens de programação de mais baixo nível que um ser humano, ao in-
teragir com um sistema computacional, pode utilizar para codificar instruções.

Sabemos que existem basicamente três grande grupos de linguagens:
baixo nível
alto nível
altíssimo nível

E por incrível que pareça, nos cursos de computação atuais, os alunos costumam
aprender linguagens de alto nível, quando não, apenas de altíssimo nível.

Por mais que eu seja fã de Python, Ruby ou Perl, sei que essas linguagens não
são as melhores quando precisamos compreender como um sistema funciona realmente
e como tratar diretamente com as instruções executadas pelo processador. Logo,
digamos que, quem aprende a programar hoje em dia, começa pela cereja do bolo,
ao invés de debulhar o trigo para fabricar a farinha.

Esse tipo de aprendizado, mesmo que seja com o foco no mercado de trabalho, pode
ser danoso à longo prazo, pois esses profissionais possuem um conhecimento bem
menor de debugging e otimização de código, do que aqueles que sabem como funcio-
na o core de um sistema.

Daí surgiu a ideia desse pequeno material sobre assembly... Em primeiro lugar,
precisamos entender um pouco mais sobre como um processador funciona, de forma
en passant e depois vamos às instruções específicas dessa linguagem.

Sei que muitos acham que os dados que os programas manipulam ficam todo residen-
tes na memória RAM, o que é um grande engano. Nessa memória primária, e volátil,
ficam apenas os dados maiores e que não estão sendo utilizados pelo processador
em dado momento para a realização de algum tipo de operação. Imaginem o trabalho
que não seria para o processador ir até a memória, pegar os dados, processá-los
e devolvê-lo, sempre trabalhando fora de seu núcleo. Isso demandaria um poder
de processamento muito maior e também maior gasto de energia.

Nesse tipo de situação, a arquitetura adotado quando da criação dos processado-
res foi a seguinte: criar pequenos containers dentro do próprio processador, pa-
ra que os dados utilizados em determinadas operações naquele dado momento, pu-
dessem ser armazenados temporariamente para agilizar o processamento e aumentar
a rapidez nas respostas. Daí surgem os denominados registradores.

Temos os registradores de uso geral e os registradores especiais, cada qual com
suas funções e características bem definidas. Devemos lembrar também, que depen-
dendo da geração e tecnologia empregada na fabricação daquele processador, isso
influenciará diretamente na capacidade de armazenamento de dados pelo registra-
dor bem como em seu nome.

Por exemplo, o registrador BP, na arquitetura de 16 bits, possui a mesma função
que o EBP, de 32 bits, e o RBP, de 64 bits. Entretanto, sua capacidade de arma-
zenamento muda de arquitetura para arquitetura.

Mas antes de falarmos de registradores, precisamos entender como funciona a CPU.
A unidade central de processamento de um computador possui os seguintes elemen-
tos que permitem que o mesmo pegue dados da memória e processe-os:
- Contador
- Decodificador de Instrução
- Barramento de dados
- Registradores de uso geral
- Unidade lógica e aritmética

O contador é utilizado para dizer ao computador onde está localizada a próxima
instrução a ser executada. Ao localizar tal instrução, através do endereço de
memória armazenado no contador, tal função é transferida ao decodificador, que
buscará entender o que a mesma significa. Isso inclui qual o tipo de processo
será necessário (adição, subtração e etc) e em qual local da memória os dados
necessários se encontram.

Após essas operações básicas, o barramento de dados (Data Bus) é utilizado para
fazer a conexão entre a CPU e a memória. Além da memória externa ao processador,
esse último tem alguns locais na memória chamado de registradores, como citado
anteriormente.

Os registradores de uso geral são onde as principais ações ocorrem. Operações
como adição, subtração, multiplicação, comparações lógicas e outras, utilizam os
registradores de uso geral para o processamento dos dados.

Já os registradores especiais, que são a segunda categoria de registradores
existentes, possuem propósitos bem específicos, que serão abordados mais a fren-
te.

Após a CPU recuperar todos os dados necessários, ele os transfere, bem como as
instruções decodificadas, para a unidade lógica e aritmética para o posterior
processamento. É aqui que a instrução é executada. Obviamente que essa é uma ex-
plicação bem simplória, mas já serve para compreendermos o funcionamento básico
de uma CPU.

Para já conhecermos os registradores, vamos separá-los pelas duas categorias ci-
tadas: uso geral e especiais. Alguns dos registradores de uso geral, onde pode-
mos armazenar valores para serem utilizados em operações, são os seguinte:

EAX = Extended Acumullator (registrador acumulador extendido)
EBX = Extended Base (registrador de base extendido)
ECX = Extended Couter (registrador contador extendido)
EDX = Extended Data (registrador de dados extendido)
ESI = Extended Source Index (registrador de índice de origem extendido)
EDI = Extended Destination Index (registrador de índice de destino extendido)

Como havia comentado antes, os registradores de 16 bits possuíam nomes um pouco
diferentes dos de 32 bits, por conta de sua capacidade de armazenamento. Por
exemplo, os registrador EDX possui esse nome porque faz parte de uma CPU de ar-
quitetura de 32bits de dados, quanto que se fossem apenas 16bits seu nome seria
DX.

Um gráfico tosco para entender isso seria mais ou menos assim:

---------------------------------------------------------------------------------
| 					EDX					|
---------------------------------------------------------------------------------
|					|		    DX			|
---------------------------------------------------------------------------------
|					|	 DH	     |      DL		|
---------------------------------------------------------------------------------

EDX armazenaria, por exemplo, um valor fictício de 0x00000000.

DX, que é a parte alta de EDX, armazenaria 0x0000.

DH, é a parte alta de DX, enquanto DL é a parte baixa de DX (ambos são de arqui-
tetura 8bits), e armazenam apenas 0x00 cada um.

Em adição aos registradores de uso geral, temos os registradores especiais, que
são:

EBP = Extended Base Pointer (Ponteiro de Base)
ESP = Extended Stack Pointer (Ponteiro de Stack/Pilha)
EIP = Extended Instruction Pointer (Ponteiro de Instrução)
EFLAGS

Uma coisa que precisamos ter sempre em mente, é que tanto como o EIP quanto o
EFLAGS, só poderão ser acessados atarvés de instruções especiais e bem específi-
cas, diferente dos demais registradores.

O EBP sempre aponta para a base da pilha, e também é utilizado para acessar essa
mesma pilha, apesar de também poder ser utilizado como um registrador comum (de
uso geral). Já o ESP, aponta para a posição atual da stack (pilha) e é o offset
do SS (Stack Segment).

Agora, por que precisamos conhecer os registradores para aprender assembly? Sim-
plesmente porque todas as instruções dessa linguagem, lida diretamente com tais
registradores, assim como todos os programas. A diferença, é que nas demais lin-
guagens, não precisamos conhecer dessa estrutura de baixo nível. No entanto, to-
das elas, após serem compiladas, ou interpretadas pela CPU, suas instruções vão
trabalahr diretamente com essa estrutura de funcionamento.

Caminhando em direção à linguagem propriamente dita, precisamos saber que exis-
tem duas principais sintaxes de assembly, que são diferentes uma da outra. A In-
tel, utilizada principalmente em sistemas Windows, e a AT&T, utilizada em siste-
mas GNU Linux.

E há diferenças bem importantes entre essas sintaxes. Diz-se que dificlmente al-
guém aprende a sintaxe AT&T primeiro, pois ela pode ser um pouco confusa para
iniciantes. Mas depois que se aprende, torna-se uma poderosa ferramenta. Eu, pe-
lo menos, sou adepto da sintaxe AT&T e é essa que vamos abordar nesse artigo.

Por exemplo, na sintaxe Intel, uma instrução comum ficaria assim:

instrucao destino, origem

Em AT&T é:

instrucao origem, destino

Na sintaxe AT&T,quando desejamos realizar algum tipo de endereçamento de memória,
precisamos seguir a seguintesintaxe de comando:

segmento:offset(base, indexador, escala)

Na Intel, um endereçamento ficaria assim:

[es:eax+ebx*4+100]

Já na AT&T, a mesma linha ficaria assim:

%es:100(%eax, %ebx, 2)

Uma questão importante de se lembrar, é que na sintaxe AT&T, todos os registra-
dores devem ser prefixados pelo símbolo %, enquanto que o valores literais, pelo
símbolo $. Portanto, 100 é diferente de $100, onde o primeiro é um endereço de
memória, e o segundo é um valor numeral. Outro símbolo importante, é o $0x uti-
lizado para referenciar hexadecimais.

Vamos deixar a teoria um pouco de lado e vamos ao nosso primeiro programa. Ele
fará pouca coisa por enquanto, apenas executará um exit, utilizando uma syscall
específica do sistema operacional GNU Linux. Vejamos o código, que pode ser di-
gitado utilizando o vim, vi, nano, emacs ou seja lá o que preferir:

#OBJETIVO: Programa simples que executa um exit e retorna um código de status para o kernel Linux
#
#ENTRADA: nenhuma
#
#OUTPUT: retorna um status código de status, que pode ser visto executando no terminal o comando:
#
# echo $?
#
# após a execução do programa
#
#VARIÁVEIS:
# %eax armazena o número da syscall
# %ebx armazena o status retornado
#

.section .data

.section .text

.globl _start

_start:

movl $1, %eax 		# esta é a syscall do kernel Linux para sair de um programa
movl $0, %ebx 		# este é o status que retornaremos para o SO.
			# altere esse valor, e verá coisas diferentes ao executar o
			# echo $?
int $0x80 		# isso chama o kernel para executar a syscall 1

Salve esse código como exemplo1.s e compile e linkedite-o:

# as exemplo1.s -o exemplo1.o
# ld exemplo1.o -o exemplo1

Após esse processo, para executar nosso primeiro programa, basta digitar no ter-
minal:

# ./exemplo1

Executando o programa, você perceberá que a única coisa diferente que ocorrerá,
é que seu cursor irá para a próxima linha. Isso ocorre porque nosso programa foi
feito apenas para execurtar um exit.

Para visualizarmos o código de status retornado para o SO, basta digitarmos no
terminal:

# echo $?

Se tudo correr bem, você terá um "0" como saída. Esse é o código de status ne-
cessário a ser passado para o kernel, avisando de que tudo está ok para sair do
programa.

Agora vamos à explicação das partes do programa

Tudo o que possui uma # no início, é comentário. Acho que não há muito o que se
dizer sobre isso ;-)

Logo depois dos comentários, temos algums seções específicas. E sempre que há
algo que comece com .section, não é uma instrução para que o computador execute,
mas sim uma instrução diretamente inserida para o assembler, como é o caso das
seções abaixo, que quebra o programa em pedaços (seções) diferentes:

.section .data = esse comando cria a seção "data", onde listamos quaisquer con-
tainer de memória que precisaremos para os dados.

.section .text = é nessa seção onde inserimos as instruções a serem executadas.

.globl _start = .globl é uma instrução que diz que o símbolo _start não deve ser
descartado após a compilação e linkedição do código. E _start é um símbolo que
marca um determinado local da memória que servirá como referência para a execução
de determinadas instrução, que vem logo abaixo.

_start:  = é onde definimos o valor do label _start, que terá vinculado à si, o
conjunto de instruções que seguem logo abaixo. Podemos traçar um paralelo com as
funções que utilizamos em C.

movl $1, %eax = aqui temos a instrução movl, seguido de dosi operadores. Os ope-
radores podem ser números, referência a locais da memória ou registradores. Nes-
se caso, inserimos o valor 1 no registrador EAX. Esse número é o valor de uma
syscall específica (exit - para conhecer os valores das demais syscall, execute
o comando "cat /usr/include/asm-i386/unistd.h" no terminal Linux).

Bem, com o comando acima, dizemos ao programa qual syscall será executada pelo
kernel ao ser chamado. No entanto, essa syscall precisa de um parâmetro para di
zer que está tudo ok e o programa poderá ser finalizado. Esse parâmatro será ar
mazenado em outro registrador, com a próxima instrução:

movl $0, %ebx = aqui, inserimos o parâmetro através do valor "0" no registrador
EBX. Isso é o que dirá para o kernel que está td ok para o exit ser executado.
Lembra um pouco o "return (0)" do C.

A próxima instrução é a que faz a sinalização para chamar o kernel e executar a
syscall exit:

int $0x80 = int é o mesmo que interrupt. Uma interrupção corta o fluxo de funci-
onamento de um programa e passa o comando para o Linux, o que em nosso caso fará
com que o kernel execute a syscall 1 (exit). E o valo $0x80 é o número de inter-
rupção utilizado para que essa passagem de controle para o Linux, aconteça. Não
se preocupe ainda do porque ser esse valor, e não outro, porque isso não importa,
apenas precisa lembrar-se que é a instrução de interrupção padrão utilizada pelo
Assembly AT&T.

Se você conseguiu entender a explicação do que foi feito até aqui, poderá esfor-
çar-se mais um pouco e entenderá o próximo código... Esse novo programa, tem como
função ler algo digitado pelo usuário, armazená-lo e depois exibi-lo:

#OBJETIVO: Ler uma string digitada pelo usuário
#
#ENTRADA: qualquer string que pode ser digitada
#
#OUTPUT: retorna o que foi digitado pelo usuário
#
#VARIÁVEIS:
# string = armazena a string digitada
# tam = armazena o tamanho da variável string
#

.section .data
string: .string "Digite algo:\n"
tam: .long . - string

.section .text

.globl _start

_start:

movl $4, %eax		# insere o valor 4, para a chamada da syscall write no EAX
movl $1, %ebx		# passa o parâmetro da syscall 4 para que algo seja exibido
leal string, %ecx	# carrega o endereço de memória do ECX e exibe o conteúdo de string
movl tam, %edx		# armazena o valor de tam no EDX
int $0x80

movl %esp, %ecx 	# Salva o Stack Pointer em %ecx
subl $10, %esp		# Reserva 10 bytes para o usuario digitar no stack
movl $3, %eax 		# insere o valor da syscall read (3) no EAX, o que for escrito tbm será armazenado em EAX
movl $9, %edx 		# Tamanho do que vai ser lido para EDX
int $0x80

movl %eax, %edx 	# Move o que foi digitado para EDX.
movl $4, %eax		# syscall write
movl $1, %ebx
int $0x80

movl $0x1, %eax
movl $0x0, %ebx
int $0x80

Salve como leia.s, compile, linkedite e execute:

# as leia.s -o leia.o
# lf leia.o -o leia
# ./leia

Vamos ver agora um terceiro programa. Simples também, mas que resgata uma infor-
mação que já se encontra em determinados segmentos de memória do processador: o
seu fabricante.

#OBJETIVO: extrair no nome do fabricante do processador
#
#ENTRADA: nenhuma
#
#OUTPUT: nome do fabricante do processador
#
#VARIÁVEIS:
# output = armazena o nome do fabricante
#

.section .data
output:
   .ascii "O ID do fabricante do processador e 'xxxxxxxxxxxx'\n"

.section .text

.globl _start
_start:
        nop
        mov  $0, %eax
        cpuid
        movl $output, %edi
        movl %ebx, 28(%edi)
        movl %edx, 32(%edi)
        movl %ecx, 36(%edi)
        movl $4, %eax         # USAR SYSCALL 4 (WRITE) P/ IMPRIMIR NA TELA
        movl $1, %ebx         # IMPRIMIR EM STDOUT (FD 1)
        movl $output, %ecx    # ENDERECO INICIO DO TEXTO A SER IMPRESSO
        movl $42, %edx        # COMPRIMENTO DO TEXTO A SER IMPRESSO
        int  $0x80            # CHAMA SYSCALL DO LINUX

        movl $1, %eax         # USAR SYSCALL 1 (EXIT) P/ FINALIZAR PROGRAMA
        movl $0, %ebx         # SAIR COM ERROR CODE = 0
        int  $0x80            # CHAMAR SYSCALL DO LINUX

Salvar como cpuid.s. Para gerar o executável:

# as cpuid.s -o cpuid.o
# ld cpuid.o -o cpuid

Executando:

# ./cpuid

Agora para o nosso quarto programa, vamos criar um arquivo de texto e escrever
algo dentro do mesmo.

#OBJETIVO: escrever algo dentro de um arquivo txt
#
#ENTRADA: nenhuma
#
#OUTPUT: arquivo open.txt com uma frase de conteúdo
#
#VARIÁVEIS:
# string1 = mensagem a ser exibida
# string2 = o que será escrito dentro do arquivo
# tam1	= tamanho de string1
# tam2 = tamanho de string2
# arq = path e nome do arquivo
# perm = modo do arquivo, que estará como leitura/escrita
#

.section .data
string1: .string "Criar um arquivo e inserir conteúdo \n"
tam1: .long . - string1
string2: .string "Cogumelo binário\n"
tam2: .long . - string2
arq: .string "/tmp/arquivo.txt"
perm: .string "O_RDWR"

.section .text

.globl _start
_start:

movl $4, %eax		# syscall write
movl $1, %ebx
leal string1, %ecx
movl tam1, %edx
int $0x80

movl $5, %eax 		# syscall open (5)
movl $arq, %ebx 	# arquivo que será aberto
movl $perm, %ecx 	# modo do arquivo
movl $0, %edx 		# Permissão 0
int $0x80

movl %eax, %esi 	# Move o retorno da funcao open para ESI

movl $4, %eax		# syscall write, para efetuar a escrita no arquivo
movl %esi, %ebx 	# local de escrita, arquivo.txt
leal string2, %ecx 	# escrita do conteúdo de string2 para dentro do arquivo
movl tam2, %edx 	# O tamanho da variavel
int $0x80

movl $6, %eax		# syscall close (6)
movl %esi, %ebx 	# Fecha o arquivo
int $0x80

movl $1, %eax
movl $0, %ebx
int $0x80

Vamos ficar por aqui com esse paper, mas há muito mais coisas a serem ditas sobre
assembly. A gente nem sequer chegou nos loops e estruturas condicionais com JMP.
Mas acredito que tenha sido possível ao menos fazer com que tenham um primeiro
contato com essa linguagem tão poderosa. Quem sabe em um outro paper, não nos
aprofundamos mais no assunto? Até a próxima!

           _____
         .:     :.
        (_________)
     __     | |
   .:  :.   | |
  (______)  / /
     ||    / /
     ||   / / _
   _ ||  | | (_)          ,
  (_) \\010|  |         .;       _..--,
   \\.0101010110.      ;':      '  ',,,\    .^.                      .^.     .^.
   .0101011010101.     ;_;             '|_          ,'
  .100101010101011.    |              .;;;;.,     ,':     .^.      '.   .^.
                   ,;::;:::..      ..;;;;;;;;.. :_,'             .;'
    .^.          .'     '':::;._.;;::::''''':;::;/'             .;:;
                .          ':::::::;;'      ':::::          ...;:               .^.
  .^.                         ':::'          /':::;      ..:::::;:..::::::::..      .^.
          .^.          .^.       ;         ,'; ':::;;...;::;;;;' ';;.        .^.
                            ,,,_/          ; ;   ';;:;::::'          '.
   .^.                   ..'  ,'           ;'         ''\             '
         .^.            '  '''     .^.    '              ;'.    .^.     .^.
                                                         : :        .^.