Aprenda Assembly

  • Página Inicial
  • Contato!
  • Tudo sobre Assembly Parte 1!
  • Tudo sobre Assembly Parte 2!
  • Tudo sobre Assembly Parte 3!
  • Tudo sobre Assembly Parte 4!
  • Tudo sobre Assembly Parte 1

    Hello World (32 bits)

    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

    Entrada de Dados (32 bits)

    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.

    Entendimento (32 bits)

    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.

    Comparação (32 bits)

    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.

    Introdução à Funções (32 bits)

    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.