1352 lines
47 KiB
Org Mode
1352 lines
47 KiB
Org Mode
#+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]]
|