Primeiro Programa Assembly no Linux com NASM
Programar em Assembly é a forma mais próxima de "conversar" diretamente com o processador do seu computador. É uma habilidade que desmistifica o funcionamento do software em seu nível mais fundamental. Neste guia, vamos usar o montador NASM no ambiente Linux (Ubuntu) para criar, passo a passo, nosso primeiro programa executável.
Instalação
Primeiro, precisamos garantir que as ferramentas necessárias estejam instaladas. Abrimos o terminal e executamos dois comandos. O primeiro sincroniza a lista de pacotes do seu sistema:
sudo apt update
O segundo instala o Netwide Assembler (NASM):
sudo apt -y install nasm
Para confirmar que a instalação foi bem-sucedida, você pode verificar a versão do NASM:
user@programicio:~$ nasm -v NASM version 2.15.05 user@programicio:~$
A versão do NASM que instalamos com o comando apt é a versão oficial mantida e testada pela equipe do Ubuntu. Ela é garantida para ser estável e compatível com o sistema, sendo perfeita para aprender e para a maioria dos usos.
Contudo, essa versão pode não ser a mais recente lançada pelos desenvolvedores do NASM. Para usuários avançados que precisam das últimas funcionalidades ou otimizações, a versão mais atual está sempre disponível para download no site oficial do NASM. Para tudo o que faremos neste guia, a versão do repositório é mais do que suficiente.
O Primeiro Código com NASM
Vamos começar com um programa que não faz nada além de encerrar sua própria execução de forma limpa. Em um diretório de sua escolha, crie o arquivo hello.asm com o seguinte conteúdo:
global _start ; Torna o ponto de entrada visível para o linker
section .text ; Início da seção de código executável
_start: ; Define o ponto de entrada do programa
mov rax, 60 ; Código da syscall 'exit'
mov rdi, 22 ; Código de retorno para o sistema
syscall ; Pede ao kernel para executar a syscallVamos entender a estrutura:
- Seções: O código é organizado em seções. A seção
.texté onde ficam as instruções que a CPU irá executar. - Ponto de Entrada: O rótulo
_start:marca o início da execução. A diretivaglobal _startinforma ao sistema que este é o "portão de entrada" do nosso programa.
A lógica central é a chamada de sistema (system call ou syscall). Pense nela como preencher um formulário para o kernel do Linux e pedir que ele faça algo em nosso nome.
mov rax, 60: O registradorraxfunciona como o campo "Assunto" do nosso formulário. O valor60é o código que diz ao kernel: "Eu quero executar a ação de sair (exit)".mov rdi, 22: O registradorrdicontém o primeiro argumento. Para a chamadaexit, este é o código de status que o programa retornará ao sistema. Escolhemos22arbitrariamente.syscall: Esta instrução é o botão "Enviar". Ela pausa nosso programa, entrega o "formulário" (os registradores) ao kernel, que então executa a ação solicitada.
Compilando e Executando
Nosso código-fonte (.asm) precisa passar por um processo de duas etapas para se tornar um programa que o sistema possa rodar.
1. Montagem: Traduzimos nosso código Assembly legível para código de máquina.
nasm -f elf64 hello.asm -o hello.o
-f elf64: Especifica que queremos gerar um arquivo no formato ELF64, o padrão para executáveis de 64 bits no Linux.-o hello.o: Define o nome do arquivo de saída. O resultado é um arquivo objeto (.o), que é uma tradução do nosso código, mas ainda não está pronto para ser executado.
2. Ligação (Linking): Pegamos o arquivo objeto e o transformamos em um executável final.
ld -o hello hello.o
O linker ld "encaderna" nosso código objeto, adicionando as informações necessárias para que o sistema operacional saiba como carregá-lo e executá-lo. Agora, temos um arquivo hello executável.
Vamos rodá-lo e verificar o código de saída:
user@programicio:~/asm$ ./hello user@programicio:~/asm$ echo $? 22 user@programicio:~/asm$
O comando echo $? confirma que nosso programa retornou o código 22, exatamente como programado.
Adicionando Saída ao Console
Agora, vamos fazer nosso programa se comunicar com o mundo. Modifique o hello.asm para exibir uma mensagem:
global _start
section .data ; Seção para dados inicializados
message: db "Hello Programicio!", 10 ; Nossa string e uma quebra de linha
section .text
_start:
; --- Syscall para escrever no console ---
mov rax, 1 ; Código da syscall 'write'
mov rdi, 1 ; Argumento 1: Descritor do arquivo (1 = stdout)
mov rsi, message ; Argumento 2: Endereço da nossa mensagem
mov rdx, 19 ; Argumento 3: Tamanho da mensagem em bytes
syscall
; --- Syscall para sair do programa ---
mov rax, 60 ; Código da syscall 'exit'
mov rdi, 0 ; Código de retorno 0 (convenção para sucesso)
syscallAs novidades aqui são:
- Seção
.data: Uma nova seção para armazenar dados que já têm um valor no início do programa. Aqui, criamosmessage, uma sequência de bytes contendo nossa string. O, 10no final é o código ASCII para o caractere de quebra de linha (\n). Syscall
write: Para escrever no console, preenchemos um novo "formulário":rax = 1: O código da syscallwrite.rdi = 1: O destino.1é o descritor de arquivo para a saída padrão (stdout), ou seja, o terminal.rsi = message: O endereço de memória onde nossa mensagem começa. É crucial entender que passamos a localização, não o texto em si.rdx = 19: O número de bytes a serem lidos a partir desse endereço (18 caracteres da frase + 1 byte da quebra de linha).
Note que agora usamos mov rdi, 0 na chamada exit. Retornar 0 é a convenção universal para indicar que um programa foi executado com sucesso.
Repetimos o processo de montagem e ligação e executamos o resultado:
user@programicio:~/asm$ nasm -f elf64 hello.asm -o hello.o user@programicio:~/asm$ ld -o hello hello.o user@programicio:~/asm$ ./hello Hello Programicio! user@programicio:~/asm$
Resumo
- Estrutura em Seções: Programas NASM se organizam em seções como
.text(código) e.data(dados). - Ponto de Entrada: O rótulo
_start, declarado comglobal, é o início da execução no Linux. - Chamadas de Sistema: São a forma de interagir com o kernel do sistema operacional para tarefas como sair (
exit, syscall 60) ou escrever na tela (write, syscall 1). - Convenção de Registradores: O número da syscall vai em
rax, e os argumentos emrdi,rsi,rdx, etc. - Compilação em Duas Etapas: O código é primeiro montado (
nasm) para um arquivo objeto (.o) e depois ligado (ld) para criar o executável final.