diff --git a/curso/aula-00.org b/curso/aula-00.org new file mode 100644 index 0000000..53431e9 --- /dev/null +++ b/curso/aula-00.org @@ -0,0 +1,1352 @@ +#+title: 0 -- Introdução à linguagem Assembly (NASM) +#+author: Blau Araujo +#+email: cursos@blauaraujo.com + +#+options: toc:3 + +* Objetivos + +- Apresentar as características e os elementos básicos da linguagem Assembly. +- Diferenciar conceitos da programação em alto e baixo nível. +- Conhecer as características da implementação NASM para Linux 64 bits. +- Aprender as instruções e diretivas essenciais para começar a programar. +- Criar executar um primeiro programa em Assembly. + +* O que é Assembly + +Assembly é uma linguagem de programação que oferece formas de representar, em +texto legível, os códigos numéricos das operações que o hardware da máquina é +capaz de processar. A rigor, qualquer linguagem faz isso, mas um programa em +Assembly se diferencia por expressar instruções que correspondem, cada uma, a +apenas um passo daquilo que o processador (a CPU) deve fazer -- sem abstrações +e numa relação praticamente direta com o código de máquina. + +** Linguagem dependente da arquitetura + +Como representa diretamente as instruções executadas por um processador, +a linguagem Assembly depende necessariamente das características da +arquitetura desse processador. Em especial, cada CPU é projetada com seu +próprio conjunto de instruções (ISA, do inglês /Instruction Set Architecture/). +A ISA é, por definição, a própria interface entre o hardware e o software +de um sistema computacional. É ela que define, entre outras coisas: + +- Quais operações a CPU pode executar; +- Quais registradores estão disponíveis; +- Como as instruções são codificadas em binário; +- Como os operandos são manipulados; +- Como a memória pode ser utilizada. + +Sendo assim, não há apenas uma linguagem Assembly, a não ser como um termo +genérico que engloba diversas linguagens, todas baseadas na representação +simbólica das operações de arquiteturas específicas. + +No contexto dos nossos estudos, a linguagem Assembly que utilizaremos é +aquela voltada à arquitetura Intel x86_64, tal como implementada no /montador/ +NASM (/Netwide Assembler/), e é a ela que estaremos nos referindo todas as +vezes que dissermos /"Assembly"/. + +** Linguagem de montagem + +A palavra /"assembly"/ vem do inglês e significa /"montagem"/. Portanto, o nome +deriva da primeira etapa do processo que transforma os programas escritos na +linguagem (os /códigos-fonte/) nos códigos binários executáveis pela máquina +(/código de máquina/). Esse processo se chama /"montagem"/ e é feita por um +programa chamado /"montador"/ (ou /"assembler"/, em inglês). + +#+begin_quote +*Importante!* A linguagem se chama /"Assembly"/, não /"assembler"/, que é como +chamamos o programa que implementa a linguagem e faz a montagem do código +de máquina. +#+end_quote + +** Classificações da linguagem + +O Assembly pode ser classificado como uma linguagem... + +*** De baixo nível de abstração (segunda geração) + +Trabalha diretamente com os recursos da arquitetura da máquina, como +registradores, endereços de memória e instruções específicas da CPU. Não +há camadas intermediárias entre o código e o hardware. + +*** Imperativa + +As instruções são dadas em uma sequência explícita, descrevendo passo a +passo o que deve ser feito. O controle do fluxo é manual, baseado em saltos +e comparações, sem abstrações de alto nível. + +*** De tipagem inexistente + +Não há distinção formal entre tipos de dados. Tudo é interpretado como +sequências de bytes, cabendo a quem programa decidir como os dados devem +ser lidos ou manipulados. + +*** Com gerenciamento manual de memória + +Não há alocação automática ou coleta de lixo. O programador deve lidar com +endereços de memória diretamente, reservando, acessando e liberando espaços +manualmente, de acordo com a necessidade. + +*** Compilada via montagem + +O código Assembly não é interpretado nem compilado. Em vez disso, ele é +traduzido diretamente para código de máquina pelo montador (/assembler/), +que gera um binário executável diretamente ou um arquivo binário do tipo +/objeto/. Para que tenhamos um executável de fato, ainda pode ser necessário +realizar uma etapa de /ligação/, em que utilizamos um /editor de ligações/ para +adequar o binário montado às especificações do formato de executáveis do +sistema operacional. + +*** Paradigmas + +Também pode ser considerada uma linguagem procedural, mas com ressalvas, já +que é uma linguagem que só oferece instruções e, portanto, não impõe nenhum +paradigma. Isso quer dizer que nós podemos escrever programas de forma +procedural, mas também de forma totalmente linear e até orientada a macros. + +*** De uso específico + +Apesar de ser uma linguagem completa, o Assembly é geralmente usado para +finalidades bem específicas, como a implementação de trechos críticos de +desempenho, rotinas de acesso direto ao hardware, criação de módulos de +sistemas operacionais ou interação com o sistema em níveis muito mais +baixos. Raramente é usado para o desenvolvimento de aplicações inteiras, +principalmente devido à sua complexidade, verbosidade e baixa portabilidade. + +** Comparação com outras linguagens + +Por ser uma linguagem de segunda geração, o Assembly opera em um nível muito +mais próximo do hardware do que linguagens de alto nível. Isso traz uma série +de diferenças fundamentais em relação à forma como os programas são escritos, +estruturados e compreendidos. + +Aqui estão algumas dessas diferenças... + +*** Não há estruturas de controle + +Em Assembly nós não temos estruturas de controle abstratas, como =if=, =case=, +=for= ou =while=. Em vez disso, todo o controle de fluxo do programa tem que +ser implementado com instruções de comparação e saltos para outras partes +do programa. + +*** Não há o conceito de variáveis + +Os dados são associados diretamente a registradores da CPU ou a endereços +de memória identificados por /rótulos/. Toda a manipulação de dados é feita +por meio de instruções de cópia, como resultado de operações lógicas e +aritméticas ou em definições que serão processadas durante a montagem +do arquivo binário. + +*** Ponteiros, só por analogia + +Como não há variáveis, também não existe exatamente o conceito de /ponteiro/, +a não ser por analogia. Os rótulos, por exemplo, representam endereços de +memória, e nós até podemos pensar neles como os ponteiros e vetores da +linguagem C, mas isso pode causar algumas confusões conceituais e deve +ser evitado. + +*** Em vez de tipos, quantidades de bytes + +Novamente: todos os dados são escritos diretamente em registradores ou na +memória. Se usarmos registradores, as suas capacidades determinarão quantos +bytes poderão (ou deverão) ser escritos. A escrita direta na memória, por +outro lado, não tem um limite fixo, mas depende da quantidade de memória +endereçável e da maneira como o programa a utiliza. + +#+begin_quote +*Nota:* uma das principais finalidades do conceito de /tipos/ é a delimitação +do acesso aos dados. Por exemplo, declarar uma variável como do tipo =int= +limita o acesso a apenas 4 bytes (em arquiteturas x86_64), mas não existe +essa forma de restrição no Assembly. Nós somos livres para ler ou escrever +quantos bytes quisermos, em qualquer endereço, desde que tenhamos permissão +do sistema. +#+end_quote + +** Por que aprender Assembly? + +*** Entendimento do funcionamento de computadores + +Aprender Assembly proporciona uma visão detalhada do que realmente acontece +durante a execução de um programa, como o ciclo de execução de instruções, +o uso de registradores, o papel da memória RAM, o funcionamento da pilha e +tantos outros conhecimentos fundamentais para a compreensão da arquitetura +e da organização de sistemas computacionais modernos. + +*** Depuração e análise + +Ferramentas de depuração, análise, segurança e engenharia reversa são muito +mais eficazes nas mãos de quem entende Assembly, especialmente quando tudo +que temos são o código de máquina ou a /desmontagem/ do binário de um programa. + +*** Integração com C + +É comum utilizar Assembly para otimizações pontuais em programas escritos +em C, como em trechos críticos de desempenho ou quando se deseja acesso +direto a instruções específicas da arquitetura. Também é necessário para +a escrita de rotinas de sistema, drivers ou na manipulação direta de +hardware, especialmente em ambientes com recursos limitados ou sem suporte +a bibliotecas padrão. + +*** Aprendizado + +Assembly é uma tremenda ferramenta didática em disciplinas como arquitetura +e organização de computadores, sistemas operacionais, design de linguagens +e construção de compiladores. Com ele, nós podemos demonstrar e observar +diretamente conceitos que, de outra forma, seriam ocultados por várias +camadas de abstração. + +** Principais sintaxes + +Embora as instruções de máquina sejam definidas pela arquitetura da CPU, a +maneira como essas instruções são escritas em Assembly pode variar bastante, +dependendo da sintaxe adotada pelo montador. Para as arquiteturas x86 +e x86_64, destacam-se duas sintaxes principais: a sintaxe /Intel/ e a sintaxe +/AT&T/. + +*** Sintaxe AT&T + +A sintaxe AT&T foi popularizada no contexto dos sistemas Unix e do projeto +GNU. Ela é usada por padrão pelo montador GAS (/GNU Assembler/) e é a sintaxe +adotada implicitamente pelo GCC ao gerar código Assembly. Os operandos +seguem a ordem /origem → destino/, com prefixos =%= obrigatórios para +registradores e =$= para valores (ex.: =movl $1, %rax=). + +*"Salve, simpatia!" em sintaxe AT&T (GAS):* + +Arquivo: [[exemplos/00/salve-att.s][salve-att.s]] + +#+begin_src asm :tangle exemplos/00/salve-att.s +# salve-att.s +# Montar com: as salve-att.s -o salve-att.o +# Linkar com: ld salve-att.o -o salve-att + + .section .data +msg: + .ascii "Salve, simpatia!\n" +len = . - msg + + .section .text + .global _start + +_start: + # write(1, msg, len) + mov $1, %rax # syscall: write + mov $1, %rdi # stdout + lea msg(%rip), %rsi # endereço da mensagem + mov $len, %rdx # tamanho da mensagem + syscall + + # exit(0) + mov $60, %rax # syscall: exit + xor %rdi, %rdi # status 0 + syscall +#+end_src + +*** Sintaxe Intel + +Mais comum em livros didáticos, manuais da Intel e tutoriais voltados ao +desenvolvimento em baixo nível, especialmente em ambientes /bare-metal/. Ela +apresenta os operandos na ordem /destino ← origem/ (ex.: =mov rax, 1=), não usa +prefixos nos registradores e é adotada por montadores como o NASM, que +utilizaremos nos nossos estudos. + +*"Salve, simpatia!" em sintaxe Intel (NASM):* + +Arquivo: [[exemplos/00/salve-intel.asm][salve-intel.asm]] + +#+begin_src asm :tangle exemplos/00/salve-intel.asm +; salve-intel.asm +; Montar com: nasm -f elf64 salve-intel.asm +; Linkar com: ld salve-intel.o -o salve-intel + +section .data + msg db "Salve, simpatia!", 10 ; 10 = '\n' + len equ $ - msg + +section .text + global _start + +_start: + ; write(1, msg, len) + mov rax, 1 ; syscall número 1: write + mov rdi, 1 ; stdout + mov rsi, msg ; endereço da mensagem + mov rdx, len ; tamanho da mensagem + syscall + + ; exit(0) + mov rax, 60 ; syscall número 60: exit + xor rdi, rdi ; status 0 + syscall +#+end_src + +*** Compatibilidade + +Embora o GAS (executável =as=) use a sintaxe AT&T por padrão, ele também +aceita código em sintaxe Intel com a diretiva =.intel_syntax= no início do +código-fonte, geralmente acompanhada de =noprefix=. Alguns compiladores +e depuradores, como o GCC e o GDB, também oferecem suporte a ambas as +sintaxes -- por exemplo, com a opção =-masm=intel=, no GCC, e o comando +=set disassembly-flavor=, no GDB. + +* O Netwide Assembler (NASM) + +O /Netwide Assembler/ (NASM) é um montador livre muito utilizado na programação +em Assembly, especialmente para arquiteturas x86 e x86_64. Ele é conhecido por +sua sintaxe clara e direta e por oferecer suporte a formatos de saída para +várias plataforma. + +| Característica | Descrição | +|--------------------------+---------------------------------------------------------------------------| +| *Sintaxe* | Intel (não suporta AT&T) | +| *Arquiteturas suportadas* | x86 (16/32 bits), x86_64 (64 bits) | +| *Formatos de saída* | ELF, COFF, Mach-O, bin, RDOFF | +| *Processo de montagem* | Gera binários brutos (/raw/) e arquivos objeto (exige link-edição separada) | +| *Controle de código* | Total controle sobre instruções, endereços e seções | +| *Pré-processamento* | Diretivas simples; sem pré-processador complexo como no C | +| *Suporte a macros* | Sim, com argumentos e controle condicional | +| *Integração com o sistema* | Compatível com chamadas de sistema Linux, Windows e Unix em geral | +| *Otimizações* | Não realiza otimizações nem análises semânticas | +| *Documentação* | Completa e com muitos exemplos | + +** Sintaxe de instruções + +A sintaxe geral de uma instrução em NASM segue uma forma bastante regular +e compreensível: + +#+begin_example +[rótulo:] instrução operando1, operando2 ; comentário +#+end_example + +Onde... + +| Elemento | Obrigatório | Exemplos | Descrição | +|------------+-------------+-------------------+----------------------------------------------------------------------------------------------| +| *Rótulo* | Opcional | =loop_start:= | Nome associado a um endereço. Pode ser usado como destino de saltos. | +| *Instrução* | Sim | =mov=, =add=, =jmp= | A operação a ser executada. | +| *Operando 1* | Depende | =rax=, =[msg]= | Primeiro operando: geralmente o destino (registrador ou memória). | +| *Operando 2* | Depende | =rbx=, =1=, =[valor]= | Segundo operando: geralmente a origem dos dados (registrador, memória ou um valor imediato). | +| *Comentário* | Opcional | =; isso é um salto= | Começa com =;= e vai até o fim da linha. Ignorado pelo montador. | + +*** Diretivas para definições de dados + +Além das instruções, mas ainda seguindo a estrutura geral da sintaxe, +o NASM oferece várias diretivas para definir dados ou reservar espaços +na memória, como: + +| Diretiva | Uso | Ação | Equivalente aproximado em C | +|----------+---------------+------------------------------------------+------------------------------------| +| =db= | =db 0x48= | Define um byte (define byte) | =char x = 0x48;= | +| =dw= | =dw 1234h= | Define uma *word* (2 bytes) | =short x = 0x1234;= | +| =dd= | =dd 1.0= | Define uma *double word* (4 bytes) | =int x = 1;= / =float x = 1.0f;= | +| =dq= | =dq 3.14159= | Define uma *quad word* (8 bytes) | =double x = 3.14159;= | +| =dt= | =dt 1.23e400= | Define uma *ten-byte* (80 bits, float ext) | =long double x = …= (FPU específico) | +| =resb= | =resb 64= | Reserva 64 bytes (sem inicializar) | =char buffer[64];= (sem valor) | +| =resw= | =resw 8= | Reserva 8 words (16 bytes) | =short arr[8];= | +| =resd= | =resd 4= | Reserva 4 dwords (16 bytes) | =int arr[4];= | +| =resq= | =resq 2= | Reserva 2 qwords (16 bytes) | =long arr[2];= | + +Essas diretivas podem ou não ser antecedidas de rótulos para indicar onde +esses dados serão inseridos na memória, por exemplo: + +#+begin_src asm +msg: db "Salve, simpatia!", 0 ; 'msg' é um rótulo para o endereço do primeiro byte +buffer: resb 64 ; 'buffer' é um rótulo para a área reservada +#+end_src + +É permitido definir e reservar espaços sem rótulos, mas os dados só poderão +ser acessados com base em cálculos de posições relativas. + +** Sintaxe de metaprogramação da montagem + +A sintaxe de metaprogramação da montagem refere-se ao conjunto de diretivas, +macros e comandos especiais fornecidos pelo montador para escrever código +que gera ou manipula outro código durante a montagem. Essa sintaxe vai além +das instruções padrão da CPU e possibilita: + +- Definir constantes simbólicas e macros reutilizáveis. +- Realizar substituições textuais antes da montagem real. +- Implementar estruturas condicionais e laços para controle do fluxo de montagem. +- Gerar código de forma dinâmica e programável, facilitando a automação e a abstração. + +Na tabela, nós temos alguns exemplos de diretivas e comandos usados na +sintaxe de metaprogramação de montagem do NASM: + +| Diretiva / Comando | Categoria | Descrição breve | Exemplo simples | +|--------------------+-----------------------+----------------------------------------------------+----------------------| +| =equ= | Definição simbólica | Define uma constante simbólica imutável | =MAXLEN equ 32= | +| ~=~ | Definição simbólica | Define símbolo numérico, pode ser redefinido | ~counter = 0~ | +| =%define= | Macros simbólicas | Define uma macro simples (substituição textual) | =%define MSG "Olá!"= | +| =%assign= | Macros simbólicas | Define símbolo numérico que pode ser alterado | =%assign i 10= | +| =%macro= | Macros com parâmetros | Define macros com parâmetros e corpo expansível | =%macro PRINT_MSG 0= | +| =%ifdef= / =%ifndef= | Controle condicional | Compila bloco se símbolo estiver (ou não) definido | =%ifdef DEBUG= | +| =%include= | Inclusão de arquivos | Insere outro arquivo fonte no ponto da montagem | =%include "utils.inc"= | +| =%rep= / =%endrep= | Laços e repetição | Repete um bloco de código um número fixo de vezes | =%rep 4= | + +** Operadores e símbolos + +No NASM, a montagem de código também envolve o uso de operadores e símbolos +especiais que permitem calcular endereços, manipular valores e controlar a +geração do programa, tudo em tempo de montagem. + +Aqui estão alguns exemplos: + +| Símbolo | Descrição | Uso / Exemplo | +|-----------------+---------------------------------------------------+-----------------------------------------------| +| =$= | Endereço da instrução atual | =jmp $+5= — salta 5 bytes à frente | +| =$$= | Endereço do início do segmento ou bloco atual | =jmp $$-$= — salto relativo ao início do bloco | +| =$+n= / =$-n= | Endereço relativo deslocado =n= bytes | =jmp $-10= — volta 10 bytes | +| =%= | Indica que um símbolo é um valor numérico (macro) | =%define X 10= | +| =:= | Define ou qualifica rótulos | =loop_start:= | +| =*= | Multiplicação em expressões | =len equ 4 * 10= | +| =+=, =-=, =/=, =<<=, =>>= | Operadores aritméticos e bitwise | =size equ 1 << 4= | +| =[...]= | Acesso indireto à memória (endereços) | =mov eax, [ebx]= | +| =?= | Operador ternário em macros | =%if ?(cond) ... %endif= | + +** Seções no NASM + +Quando escrevemos um programa em Assembly, é preciso organizar o código e os +dados de forma que o sistema operacional possa carregá-lo corretamente na +memória. Essa organização é feita por meio das /seções/ (ou /segmentos/), que +dividem o programa em partes distintas com propósitos diferentes. Mas isso +não é apenas uma convenção de organização: a separação das seções é exigida +pelo formato do executável (como ELF no Linux ou PE no Windows) e é +fundamental para que o /carregador/ do sistema (/loader/) saiba onde colocar +cada parte do programa na memória. + +No NASM, nós usamos a diretiva =section= para declarar as divisões do código, +que podem ser: + +- =section .text=: armazena o código executável do programa (as instruções); +- =section .data=: contém dados inicializados (variáveis com valores definidos); +- =section .rodata=: usada para dados constantes somente leitura (como strings fixas); +- =section .bss=: reserva espaço para dados não inicializados (variáveis com valor padrão, geralmente zero). + +** Importação e exportação de símbolos + +Símbolos são nomes associados a endereços e valores em um programa. Eles +podem representar rótulos de rotinas, posições de dados na memória ou +constantes definidas em tempo de montagem. Em Assembly, esses símbolos +servem como pontos de referência tanto para o código quanto para o montador +e o editor de ligações (/link-editor/). + +Quando escrevemos programas em NASM que serão ligados com outros módulos, +como bibliotecas externas ou arquivos objeto, nós precisamos informar ao +editor de ligações quais símbolos devem ser /exportados/ para esses módulos +e quais devem ser /importados/ de outros lugares. No NASM, isso é feito com +as diretivas =global= (importação) e =extern= (exportação). + +No GNU/Linux, especialmente quando não usamos uma linguagem de mais alto +nível (como C), nós precisamos especificar manualmente o ponto de entrada +do programa — ou seja, a função onde o sistema operacional deve começar +a execução. Por padrão, o Linux espera que o ponto de entrada seja chamado +de =_start=, que deve ser exportado com a diretiva: + +#+begin_src asm +global _start +#+end_src + +Essa instrução diz ao NASM que o símbolo =_start= deve ser incluído na tabela +de símbolos do arquivo objeto montado. Assim, o editor de ligações (=ld=) +poderá encontrá-lo. Nó código, =_start= geralmente é escrito no início da +seção =.text=: + +#+begin_example +global _start + +section .text +_start: + + ; O código de início do programa vem aqui... +#+end_example + +** Tipos de operandos em instruções + +Toda instrução Assembly opera sobre um ou mais valores que nós chamamos de +operandos. Eles indicam a origem e o destino dos dados e podem representar: + +- Registradores; +- Endereços de memória; +- Valores imediatos (constantes escritas diretamente no código). + +A combinação entre esses tipos de operandos determina a validade e o +comportamento das instruções. Por exemplo, uma instrução =mov= pode copiar... + +- Dados de um registrador para outro registrador; +- Um valor imediato para um registrador; +- Dados em um endereço de memória para um registrador; +- Dados de um registrador para um endereço de memória. + +Mas não pode, por exemplo, copiar dados de um registrador, ou de um endereço +de memória, para um valor imediato, e o NASM exige que essas combinações +estejam de acordo com a sintaxe e a semântica definida pela arquitetura. + +* Instruções essenciais + +A arquitetura x86_64 tem centenas de instruções, mas nós só precisamos +conhecer algumas delas para começar a acompanhar os exemplos dos próximos +tópicos -- com o tempo, nós acrescentaremos organicamente algumas outras +ao nosso arsenal da linguagem Assembly. + +** Instrução =mov= + +A instrução =mov= é usada para copiar valores de um operando para outro. É +uma das instruções mais utilizadas em Assembly, pois quase todo código precisa +manipular dados em registradores, na memória ou com valores imediatos. + +*Forma geral:* + +#+begin_example +mov destino, origem +#+end_example + +O valor de =origem= é copiado para =destino=. + +#+begin_quote +*Importante!* Isso não é uma troca de dados entre operandos, é uma cópia +de um operando para outro. +#+end_quote + +*Combinações válidas:* + +| Origem | Destino | Exemplo | Observações | +|-------------+-------------+----------------+-------------------------------------------| +| Imediato | Registrador | =mov rax, 42= | Valor literal (imediato) para registrador | +| Registrador | Registrador | =mov rbx, rax= | Cópia entre registradores | +| Registrador | Memória | =mov [var], rax= | Salva conteúdo em memória | +| Memória | Registrador | =mov rax, [var]= | Carrega conteúdo da memória | +| Imediato | Memória | =mov [var], 10= | Atribuição direta a endereço | + +O NASM não permite movimentos diretos de memória para memória. + +*Exemplo de uso:* + +#+begin_src asm +section .data + msg db 42 ; variável na memória + +section .text + global _start +_start: + mov rax, [msg] ; carrega o valor de msg em rax + mov rbx, rax ; copia rax para rbx + mov [msg], 99 ; sobrescreve msg com novo valor +#+end_src + +** Instrução =lea= + +Carrega o endereço efetivo de um operando de memória (não o valor na memória). + +#+begin_quote +O endereço efetivo é o resultado da avaliação de uma expressão que resulta +no endereço que será usado para acessar a memória. +#+end_quote + +*Forma geral:* + +#+begin_example +lea destino, [base + índice*escala + deslocamento] +#+end_example + +*Combinações válidas:* + +| Destino | Origem (memória) | Exemplo | +|-------------+-------------------+----------------------| +| Registrador | Memória simbólica | =lea rax, [rbx+4]= | +| Registrador | Endereços | =lea rsi, [rip+msg]= | + +*Exemplo de uso:* + +#+begin_src asm +section .rodata + msg: db "Olá, mundo!", 10 ; string com quebra de linha + +section .text +global _start + +_start: + lea rsi, [rel msg] ; carrega o endereço de msg em rsi + mov rax, 1 ; syscall: write + mov rdi, 1 ; descritor: stdout + mov rdx, 13 ; tamanho da mensagem + syscall + ; ... +#+end_src + +** Instruções aritméticas básicas (=inc=, =dec=, =add= e =sub=) + +Estas instruções realizam operações aritméticas simples diretamente em +registradores ou na memória. + +*** Instrução =inc= + +Incrementa (soma 1) o valor de um operando. + +*Forma geral:* + +#+begin_example +inc destino +#+end_example + +*Operandos válidos:* registrador ou endereço de memória. + +*Exemplo:* + +#+begin_src asm +mov rcx, 5 +inc rcx ; rcx passa a valer 6 +#+end_src + +*** Instrução =dec= + +Decrementa (subtrai 1) o valor de um operando. + +*Forma geral:* + +#+begin_example +dec destino +#+end_example + +*Operandos válidos:* registrador ou endereço de memória. + +*Exemplo:* + +#+begin_src asm +mov rcx, 5 +dec rcx ; rcx passa a valer 4 +#+end_src + +*** Instrução =add= + +Soma um operando ao destino e armazena o resultado no destino. + +*Forma geral:* + +#+begin_example +add destino, origem +#+end_example + +*Combinações válidas:* + +| Destino | Origem | Exemplo | +|-------------+-----------------+-------------------| +| Registrador | Registrador | add rax, rbx | +| Registrador | Valor imediato | add rax, 10 | +| Registrador | Memória | add rax, [var] | +| Memória | Registrador | add [var], rax | +| Memória | Valor imediato | add [var], 1 | + +*Exemplo:* + +#+begin_src asm +mov rax, 3 +add rax, 7 ; rax passa a valer 10 +#+end_src + +*** Instrução =sub= + +Subtrai um operando do destino e armazena o resultado no destino. + +*Forma geral:* + +#+begin_example +sub destino, origem +#+end_example + +*Combinações válidas:* + +| Destino | Origem | Exemplo | +|-------------+-----------------+-------------------| +| Registrador | Registrador | sub rax, rbx | +| Registrador | Valor imediato | sub rax, 10 | +| Registrador | Memória | sub rax, [var] | +| Memória | Registrador | sub [var], rax | +| Memória | Valor imediato | sub [var], 1 | + +*Exemplo:* + +#+begin_src asm +mov rax, 10 +sub rax, 3 ; rax passa a valer 7 +#+end_src + +** Instruções de multiplicação e divisão (=mul=, =imul=, =div= e =idiv=) + +Estas instruções realizam operações aritméticas de multiplicação e divisão. + +*** Instrução =mul= (multiplicação sem sinal) + +Realiza multiplicação sem sinal entre o valor no registrador acumulador e +um operando, armazenando o resultado em registradores específicos. + +*Funcionamento:* + +| Tamanho dos operandos | Multiplicação | Resultado | +|-----------------------+------------------+-----------| +| 8 bits | =AL= por operando | =AX= | +| 16 bits | =AX= por operando | =DX:AX= | +| 32 bits | =EAX= por operando | =EDX:EAX= | +| 64 bits | =RAX= por operando | =RDX:RAX= | + +*Forma geral:* + +#+begin_example +mul operando +#+end_example + +*Operando:* registrador ou memória, sem sinal. + +*Exemplo:* + +#+begin_src asm +mov rax, 5 +mul qword [valor] ; rax * [valor], resultado em RDX:RAX +#+end_src + +*** Instrução =imul= (multiplicação com sinal) + +Mais flexível que =mul=, pode usar 1, 2 ou 3 operandos. + +*Forma geral unária (1 operando):* + +#+begin_example +imul operando +#+end_example + +| Tamanho dos operandos | Multiplicação | Resultado | +|-----------------------+------------------+-----------| +| 8 bits | =AL= por operando | =AX= | +| 16 bits | =AX= por operando | =DX:AX= | +| 32 bits | =EAX= por operando | =EDX:EAX= | +| 64 bits | =RAX= por operando | =RDX:RAX= | + +*Forma geral binária (2 operandos):* + +#+begin_example +imul destino, origem (resultado em destino) +#+end_example + +- Multiplica =destino= por =origem= e armazena o resultado em =destino=. +- Se o resultado exceder a capacidade de =destino=, apenas os bytes mais + baixos serão armazenados (não há extensão de registradores). +- Afeta as flags =CF= (/carry/) e =OF= (/overflow/) no caso de estouro da + capacidade de =destino=. + +*Forma geral ternária (3 operandos):* + +#+begin_example +imul destino, origem, imediato (resultado em destino) +#+end_example + +- Multiplica =origem= pelo valor de =imediato= e armazena o resultado em =origem=. +- Se o resultado exceder a capacidade de =destino=, apenas os bytes mais + baixos serão armazenados (não há extensão de registradores). +- Afeta as flags =CF= e =OF= no caso de estouro da capacidade de =destino=. + +*Operandos válidos para =imul=:* + +| Forma | Operando 1 (destino) | Operando 2 (origem) | Operando 3 (imediato) | +|-------------+--------------------------------+---------------------------------+-----------------------| +| 1 operando | =RAX=, =EAX=, =AX= ou =AL= (implícito) | Qualquer registrador ou memória | (Não há) | +| 2 operandos | Registrador | Registrador ou memória | (Não há) | +| 3 operandos | Registrador | Registrador ou memória | Valor imediato | + +*Exemplo unário:* + +#+begin_src asm +mov rax, 10 +mov rbx, -4 +imul rbx ; rdx:rax = rax * rbx (com sinal) +#+end_src + +*Exemplo binário:* + +#+begin_src asm +mov rax, 5 +mov rbx, -3 +imul rax, rbx ; rax = rax * rbx (com sinal) +#+end_src + +*Exemplo ternário:* + +#+begin_src asm +mov rbx, 7 +imul rax, rbx, -2 ; rax = rbx * -2 (com sinal) +#+end_src + +*** Instrução =div= (divisão sem sinal) + +Realiza divisão sem sinal entre o valor no registrador acumulador estendido +e um operando. + +*Funcionamento:* + +| Tamanho dos operandos | Divisão | Quociente | Resto | +|-----------------------+----------------------+-----------+-------| +| 8 bits | =AX= por operando | =AL= | =AH= | +| 16 bits | =DX:AX= por operando | =AX= | =DX= | +| 32 bits | =EDX:EAX= por operando | =EAX= | =EDX= | +| 64 bits | =RDX:RAX= por operando | =RAX= | =RDX= | + +*Forma geral:* + +#+begin_example +div operando +#+end_example + +*Operando:* registrador ou memória sem sinal. + +*Exemplo:* + +#+begin_src asm +mov rax, 20 +xor rdx, rdx ; limpa (zera) RDX antes da divisão +div qword [divisor] ; divide RDX:RAX por [divisor] +#+end_src + +*** Instrução =idiv= (divisão com sinal) + +Realiza divisão com sinal entre o valor no registrador acumulador estendido +e um operando, considerando números negativos. + +*Funcionamento:* + +| Tamanho dos operandos | Divisão | Quociente | Resto | +|-----------------------+----------------------+-----------+-------| +| 8 bits | =AX= por operando | =AL= | =AH= | +| 16 bits | =DX:AX= por operando | =AX= | =DX= | +| 32 bits | =EDX:EAX= por operando | =EAX= | =EDX= | +| 64 bits | =RDX:RAX= por operando | =RAX= | =RDX= | + +*Forma geral:* + +#+begin_example +idiv operando +#+end_example + +*Operando:* registrador ou memória com sinal. + +*Exemplo:* + +#+begin_src asm +mov rax, -50 +cqto ; extende o sinal de RAX para RDX:RAX (antes de idiv) +idiv qword [divisor] ; divide RDX:RAX por [divisor] +#+end_src + +#+begin_quote +*Nota:* Antes de usar =idiv=, é necessário preparar o registrador de extensão +(RDX) com o valor correto usando =cqto= (/Convert Quadword to Octaword/), +que estende o sinal de RAX para RDX:RAX. Para operandos menores, são +utilizados =cwd= (16 bits) ou =cdq= (32 bits). +#+end_quote + +** Instrução =jmp= (salto incondicional) + +Realiza um salto incondicional para o endereço ou rótulo especificado. A +execução do programa continua a partir da posição de destino, sem avaliar +qualquer condição. + +*Forma geral:* + +#+begin_example +jmp destino +#+end_example + +*Combinações válidas:* + +| Destino | Tipo de operando | Exemplo | +|-------------+------------------------+-----------| +| Rótulo | Endereço relativo | =jmp fim= | +| Registrador | Endereço absoluto | =jmp rax= | +| Memória | Ponteiro para endereço | =jmp [rax]= | +| Imediato | Offset relativo | =jmp $+5= | + +*Exemplo de uso:* + +#+begin_src asm +section .text +global _start + +_start: + mov rax, 1 ; syscall: write + mov rdi, 1 ; descritor: stdout + mov rsi, msg ; ponteiro para a string + mov rdx, 16 ; tamanho da string + syscall + + jmp fim ; salta incondicionalmente para o final + +meio: + ; código que nunca será executado + mov rax, 60 + mov rdi, 1 + syscall + +fim: + mov rax, 60 ; syscall: exit + xor rdi, rdi ; Estado de término 0 + syscall + +section .rodata + msg: db "Salve, simpatia!", 10 +#+end_src + +** Instruções de salto condicional + +As instruções de salto condicional (ou desvio condicional) alteram o fluxo +de execução com base no estado das flags da CPU, definidas por instruções +de comparação como =cmp= ou =test=. + +*Forma geral:* + +#+begin_example +jXX destino +#+end_example + +Onde =jXX= representa uma das variantes condicionais e =destino= é um rótulo +no código. + +*Flags envolvidas:* + +- =ZF= (/Zero Flag/): Ativa (=1=) se o resultado de uma operação for zero. +- =SF= (/Sign Flag/): Reflete o bit de sinal do resultado (=0= para positivo, =1= para negativo). +- =OF= (/Overflow Flag/): Ativa (=1=) se ocorreu /overflow/ em operação aritmética com sinal. +- =CF= (/Carry Flag/): Ativa se houve transporte (/carry/) ou empréstimo (/borrow/) em + operações aritméticas com números sem sinal. + +*Tabela de saltos comuns (em inteiros com sinal):* + +| Instrução | Sinônimo | Condição | Descrição | +|-----------+----------+--------------------+-------------------------------| +| =je= | =jz= | ~ZF = 1~ | Salta se igual (zero) | +| =jne= | =jnz= | ~ZF = 0~ | Salta se diferente (não zero) | +| =jg= | =jnle= | ~ZF = 0~ e ~SF = OF~ | Salta se maior | +| =jge= | =jnl= | ~SF = OF~ | Salta se maior ou igual | +| =jl= | =jnge= | ~SF != OF~ | Salta se menor | +| =jle= | =jng= | ~ZF = 1~ ou ~SF != OF~ | Salta se menor ou igual | + +*Outras variantes comuns (sem sinal):* + +| Instrução | Condição | Descrição | +|-----------+------------------+--------------------------| +| =ja= | ~CF = 0~ e ~ZF = 0~ | Salta se acima | +| =jae= | ~CF = 0~ | Salta se acima ou igual | +| =jb= | ~CF = 1~ | Salta se abaixo | +| =jbe= | ~CF = 1~ ou ~ZF = 1~ | Salta se abaixo ou igual | + +*Exemplo de uso:* + +#+begin_src asm +section .text +global _start + +_start: + mov rax, 5 + mov rbx, 3 + cmp rax, rbx ; compara rax com rbx + + jg maior ; salta se rax > rbx + jl menor ; salta se rax < rbx + je igual ; salta se rax == rbx + +maior: + ; código para o caso de rax > rbx + jmp fim + +menor: + ; código para o caso de rax < rbx + jmp fim + +igual: + ; código para o caso de rax == rbx + +fim: + mov rax, 60 ; syscall: exit + xor rdi, rdi ; Resula em 0 + syscall +#+end_src + +** Instrução =cmp= + +Compara dois operandos realizando uma subtração entre eles, mas sem armazenar +o resultado. O objetivo da instrução é atualizar as flags da CPU, que podem +ser usadas posteriormente em instruções de salto condicional. + +*Forma geral:* + +#+begin_example +cmp operando1, operando2 +#+end_example + +Internamente, equivale a: + +#+begin_example +sub operando1 operando2 +#+end_example + +Mas sem alterar o conteúdo de =operando1=. + +*Combinações válidas:* + +| Operando 1 | Operando 2 | Exemplo | +|-------------+-------------+------------------| +| Registrador | Registrador | =cmp rax, rbx= | +| Registrador | Imediato | =cmp rsi, 10= | +| Registrador | Memória | =cmp rcx, [array]= | +| Memória | Registrador | =cmp [rdi], rax= | +| Memória | Imediato | =cmp [rsi], 1= | + +*Exemplo de uso:* + +#+begin_src asm +section .data + valor: dq 42 + +section .text +global _start + +_start: + mov rax, [valor] ; carrega o valor da memória + cmp rax, 42 ; compara com 42 + je igual ; salta se for igual + + mov rdi, 1 ; Termina com erro: não é igual + jmp fim + +igual: + mov rdi, 0 ; Termina com sucesso: é igual + +fim: + mov rax, 60 ; syscall: exit + syscall +#+end_src + +** Instrução =test= + +Realiza uma operação lógica =AND= entre dois operandos e atualiza as flags de +acordo com o resultado. A instrução é usada principalmente para testar, de +forma não destrutiva, se bits estão ligados ou se valores são zero. + +#+begin_quote +A instrução =cmp= testa diferença (via subtração), enquanto =test= testa presença +de bits em comum (=AND= bit a bit) entre dois valores. +#+end_quote + +*Forma geral:* + +#+begin_example +test operando1, operando2 +#+end_example + +O resultado da operação =operando1 AND operando2= não é armazenado, apenas as +flags da CPU (zero, sinal, paridade, etc.) são atualizadas. + +*Combinações válidas:* + +| Operando 1 | Operando 2 | Exemplo | +|-------------+--------------+-----------------------| +| Registrador | Registrador | =test rax, rax= | +| Registrador | Imediato | =test rbx, 1= | +| Memória | Registrador | =test [rdi], rax= | +| Memória | Imediato | =test [rsi], 0xFF= | + +*Exemplo de uso:* + +#+begin_src asm +section .text +global _start + +_start: + mov rax, 0 ; valor a testar + test rax, rax ; testa se é zero + jz deu_zero ; salta se resultado for zero + + ; não deu zero + mov rdi, 1 + jmp sair + +deu_zero: + ; era zero + mov rdi, 0 + +sair: + ; Termina com o valor recebido em rdi... + mov rax, 60 ; syscall: exit + syscall +#+end_src + +** Instruções =call= e =ret= + +As instruções =call= e =ret= são utilizadas para implementar chamadas e retornos +de sub-rotinas em Assembly, o que possibilita a estruturação do código em +blocos reutilizáveis. A instrução =call= salva o endereço da próxima instrução +(retorno) na pilha e salta para o endereço especificado. A instrução =ret=, +por sua vez, recupera o endereço de retorno na pilha e desvia a execução +de volta para o ponto após a chamada. + +*Forma geral:* + +#+begin_example +call destino +... +destino: +... +ret +#+end_example + +*Combinações válidas:* + +| Instrução | Operando | Exemplo | +|-----------+---------------------+--------------| +| =call= | Rótulo | =call uma_sub= | +| =call= | Registrador | =call rax= | +| =call= | Ponteiro de memória | =call [rax]= | +| =ret= | (sem operando) | =ret= | + +*Observações:* + +- As instruções =call= e =ret= afetam o registrador de instrução (=RIP=), + controlando o fluxo de execução. +- A instrução =call= também é utilizada para chamar funções importadas + com =extern=. +- Em x86_64, os argumentos de função geralmente são passados por + registradores, conforme a convenção de chamada de funções do sistema. + +*Exemplo de uso:* + +#+begin_src asm +section .text +global _start + +_start: + call salve ; chama a sub-rotina + + mov rax, 60 ; syscall: exit + xor rdi, rdi ; estado de término 0 + syscall + +salve: + mov rax, 1 ; syscall: write + mov rdi, 1 ; stdout + mov rsi, msg + mov rdx, msglen + syscall + ret + +section .rodata + msg: db "Salve, simpatia!", 10 + msglen equ $ - msg +#+end_src + +** Instrução =syscall= + +A instrução =syscall= realiza uma chamada ao kernel do sistema operacional +em 64 bits (em 32 bits, seria a instrução =int 80=), transferindo para ele +o controle temporário da execução do programa. É o principal meio de acesso +a serviços como leitura e escrita em arquivos, alocação de memória, término +do programa, etc. + +#+begin_quote +Ao contrário das funções da biblioteca padrão (como =printf= ou =exit=), que são +abstrações em C, a =syscall= faz chamadas diretas ao sistema. +#+end_quote + +*Forma geral:* + +#+begin_example +mov rax, numero_da_syscall +mov rdi, argumento1 +mov rsi, argumento2 +mov rdx, argumento3 +mov r10, argumento4 +mov r8, argumento5 +mov r9, argumento6 +syscall +#+end_example + +*Registradores de argumento:* + +| Posição | Registrador | Descrição | +|---------+-------------+--------------------| +| 1º | =rdi= | Primeiro argumento | +| 2º | =rsi= | Segundo argumento | +| 3º | =rdx= | Terceiro argumento | +| 4º | =r10= | Quarto argumento | +| 5º | =r8= | Quinto argumento | +| 6º | =r9= | Sexto argumento | + +*Resultado:* + +- O valor de retorno da /syscall/ é carregado em =rax=. +- Erros são indicados com valores negativos em =rax=. + +*Exemplo de uso (escrevendo na saída padrão):* + +#+begin_src asm +section .rodata + msg: db "Salve, simpatia!", 10 + len: equ $ - msg + +section .text +global _start + +_start: + mov rax, 1 ; syscall: write + mov rdi, 1 ; descritor de saída: stdout + mov rsi, msg ; ponteiro para os dados + mov rdx, len ; tamanho da mensagem + syscall + + mov rax, 60 ; syscall: exit + xor rdi, rdi ; código de saída: 0 + syscall +#+end_src + +#+begin_quote +*Nota:* A lista de números das chamadas varia conforme o sistema operacional. +#+end_quote + +** Padrões frequentemente utilizados + +Aqui estão alguns padrões de código muito utilizados em Assembly, seja por +performance, clareza para o processador, ou por simples abreviação da +escrita do programa. + +Por exemplo, em vez de usar: + +#+begin_src asm +mov reg, 0 +#+end_src + +É comum escrever: + +#+begin_src asm +xor reg, reg ; operação XOR bit a bit +#+end_src + +Motivação: + +- É mais rápido em alguns processadores. +- Utiliza menos bytes de código. +- Não depende de carregar um imediato =0=. + +Nessa mesma linha, existem outros padrões bastante encontrados, como: + +| Padrão | Descrição | +|---------+--------------------------------------------------------------| +| =inc reg= | Incrementa o registrador em 1 em vez de usar =add=. | +| =dec reg= | Decrementa o registrador em 1 em vez de usar =sub=. | +| =neg reg= | Inverte o sinal do registrador em vez de multiplicar por =-1=. | + +* Montagem e execução de programas em Assembly NASM + +** Requisitos + +Para garantir que você tenha todas as ferramentas necessárias para acompanhar +nossos exemplos e demonstrações, verifique se os programas abaixo estão +instalados no seu sistema: + +- Um editor de código da sua preferência (Vim, Emacs, Geany, etc) +- =git= +- =nasm= +- =gcc= +- =as= +- =ld= +- =gdb= +- =readelf= +- =objdump= +- =nm= +- =ldd= +- =hexedit= +- =xxd= (geralmente instalado com o editor Vim) + +No Debian, a maioria deles é instalada com os comandos: + +#+begin_example +sudo apt update +sudo apt install build-essential git nasm gdb binutils hexedit +#+end_example + +** Exemplo: "Salve, simpatia!" + +Tomando como exemplo o programa [[exemplos/00/salve-intel.asm][salve-intel.asm]], escrito com a sintaxe Intel +implementada pelo NASM: + +#+begin_src asm +; salve-intel.asm +; Montar com: nasm -f elf64 salve-intel.asm +; Linkar com: ld salve-intel.o -o salve-intel + +section .data + msg db "Salve, simpatia!", 10 ; 10 = '\n' + len equ $ - msg + +section .text + global _start + +_start: + ; write(1, msg, len) + mov rax, 1 ; syscall número 1: write + mov rdi, 1 ; stdout + mov rsi, msg ; endereço da mensagem + mov rdx, len ; tamanho da mensagem + syscall + + ; exit(0) + mov rax, 60 ; syscall número 60: exit + xor rdi, rdi ; status 0 + syscall +#+end_src + +O processo de montagem envolve a execução do montador =nasm= informando o +formato de saída esperado (no caso, formato ELF de 64 bits): + +#+begin_example +:~$ nasm -f elf64 salve-intel.asm +#+end_example + +O =nasm= gera o binário montado em um arquivo com mesmo nome do código-fonte, +mas com a extensão =.o=, de /objeto/: + +#+begin_example +ls +salve-intel.asm salve-intel.o +#+end_example + +Esse arquivo objeto até poderia ser vinculado a outros binários para compor +um programa (/ligação estática/), mas ele ainda não pode ser executado como +está, pois ainda não contém todas as informações que o sistema operacional +requer para carregá-lo na memória e executá-lo. Para isso, o arquivo objeto +precisa ser processado por um editor de ligações (/link-editor/ ou simplesmente +/ligador/): + +#+begin_example +:~$ ld salve-intel.o -o salve-intel +#+end_example + +Como resultado, nós teremos um arquivo binário executável (inclusive com as +devidas permissões) de nome =salve-intel=: + +#+begin_example +:~$ ls +salve-intel salve-intel.asm salve-intel.o +#+end_example + +#+begin_quote +Se não tivéssemos definido um nome de saída (opção =-o=), o executável seria +chamado, por padrão, de =a.out=. +#+end_quote + +Para testar, basta executar: + +#+begin_example +:~$ ./salve-intel +Salve, simpatia! +#+end_example + +* Links e referências úteis + +- [[https://www.nasm.us/xdoc/2.16.02/html/][Manual do NASM (2.16.02)]] +- [[https://namazso.github.io/x86/][Guia de referência das instruções Intel 64]] +- [[https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/][Tabela de chamadas de sistema Linux x86_64]]