terça-feira, 1 de abril de 2014

ASSEMBLY com NASM

Eu sou o pão vivo que desceu do céu; se alguém comer deste pão, viverá para sempre; e o pão que eu der é a minha carne, que eu darei pela vida do mundo. ( João 6:51 )

Antes de dar continuidade á leitura desta postagem, seria bom voçê já ter uma noção de programação. Já tem? Pois bem, então, esqueça. Estamos diante de um paradigma totalmente diferente. Assembly é uma linguagem considerada de baixo nível, ou seja, mais próxima da linguagem de máquina (Que só a máquina entende). PHP, JAVA e PYTHON podem ser consideradas linguagem de alto nível (Muito mais próximo do entendimento humano).  Ela tem algumas peculiaridades bem interessantes, que deixam qualquer programador acostumado com linguagens de alto nível de cabelo em pé.

Este tutorial serve para voçê ter uma noção de como trabalha e se desenvolve nesta linguagem tão clássica. Voçê terá uma noção teórica importante antes de por a mão na massa, e alguns exemplos para analisar e ir treinando.

Esta linguagem, hoje em dia, é utilizada para escrever softwares que se comunicam diretamente com o hardware, como, por exemplo: controle remoto, microcontrolador, etc.

Pra começar, voçê deve saber que, uma vez que o código-fonte já esteja sido escrito, ele precisa ser compilado, ou seja, precisa ser transformado num programa executável, para poder rodar. Os programas que fazem este papel chamam-se ASSEMBLADORES. Assemblador, em poucas palavras, converte a linguagem assembly em linguagem de máquina, por processo conhecido como montagem. A máquina só entende agrupamentos de bits 0 e 1. O assemblador converte o seu código em bits 0 e 1, compreensíveis ao seu computador.

Para utilizar nossos exemplos, utilizaremos o assemblador NASM, que pode rodar tanto em WINDOWS quanto em LINUX. Está disponível no seguinte endereço:  Site oficial da NASM.

Sugiro que baixe e instale este programa antes de dar continuidade á este artigo.

Pois bem, antes de continuar, vamos entender o que é registrador, pois, voçê vai manipulá-los direto. Registrador é a unidade de armazenamento interno do processador. Em poucas palavras, são os espaços para manipular dados. Normalmente, através das  instruções em assembly, estes registradores são manipulados. Fique esperto que, alguns registradores tem finalidade específica. Por exemplo: EAX pode ser usada para realização de operações aritméticas, mas também é utilizadas para fazer chamadas ao núcleo do sistema operacional, como por exemplo: Exibir algo na tela, finalizar execução, etc. Segue ao lado uma lista destas interrupções que se deve setar no registrador EAX:  Lista de interrupções.

É preciso lembrar que, existem registradores de 32 bits, de 16 bits e de 8 bits. Por exemplo: EAX é um registrador acumulador de 32 bits, AX é o mesmo só que em 16 bits; AL e AH  são o mesmo em 8 bits. Neste caso, AX é composto por AX e AL, tendo 8 bits cada. Uma explicação mais detalhada sobre cada registrador voçê vê em:  Lista de registradores

Agora sim, vamos por  a mão na massa. Lembrando que, cada assemblador possui uma estrutura própria de programa. No nosso caso, a estrutura própria é a seguinte:

section .data
 ; Dados inicializados
section .bss
 ; Dados não-inicializados

section .text
 ; Conteúdo do programa
global _start ; Definindo como global, para ser interceptado por alguma biblioteca em C
 
_start:
 ; Conteúdo mesmo é aqui

 

Pois bem, segue abaixo um programa que recebe dois números (Que voçê ira digitar), e irá retornar a soma.

 

 

 

section .data
 ; Onde ficam dados inicializados. Normalmente o padrão para declarar variável é:
 ; NOME TIPO (POde ser DB-BYTE,DD-DOUBLE WORD,DW-WORD) VALOR
 p DB "Digite um número: "
 len_p  equ $-p ; Tamanho da variável p. É necessário para passar ao registrador edx
 pp DB "Digite o segundo número: "
 len_pp equ $-pp

 m DB "Resultado: "
 len_m equ $-m




section .bss
 ; Reservando espaço de 1 byte para posterior uso. No nosso caso, troca-se apenas o D por RES
 numero RESB 2
 resultado RESB 2
 segundo RESB 2

section .text

global _start

_start:
 ; Qualquer instrução do trecho de programa _start ficará aqui. Neste caso, _start é um rótulo dado á este trecho específico de programa. _start é o fluxo principal do programa


; Em instruções que se exibe algo na tela. O EAX possui valor 4, que é a interrupção para se exibir algo na tela. EDX armazena a quantidade de caracteres a serem exibidas
 mov edx, len_p ; Tamanho da string que será exibir
 mov ecx, p ; Conteúdo da mensagem
 mov eax, 4 ; Chamada ao núcleo do sistema para exibir algo na tela
 mov ebx, 1 ; 0 = NADA; 1 = STDOUT (Saída); 2 = STDIN (Entrada)  
 int 0x80 ; CHamada ao núcleo do sistema, que recebe o valor contido em eax para executar

 ; Trecho abaixo  trata de receber o número digitado pelo usuário e o armazena na variável numero
 mov eax, 3 ; Indica ao núcleo que é pra receber um valor preenchido pelo usuário
 mov ebx, 2 ; STDIN (Preparar o programa para uma entrada de texto)
 mov edx, 1 ; Tamanho do campo recebido
 mov ecx, numero ; Armazena a entrada na variável numero
 int 0x80 ; Interrupção


 ; Exibindo na tela a segunda mensagem
 mov edx, len_pp
 mov ecx, pp
 mov eax, 4
 mov ebx, 1
 int 0x80

 ; Recebendo o segundo parâmetro
 mov eax, 3
 mov ebx, 2
 mov edx, 2
 mov ecx, segundo
 int 0x80

 ; Exibindo terceira mensagem
 mov edx, len_m
 mov ecx, m
 mov eax, 4
 mov ebx, 1
 int 0x80


 ; Efetuando a soma
 mov bl, [segundo] ; Atribuindo o valor da variável segundo ao registrador BL
 mov al, [numero] ; Atribuindo o valor da variável numero ao registrador AL
 
 sub bl, '0' ; Convertendo valor de ASCII em decimal para fins de operação


 add al, bl ; Efetuando a soma, sendo que, o resultado fica armazenado no registrador al
 
 mov [resultado], al ; Movendo o valor de al na variável resultado


 ; Trecho abaixo exibe o resultado na tela
 mov ecx, resultado
 mov edx, 2
 mov eax, 4
 mov ebx, 1
 int 0x80

 mov eax,1 ; SAIDA
 int 0x80

 

Para compilar  o programa acima, copie e cole o código acima em seu editor de texto, e salve com o nome de soma.asm. Todo código-fonte em assembly deve ter a extensão .asm.

Em seguida, execute os seguintes comandos em linha de comando:

 

nasm -f elf64 soma.asm // Para compilar
ld soma.o -o soma // Para gerar o executável
./soma   // Para executar

// Lembrando que o comando acima serve para LINUX, não sei como deve ser no WINDOWS

 

Segue outro programa que calcula a área de um triângulo tendo a base e a altura da mesma.

 

 

section .data
 msg DB "Digite a base do triangulo" ,0xa
 l_msg equ $ - msg
 msg2 DB "Digite a altura do triangulo", 0xa
 l_msg2 equ $ - msg2
 msg3 DB "Resultado", 0xa
 l_msg3 equ $ - msg3

 divisor DB '2'
 
section .bss
 base RESB 1
 altura RESB 1
 area RESB 2

section .text

global _start
 
_start:
 mov eax, 4
 mov ebx, 1
 mov edx, l_msg
 mov ecx, msg
 int 0x80

 mov eax, 3
 mov ebx, 2
 mov ecx, base
 mov edx, 2
 int 0x80

 mov eax, 4
 mov ebx, 1
 mov edx, l_msg2
 mov ecx, msg2
 int 0x80

 mov eax, 3
 mov ebx, 2
 mov ecx, altura
 mov edx, 2
 int 0x80


 ; Multiplicando base e altura
 sub [base], byte '0'
 sub [altura], byte '0'
 mov al, [base]
 mov bl, [altura]
 mul bl  ;AL = AL * BL  OU AX = AL * BL
 
 ; Dividindo o produto por 2
 sub [divisor], BYTE '0' ; Conversão de decimal para ASCII

 mov bl, [divisor]

 div bl ; Divisão do acumulador(AX) pelo divisor (bl) . No AX, está o resultado da multiplicação 
 add al, '0'
 mov [area], al

 mov eax, 4
 mov ebx, 1
 mov edx, l_msg3
 mov ecx, msg3
 int 0x80

 mov eax, 4
 mov ebx, 1
 mov edx, 2
 mov ecx, area
 int 0x80
 
 jmp sair

sair:
 mov eax, 1
 mov ebx, 0
 int 0x80

 

 

 

Caso queira conhecer um pouco mais desta linguagem, sugiro os seguintes links abaixo:

 

Tutorial de ASSEMBLY Uma breve introdução ao ASSEMBLY

Segue abaixo uma lista de exemplos que fiz para aprender a usar esta ferramenta:

Outros exemplos

Nenhum comentário:

Postar um comentário