Crie um arquivo com extensão .asm (é recomendado, não é obrigatório), como por exemplo olamundo.asm.
Podemos usar o Sublime Text para editar os códigos, para deixar ele editável para Assembly, siga as instruções aqui: https://baixonivel.wordpress.com/2016/12/02/ferramentas-preparando-o-sublime-text-para-edicao-de-codigo-assembly/
Para colocarmos comentários no Assembly, fazemos assim, usando ponto e vírgula:
; Isso aqui é comentário
; Isso também é comentário
Basicamente, um programa Assembly é definido em duas sessões, a .data (onde estarão nossas variáveis), e a .text, assim:
section .data:
section .text:
Os dados colocados dentro delas é recomendado usarmos tabulações, mas não é obrigatório também.
Altere o código assim:
section .data:
; Isso define a variável com o nome msg do tipo binário
; e que ela está na posição 0xa
msg db "Olá, Mundo!", 0xa ; Cada caractere ocupa 1 byte, msg é um ponteiro pra mensagem
; Isso define o tamanho da variável:
len equ $- msg
section .text:
; Primeiro, nessa sessão do programa, ele entra nesse método:
global _start
; Criação do método:
_start:
; Acumulador de registro de dados. Ele move edx para len, ecx pra msg, etc.:
mov edx, len
; Acumulador de registro de conta (operações string). Move ecx para nossa mensagem (onde ela é escrita):
mov ecx, msg
; Acumulador de registro base. Nosso arquivo de saída (no caso nossa mensagem):
mov ebx, 1
; Acumulador pra operações numéricas. Manda informação pro sistema (no caso de tamanho):
mov eax, 4
; Endereço do Kernel (manda pro Kernel executar os códigos de _start:
int 0x80
; Saída do programa, para finalizar ele:
mov eax, 1
mov ebx, 0
int 0x80
PS: Os programas com eax, ebx, ecx e edx são para manter a compatibilidade com 32 bits, para sistemas 64 bits usamos rax, rbx, rcx e rdx. Os sistemas de 16 usavam ax, bx, cx e dx.
Para compilar o programa digite nasm -f elf32 olamundo.asm -o olamundo.o
(elf64 se for Linux de 64 bits, em Windows é win32 e win64). Isso chama o compilador do sistema. O arquivo com extensão .o é o resultado da saída do que escrevemos em chamada de máquina.
Para montar o código objeto compilado, digite ld olamundo.o -s -o olamundo
(o segundo olamundo é o nome do executável, no Windows deve ser olamundo.exe, e talvez gerar um .obj ao invés de .o). Caso esteja num sistema 64 bits e queira um executável de 32, digite ld olamundo.o -m elf_i386 -o olamundo
.
Basicamente, o que vai em .data é onde declaramos as variáveis, e o que vai em .text é onde escrevemos nosso programa, onde ele inicializa o método _start.
PS: Caso esteja usando o Windows, pode ser necessário definir essa função no .text:
segment .text
global _WinMain@16
_WinMain@16:
mov eax, 0
ret 16
Para entender algumas instruções Assembly, leia esse artigo: http://numaboa.com.br/informatica/tutos/assembly/1122-instrucoes-comuns
Crie um novo arquivo chamado entrada.asm e coloque esse código:
; Programa para Entrada de Dados
; Variáveis declaradas e inicializadas:
SYS_EXIT equ 1
RET_EXIT equ 5
SYS_READ equ 3
SYS_WRITE equ 4
STD_IN equ 0
STD_OUT equ 1
MAX_IN equ 10
; Sessões:
segment .data
msg db "Entre com seu nome: ", 0xA, 0xD
len equ $- msg
; Sessão para inicialização de informações que serão usadas
segment .bss
nome resb 2
segment .text
global _start
; Função:
_start:
; Usando as variáveis fica mais fácil saber o que cada comando faz:
mov eax, SYS_WRITE
mov ebx, STD_OUT
mov ecx, msg
mov edx, len
int 0x80
; Entrada de dados, na ordem leitura, entrada, variável e máximo de caracteres:
mov eax, SYS_READ
mov ebx, STD_IN
mov ecx, nome
mov edx, MAX_IN
int 0x80
exit:
mov eax, SYS_EXIT
mov ebx, RET_EXIT
int 0x80
Agora compile o arquivo digitando nasm -f elf32 entrada.asm
e monte digitando ld entrada.o -s -o entrada
.
Para entendermos, vamos supor esse programa em C:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
return 2;
}
Compile esse programa para vermos o resultado. Se estiver no Linux dê echo $?
para vermos a saída de retorno dele.
Agora um programa semelhante em Assembly
section .text
global _start
_start:
mov eax, 1
mov ebx, 2 ; Valor de retorno ao método
int 0x80
Monte e compile ele.
Agora pra comparação, rode os programas criados em Assembly e dê o echo $? pra ver a saída deles. Mas isso não significa que eles são iguais, pra comparação dê o comando file
em cada um deles.
O programa em C depende de várias bibliotecas, para ver elas digite ldd progC
, se vermos os programas em Assembly eles não terão essas bibliotecas.
Para vermos o código Assembly desmontado desses programas, digitamos objdump -dM intel progA
, veremos que os programas em Assembly tem exatamente o que escrevemos ali, agora faça o mesmo no programa em C e veja quanto código foi gerado.
Assim, os programas feitos diretamente em Assembly são menores, já que os programas de linguagens de alto e médio nível (como o C), dependem de várias bibliotecas. Também podemos descobrir, por exemplo, se um programa tem vírus.
Faça um programa em Assembly com esse código:
section .data
; dd - Define double word - 4 bytes
; db - Define byte - 1 byte
; dw - Define word - 2 bytes
; dq - Define quad word - 4 bytes
; dt - Define ten word - 10 bytes
x dd 50 ; Use dq em sistemas 64 bits
y dd 10 ; Idem
msg1 db "X maior que Y", 0xa
len1 equ $- msg1
msg2 db "Y maior que X", 0xa
len2 equ $- msg2
section .text
global _start
_start:
mov eax, DWORD [x] ; Para 64 bits, use QWORD
mov ebx, DWORD [y]
cmp eax, ebx ; Pode ser maior, menor ou igual
; Chamada de método e compara números:
jge maior ; Significados: je = | jg > | jge >= | jl < | jle <= | jne !=
mov edx, len2
mov ecx, msg2
; Pula pra outro método:
jmp final
; Definição dos métodos:
maior:
mov edx, len1
mov ecx, msg1
final:
mov ebx, 1
mov eax, 4
int 0x80
mov eax, 1
int 0x80
Agora tente mudar os números.
Pra entender, caso ele compare os números maior ou igual, ele executará a função maior, senão ele continuará até o fluxo do programa e saltará pra função final.
Primeiro, crie esse programa para incrementar um número:
section .data
v1 dw '5', 0xa ; 5 no formato variável
section .text
global _start
_start:
mov eax, [v1] ; Endereço da variável
sub eax, '0' ; Converte para inteiro
add eax, 1 ; Adiciona um à variável
add eax, '0' ; Convete para caractere novamente
mov [v1], eax
mov ecx, v1
mov eax, 4
mov ebx, 1
mov edx, 1
int 0x80
_final: ; Label indicando finalização
mov eax, 1
mov ebx, 0
int 0x80
Monte e compile ele.
Podemos colocar funções, coloque isso abaixo do label final:
converter_valor:
mov eax, [v1]
sub eax, '0'
add eax, 1
add eax, '0'
mov [v1], eax
ret ; Retorno da função
mostrar_valor:
mov eax, 4
mov ebx, 1
mov edx, 1 ; Quantidade de caracteres a ser mostrada
int 0x80
ret
E em _start, coloque assim:
_start:
; Call indica invocação de funções:
call converter_valor
mov ecx, v1
call mostrar_valor
int 0x80
Agora altere a section .data assim:
section .data
v1 dw '105', 0xa
E a mostra_valor assim:
mostrar_valor:
mov eax, 4
mov ebx, 1
mov edx, 3 ; Quantidade de caracteres a ser mostrada
int 0x80
ret
Só que ele faz o incremento no primeiro elemento, e não no último. Isso corrigiremos futuramente.