Aprendendo Assembly
Doar para o autor
  • Introdução
  • Conteúdo
  • Como usar este livro
  • A base
    • Noção geral da arquitetura
    • Modos de operação
    • Sintaxe
    • Registradores de propósito geral
    • Endereçamento
    • Pilha
    • Saltos
    • Procedimentos
    • Seções e símbolos
    • Instruções assembly x86
    • Instruções do NASM
    • Pré-processador do NASM
    • Syscall no Linux
    • Olá mundo no Linux
    • Revisão
  • Aprofundando em Assembly
    • Registradores de segmento
    • CALL e RET
    • Position-independent executable
    • Atributos
    • Prefixos
    • Flags do processador
    • Instruções condicionais
    • Programando no MS-DOS
    • Interrupções de software e exceções
    • Procedimentos do BIOS
    • Usando instruções da FPU
    • Entendendo SSE
      • Instruções de movimentação de dados
      • Instruções aritméticas
      • Instruções lógicas e de comparação
      • Instruções com inteiros 128-bit
      • Instruções de conversão
  • Programando junto com C
    • Sintaxe do GAS
    • Convenção de chamada da System V ABI
    • Convenções de chamada no Windows
    • Variáveis em C
    • Funções em C
    • Ambiente hosted
    • Ambiente freestanding
    • Inline Assembly no GCC
    • Instruções intrínsecas
  • Depuração de código
    • Entendendo os depuradores
    • Depurando com o GDB
    • Depurando com o Dosbox
  • Apêndices
    • Código de máquina
      • Formato das instruções
      • Atributos e prefixos
      • Immediate
      • Displacement
      • ModR/M e SIB
      • Opcode
      • Prefixo REX
      • Codificação dos registradores
  • Metadados
    • TO DO
    • Referências
Powered by GitBook
On this page
  • Hello World
  • write
  • Entry point

Was this helpful?

Export as PDF
  1. A base

Olá mundo no Linux

Finalmente o Hello World.

Geralmente o "Hello World" é a primeira coisa que vemos quando estamos aprendendo uma linguagem de programação. Nesse caso eu deixei por último pois acredito que seria de extrema importância entender todos os conceitos antes de vê-lo, isso evitaria a intuição de ver um código em Assembly como um "código em C mais difícil de ler". Acredito que essa comparação mental involuntária é muito ruim e prejudicaria o aprendizado. Por isso optei por explicar tudo antes mesmo de apresentar o famoso "Hello World".

Hello World

Desta vez vamos escrever um código em Assembly sem misturar com C, será um executável do Linux (formato ELF64) fazendo chamadas de sistema diretamente. Vamos vê-lo logo:

hello.asm
bits 64

section .rodata
  msg:     db  `Hello World!\n`
  MSG_SIZE equ $-msg

section .text

global _start
_start:
  mov rax, 1
  mov rdi, 1
  mov rsi, msg
  mov rdx, MSG_SIZE
  syscall             ; write

  mov rax, 60
  xor rdi, rdi
  syscall             ; exit

Para compilar esse código basta usar o NASM especificando o format elf64 e desta vez iremos usar o linker do pacote GCC diretamente. O nome do executável é ld e o uso básico é bem simples, basta especificar o nome do arquivo de saída com -o. Ficando assim:

$ nasm hello.asm -felf64
$ ld hello.o -o hello
$ ./hello

Na linha 5 definimos uma constante usando o símbolo $ para pegar o endereço da instrução atual e subtraímos pelo endereço do rótulo msg. Isso resulta no tamanho do texto porque msg aponta para o início da string e, como está logo em seguida, $ seria o endereço do final da string. final - início = tamanho

write

Nome

RAX

RDI

RSI

RDX

write

1

file_descriptor

endereço

tamanho (em bytes)

Como deve ter reparado usamos mais uma syscall, que foi a syscall write. Essa syscall basicamente escreve dados em um arquivo. O primeiro argumento é um número que serve para identificar o arquivo para o qual queremos escrever os dados.

No Linux a saída e entrada de um programa nada mais é que dados sendo escritos e lidos em arquivos. E isso é feito por três arquivos que estão por padrão abertos em um programa e tem sempre o mesmo file descriptor, são eles:

Nome

File descriptor

Descrição

stdin

0

Entrada de dados (o que é digitado pelo usuário)

stdout

1

Saída padrão (o que é impresso no terminal)

stderr

2

Saída de erro (também impresso no terminal, porém destinado a mensagens de erro)

Entry point

Reparou que nosso programa tem um símbolo _start e que magicamente esse é o código que o sistema operacional está executando primeiro? Isso acontece porque o linker definiu o endereço daquele símbolo como o entry point (ponto de entrada) do nosso programa.

O entry point nada mais é o que o próprio nome sugere, o endereço inicial de execução do programa. Eu sei o que você está pensando:

Então a função main de um programa em C é o entry point?

A resposta é não! Um programa em C usando a libc tem uma série de códigos que são executados antes da main. E o primeiro deles, pasme, é uma função chamada _start definida pela própria libc.

Na verdade qualquer símbolo pode ser definido como o entry point para o executável, não faz diferença qual nome você dá para ele. Só que _start é o símbolo padrão que o ld define como entry point.

Se você quiser usar um símbolo diferente é só especificar com a opção -e. Por exemplo, podemos reescrever nosso Hello World assim:

bits 64

section .rodata
  msg:     db  `Hello World!\n`
  MSG_SIZE equ $-msg

section .text

global _eu_que_mando_no_meu_exec
_eu_que_mando_no_meu_exec:
  mov rax, 1
  mov rdi, 1
  mov rsi, msg
  mov rdx, MSG_SIZE
  syscall

  mov rax, 60
  xor rdi, rdi
  syscall

E compilar assim:

$ nasm hello.asm -felf64
$ ld hello.o -o hello -e _eu_que_mando_no_meu_exec
$ ./hello

Fácil fazer um "Hello World", né? Ei, o que acha de fazer uns macros para melhorar o uso dessas syscalls aí? Seria interessante também salvar os macros em um arquivo separado e incluir o arquivo com a diretiva %include.

PreviousSyscall no LinuxNextRevisão

Last updated 3 years ago

Was this helpful?

Se quiser ver o código de implementação desta syscall no Linux, pode .

ver aqui