forked from blau_araujo/cblc
artigo sobre linguagens de alto e baixo nível
This commit is contained in:
parent
025a49ef3b
commit
3efbda0330
1 changed files with 764 additions and 0 deletions
764
artigos/alto-e-baixo-nivel.org
Normal file
764
artigos/alto-e-baixo-nivel.org
Normal file
|
@ -0,0 +1,764 @@
|
|||
#+title: 2 -- Linguagens de Montagem e a Compilação
|
||||
#+author: Blau Araujo
|
||||
#+email: cursos@blauaraujo.com
|
||||
|
||||
#+options: toc:3
|
||||
|
||||
* Objetivos
|
||||
|
||||
- Compreender o papel das linguagens de montagem.
|
||||
- Distinguir montagem, compilação e linkagem.
|
||||
- Conhecer o funcionamento do NASM e do `ld`.
|
||||
- Utilizar ferramentas como `objdump` e `readelf`.
|
||||
|
||||
* Do código-fonte ao binário
|
||||
|
||||
Quando programamos um computador, geralmente escrevemos textos que correspondem
|
||||
a instruções em linguagens que nós, humanos, conseguimos ler com maior ou menor
|
||||
facilidade. Os arquivos com os textos escritos nessas linguagens são chamados
|
||||
de /códigos-fonte/, mas o processador da máquina (a sua CPU) não é capaz de
|
||||
interpretá-los diretamente. Para a CPU, instruções são os conjuntos de sinais
|
||||
elétricos digitais que ela identifica como /códigos de operação/ (/OpCodes/) ou,
|
||||
de forma mais genérica, como /código de máquina/.
|
||||
|
||||
** Arquivos texto e binários
|
||||
|
||||
O caminho entre o que nós escrevemos nos códigos-fonte dos nossos programas e o
|
||||
código de máquina, que a CPU consegue executar, envolve toda uma série de
|
||||
transformações que resulta na montagem daquilo que nós chamamos de /arquivo
|
||||
binário executável/. No fundo, todo arquivo é binário, ou seja, todos eles contêm
|
||||
informações que podem ser convertidas em estados elétricos alto ou baixo que,
|
||||
no fim das contas, equivalem a números escritos na base 2 (números /binários/).
|
||||
|
||||
#+begin_quote
|
||||
*Curiosidade:* consegue notar que programas são apenas números gigantescos?
|
||||
#+end_quote
|
||||
|
||||
O que diferencia os arquivos texto, em relação aos binários, é que os números
|
||||
que eles contêm são escritos de modo a codificar a representação de caracteres
|
||||
utilizados para formar textos (codificação ASCII, por exemplo). Nos arquivos
|
||||
binários, por sua vez, o que importa é a codificação de dados, que podem
|
||||
representar diversos tipos de informações, como os /pixels/ de uma imagem, a
|
||||
intensidade sonora ao longo do tempo de uma música ou, no nosso caso, as
|
||||
instruções de um programa em código de máquina.
|
||||
|
||||
** Formato de binário executável
|
||||
|
||||
Voltando aos arquivos binários executáveis, além das instruções e dados do
|
||||
programa, eles devem conter informações estruturadas que serão utilizadas pelo
|
||||
sistema operacional para, primeiro, determinar se o arquivo é um binário
|
||||
executável válido e, segundo, para definir como suas várias partes serão
|
||||
copiadas para a memória, se ele puder ser executado. A estrutura do binário,
|
||||
portanto, deve obedecer às convenções específicas de cada sistema operacional
|
||||
para arquivos executáveis. Esse conjunto de regras é chamado de /formato de
|
||||
executável/ e no sistema GNU/Linux (e sistemas /Unix-like/, em geral) é utilizado
|
||||
o formato ELF (de /Executable and Linkable Format/), que é aplicado a:
|
||||
|
||||
- Arquivos binários executáveis
|
||||
- Arquivos objeto
|
||||
- Bibliotecas compartilhadas
|
||||
- Imagens de memória geradas em casos de falha (/core dumps/)
|
||||
|
||||
A parte /"linkable"/ do formato ELF refere-se ao fato de que os arquivos objeto
|
||||
gerados podem ser ligados (isto é, combinados com outros objetos binários) para
|
||||
formar programas executáveis completos, seja por meio de ligações estáticas ou
|
||||
dinâmicas:
|
||||
|
||||
- *Ligação estática:* todos os objetos e bibliotecas são combinados em um único
|
||||
binário executável.
|
||||
- *Ligação dinâmica:* o binário executável contém referências a bibliotecas
|
||||
externas que serão carregadas na memória em tempo de execução.
|
||||
|
||||
Seja qual for o modo de ligação, os objetos e bibliotecas compartilhadas devem
|
||||
seguir as especificações do formato ELF.
|
||||
|
||||
* Programação em código de máquina
|
||||
|
||||
É possível, embora raro e extremamente trabalhoso, escrever diretamente em
|
||||
código de máquina: ou seja, especificando manualmente os bytes que representam
|
||||
as instruções da CPU. Abaixo, nós temos o arquivo texto com os 96 bytes de um
|
||||
programa escrito diretamente em código de máquina ([[exemplos/02/ok.txt][ok.txt]]):
|
||||
|
||||
#+begin_example
|
||||
7f 45 4c 46 01 01 01 00 00 00 00 00
|
||||
00 00 00 00 02 00 03 00 01 00 00 00
|
||||
54 80 04 08 34 00 00 00 00 00 00 00
|
||||
00 00 00 00 34 00 20 00 01 00 28 00
|
||||
00 00 00 00 01 00 00 00 54 00 00 00
|
||||
54 80 04 08 00 00 00 00 0c 00 00 00
|
||||
0c 00 00 00 05 00 00 00 00 10 00 00
|
||||
b8 01 00 00 00 bb 00 00 00 00 cd 80
|
||||
#+end_example
|
||||
|
||||
Para criar um arquivo binário com esses bytes, eu posso utilizar o utilitário
|
||||
=xxd=, geralmente disponível quando se instala o editor Vim:
|
||||
|
||||
#+begin_example
|
||||
:~$ xxd -r -p ok.txt > ok.bin
|
||||
:~$ ls
|
||||
ok.bin ok.txt
|
||||
#+end_example
|
||||
|
||||
Onde:
|
||||
|
||||
- =-r=: Impressão reversa (de texto para binário).
|
||||
- =-p=: Despejo bruto hexadecimal (apenas os dígitos válidos para hexadecimais).
|
||||
- =>=: Operador de redirecionamento de escrita do shell.
|
||||
|
||||
O =xxd= também pode ser utilizado para visualizar o conteúdo do arquivo binário:
|
||||
|
||||
#+begin_example
|
||||
:~$ xxd ok.bin
|
||||
00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
|
||||
00000010: 0200 0300 0100 0000 5480 0408 3400 0000 ........T...4...
|
||||
00000020: 0000 0000 0000 0000 3400 2000 0100 2800 ........4. ...(.
|
||||
00000030: 0000 0000 0100 0000 5400 0000 5480 0408 ........T...T...
|
||||
00000040: 0000 0000 0c00 0000 0c00 0000 0500 0000 ................
|
||||
00000050: 0010 0000 b801 0000 00bb 0000 0000 cd80 ................
|
||||
#+end_example
|
||||
|
||||
Para facilitar as próximas demonstrações, nós vamos formatar a impressão em
|
||||
12 colunas com grupos de apenas 1 byte:
|
||||
|
||||
#+begin_example
|
||||
:~$ xxd -c 12 -g 1 ok.bin
|
||||
00000000: 7f 45 4c 46 01 01 01 00 00 00 00 00 .ELF........
|
||||
0000000c: 00 00 00 00 02 00 03 00 01 00 00 00 ............
|
||||
00000018: 54 80 04 08 34 00 00 00 00 00 00 00 T...4.......
|
||||
00000024: 00 00 00 00 34 00 20 00 01 00 28 00 ....4. ...(.
|
||||
00000030: 00 00 00 00 01 00 00 00 54 00 00 00 ........T...
|
||||
0000003c: 54 80 04 08 00 00 00 00 0c 00 00 00 T...........
|
||||
00000048: 0c 00 00 00 05 00 00 00 00 10 00 00 ............
|
||||
00000054: b8 01 00 00 00 bb 00 00 00 00 cd 80 ............
|
||||
#+end_example
|
||||
|
||||
Os primeiros 4 bytes (=7f 45 4c 46=) são o /número mágico/ que identifica o formato
|
||||
ELF para o sistema operacional. Na coluna da representação em ASCII, na saída
|
||||
do =xxd=, nós podemos ver que os 3 últimos bytes do número mágico correspondem
|
||||
aos caracteres =ELF=. Isso pode ser encontrado em qualquer arquivo binário, por
|
||||
exemplo, em um arquivo de imagem PNG:
|
||||
|
||||
#+begin_example
|
||||
:~$ xxd -c 12 -g 1 foto.png | head -1
|
||||
00000000: 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d .PNG........
|
||||
#+end_example
|
||||
|
||||
A primeira coluna da saída do =xxd= mostra, em hexadecimal a posição que o
|
||||
primeiro byte de cada linha impressa ocupa no arquivo. Com base nisso, observe
|
||||
que tudo que vem antes do byte =0x54= (byte na posição 84) são dados calculados
|
||||
ou convencionados para atender às especificações do formato de arquivos
|
||||
binários executáveis ELF32 (32 bits) -- o programa em si, são apenas os 12
|
||||
últimos bytes:
|
||||
|
||||
#+begin_example
|
||||
00000054: b8 01 00 00 00 bb 00 00 00 00 cd 80 ............
|
||||
#+end_example
|
||||
|
||||
Aqui, estão os códigos de operação (/OpCodes/) da arquitetura x86 e seus
|
||||
respectivos operandos. Em Assembly 32 bits, eles corresponderiam a:
|
||||
|
||||
#+begin_example
|
||||
b8 01 00 00 00 -> mov eax, 0x00000001 ; Registrar o valor 1 em EAX (syscall exit).
|
||||
bb 00 00 00 00 -> mov ebx, 0x00000000 ; Registrar o valor 0 em EBX (estado de término).
|
||||
cd 80 -> int 0x80 ; Executar a interrupção 0x80 (executa a syscall).
|
||||
#+end_example
|
||||
|
||||
Nós falaremos sobre registradores e chamadas de sistemas mais adiante, mas
|
||||
esta tradução "reversa" (de código de máquina para Assembly) mostra que,
|
||||
seguindo as convenções de chamadas de sistema do Linux, eu copiei o valor
|
||||
=1= no registrador =EAX= para informar que queria executar a chamada de sistema
|
||||
=exit=, copiei =0= em =EBX= para definir o valor retornado pelo programa ao
|
||||
terminar (estado de término) e mandei executar a interrupção =0x80= que,
|
||||
em arquiteturas 32 bits, é como as chamadas de sistema são executadas.
|
||||
|
||||
Resumindo, meu programa não faz nada além de terminar com estado =0= (que
|
||||
significa /sucesso/, para o sistema). Para confirmar, vamos dar permissão
|
||||
de execução ao arquivo binário e observar seu estado de término:
|
||||
|
||||
#+begin_example
|
||||
:~$ chmod +x ok.bin
|
||||
:~$ ./ok.bin
|
||||
:~$ echo $?
|
||||
0
|
||||
#+end_example
|
||||
|
||||
Mas podemos alterar o programa de modo que ele terminasse retornando,
|
||||
por exemplo, o valor =42=. Para isso, nós precisamos de um editor de arquivos
|
||||
binários, como o =hexedit= ou o =bvi=. Como sou mais familiarizado com os
|
||||
editores Vi e Vim, eu vou utilizar o =bvi=. Para iniciar a edição:
|
||||
|
||||
#+begin_example
|
||||
:~$ bvi ok.bin
|
||||
#+end_example
|
||||
|
||||
No editor, nós veremos isso:
|
||||
|
||||
#+begin_example
|
||||
00000000 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 02 00 03 00 .ELF................
|
||||
00000014 01 00 00 00 54 80 04 08 34 00 00 00 00 00 00 00 00 00 00 00 ....T...4...........
|
||||
00000028 34 00 20 00 01 00 28 00 00 00 00 00 01 00 00 00 54 00 00 00 4. ...(.........T...
|
||||
0000003C 54 80 04 08 00 00 00 00 0C 00 00 00 0C 00 00 00 05 00 00 00 T...................
|
||||
00000050 00 10 00 00 B8 01 00 00 00 BB 00 00 00 00 CD 80 ................
|
||||
#+end_example
|
||||
|
||||
O valor que eu quero alterar está na linha iniciada pelo byte na posição
|
||||
=0x50=, logo depois do /OpCode/ =BB= (=MOV EBX=). O valor atual é formado por
|
||||
quatro bytes (32 bits) escritos na ordem /little endian/, ou seja, os bytes
|
||||
menos significativos vêm antes dos mais significativos. Portanto, se nós
|
||||
queremos escrever =0x0000002a= (valor hexadecimal de 42 em 4 bytes), o
|
||||
último byte (=2a=) deverá vir na frente dos demais.
|
||||
|
||||
Para iniciar a edição do arquivo, nós precisamos executar o comando:
|
||||
|
||||
#+begin_example
|
||||
:set memmove
|
||||
#+end_example
|
||||
|
||||
Agora, posicionando o cursor no byte que queremos substituir, nós teclamos
|
||||
=s= e digitamos =2a=, o que deixará a linha editada assim:
|
||||
|
||||
#+begin_example
|
||||
00000050 00 10 00 00 B8 01 00 00 00 BB 2A 00 00 00 CD 80 ..........*.....
|
||||
↑
|
||||
#+end_example
|
||||
|
||||
Teclando =ESC=, para voltar ao modo normal, nós podemos salvar e sair
|
||||
com o comando:
|
||||
|
||||
#+begin_example
|
||||
:wq
|
||||
#+end_example
|
||||
|
||||
Verificando o novo conteúdo do arquivo binário:
|
||||
|
||||
#+begin_example
|
||||
:~$ xxd -c 12 -g 1 ok.bin
|
||||
00000000: 7f 45 4c 46 01 01 01 00 00 00 00 00 .ELF........
|
||||
0000000c: 00 00 00 00 02 00 03 00 01 00 00 00 ............
|
||||
00000018: 54 80 04 08 34 00 00 00 00 00 00 00 T...4.......
|
||||
00000024: 00 00 00 00 34 00 20 00 01 00 28 00 ....4. ...(.
|
||||
00000030: 00 00 00 00 01 00 00 00 54 00 00 00 ........T...
|
||||
0000003c: 54 80 04 08 00 00 00 00 0c 00 00 00 T...........
|
||||
00000048: 0c 00 00 00 05 00 00 00 00 10 00 00 ............
|
||||
00000054: b8 01 00 00 00 bb 2a 00 00 00 cd 80 ......*.....
|
||||
#+end_example
|
||||
|
||||
Como o arquivo já tem as permissões necessárias, nós podemos executá-lo
|
||||
e verificar seu estado de término:
|
||||
|
||||
#+begin_example
|
||||
:~$ ./ok.bin
|
||||
:~$ echo $?
|
||||
42
|
||||
#+end_example
|
||||
|
||||
* Linguagens de baixo e alto nível
|
||||
|
||||
Criar e editar binários em código de máquina já foi comum nos primórdios da
|
||||
computação e ainda aparece em contextos de engenharia reversa, fins didáticos
|
||||
ou em casos altamente especializados, mas é impraticável para a criação de
|
||||
programas de maior complexidade. Por isso, foram desenvolvidas linguagens
|
||||
que, embora ainda muito próximas da escrita de código de máquina, tornaram
|
||||
esse processo mais viável.
|
||||
|
||||
É o caso da linguagem Assembly, classificada como uma linguagem de /baixo
|
||||
nível/ porque, embora abstraia a escrita do código de máquina por meio de
|
||||
mnemônicos e pseudo instruções legíveis por humanos, ainda nos obriga a
|
||||
descrever, passo a passo, cada uma das instruções que a CPU deverá executar
|
||||
para alcançar a finalidade do programa. Já nas linguagens de alto nível, os
|
||||
passos da CPU são abstraídos, de modo que nós podemos descrever nossos
|
||||
programas em termos de procedimentos que devem ser realizados, em vez de
|
||||
como esses procedimentos são implementados para serem executados pela
|
||||
máquina.
|
||||
|
||||
#+begin_quote
|
||||
*Nota:* É por isso que se diz que, com linguagens de alto nível, nos escrevemos
|
||||
/"o que fazer"/, ao passo que, com linguagens de baixo nível nós escrevemos
|
||||
/"como fazer"/.
|
||||
#+end_quote
|
||||
|
||||
Independentemente de estar escrito em uma linguagem de baixo ou alto nível,
|
||||
todo programa precisa, em algum momento, ser convertido em código de máquina
|
||||
para que possa ser executado pela CPU. Isso pode ocorrer por meio de tradução
|
||||
direta -- como nos processos de compilação e montagem -- ou de forma indireta,
|
||||
através da interpretação por outro programa que já esteja traduzido para código
|
||||
de máquina. Em outras palavras, os códigos-fonte só poderão ser executados se
|
||||
forem compilados, montados ou interpretados, dependendo das características da
|
||||
linguagem utilizada.
|
||||
|
||||
** Linguagens interpretadas
|
||||
|
||||
No caso das linguagens interpretadas, como Lua, Python ou PHP, por exemplo,
|
||||
é o binário do interpretador que é executado para ler, analisar e executar
|
||||
o conteúdo do código-fonte, conforme as suas instruções são processadas. Isso
|
||||
significa que o código de máquina é produzido em tempo real: o que é bastante
|
||||
flexível e prático, mas pode apresentar um desempenho inferior, se comparado
|
||||
com programas executados diretamente a partir de seus binários.
|
||||
|
||||
De modo geral, programas interpretados passam pelas seguintes etapas até
|
||||
serem executados:
|
||||
|
||||
- Leitura ::
|
||||
|
||||
O programa interpretador lê uma linha ou um bloco do código-fonte.
|
||||
|
||||
- Análise léxica ::
|
||||
|
||||
O trecho lido é dividido em palavras-chave, identificadores, operadores e
|
||||
outros símbolos significativos (um processo chamado de /tokenização/).
|
||||
|
||||
- Análise sintática ::
|
||||
|
||||
Os elementos léxicos são organizados conforme as regras gramaticais da
|
||||
linguagem, formando estruturas como expressões, comandos e blocos de
|
||||
controle.
|
||||
|
||||
- Execução ::
|
||||
|
||||
O interpretador, com base na estrutura sintática encontrada, executa a
|
||||
operação correspondente por meio de rotinas internas que já estão em
|
||||
código de máquina.
|
||||
|
||||
O processo se repete até que todo o conteúdo do código-fonte seja interpretado
|
||||
e suas instruções executadas.
|
||||
|
||||
** Linguagens compiladas
|
||||
|
||||
O termo /compilação/ é utilizado para designar, de forma geral, o processo de
|
||||
traduzir um código escrito numa linguagem de alto nível (como C, C++, Rust
|
||||
ou Pascal) para um arquivo binário no formato que o sistema computacional
|
||||
em questão seja capaz de carregar na memória e executar. Essa tradução
|
||||
é feita por programas chamados de /compiladores/, mas eles podem atuar de
|
||||
formas diferentes, a depender do conceito que pretendam implementar.
|
||||
|
||||
Tomando a linguagem C como exemplo, compiladores como o =cc= (comum em sistemas
|
||||
BSD e Unix) e o =gcc= (do Projeto GNU) realizam o processo de compilação em até
|
||||
quatro etapas principais:
|
||||
|
||||
- Pré-processamento ::
|
||||
|
||||
As diretivas encontradas no código-fonte (como =#include= e =#define=), além de
|
||||
outros requisitos implícitos, são processadas para gerar uma versão expandida
|
||||
do programa, ainda escrita em C.
|
||||
|
||||
Podemos interromper a compilação após esta etapa e inspecionar o resultado com:
|
||||
|
||||
#+begin_example
|
||||
gcc -E arquivo.c
|
||||
#+end_example
|
||||
|
||||
O resultado é impresso no terminal, sem gerar nenhum arquivo.
|
||||
|
||||
- Compilação ::
|
||||
|
||||
O código C pré-processado é traduzido para linguagem Assembly. Esse passo pode
|
||||
ocorrer de forma interna, mas também pode ser observado explicitamente gerando
|
||||
um arquivo com extensão =.s=:
|
||||
|
||||
#+begin_example
|
||||
gcc -S arquivo.c
|
||||
#+end_example
|
||||
|
||||
Note que esta etapa também é chamada de /compilação/, porque o termo se aplica
|
||||
tanto ao processo completo quanto a essa etapa específica.
|
||||
|
||||
- Montagem ::
|
||||
|
||||
O código em Assembly (interno ou gerado) é convertido em um arquivo objeto
|
||||
binário, normalmente com extensão =.o=. Podemos interromper a compilação aqui
|
||||
com:
|
||||
|
||||
#+begin_example
|
||||
gcc -c arquivo.c
|
||||
#+end_example
|
||||
|
||||
- Link-edição (ou apenas /ligação/) ::
|
||||
|
||||
O arquivo objeto é combinado com outros arquivos objeto (caso haja) e com
|
||||
as bibliotecas necessárias. Isso pode resultar na junção de código proveniente
|
||||
de bibliotecas estáticas (ligações estáticas) e na inserção de referências
|
||||
a bibliotecas compartilhadas (ligações dinâmicas).
|
||||
|
||||
Ao final, nós teremos um binário executável formatado de acordo com o padrão
|
||||
do sistema (como ELF, no Linux). Essa etapa é realizada pelo programa =ld=,
|
||||
geralmente invocado automaticamente pelo compilador. Contudo, mesmo no caso
|
||||
da linguagem C, existem compiladores que adotam propostas diferentes, gerando
|
||||
código de máquina sem passar explicitamente por uma etapa intermediária de
|
||||
tradução para Assembly.
|
||||
|
||||
** Linguagens de montagem
|
||||
|
||||
Quando se trata de linguagens de baixo nível, o que nós temos é, basicamente,
|
||||
um conjunto de mnemônicos para os /OpCodes/ da CPU e algumas pseudoinstruções
|
||||
para definir estruturas de dados e organizar as seções do que virá a ser o
|
||||
arquivo binário do programa. Em outras palavras, um código em Assembly pode
|
||||
ser visto como um "manual de construção" do código de máquina: daí o nome
|
||||
/"assembly"/, que significa /"montagem"/ em inglês. Consequentemente, o programa
|
||||
utilizado para montar o arquivo binário com o código de máquina a partir de
|
||||
um fonte em linguagem de montagem é chamado de "montador" -- ou /"assembler"/,
|
||||
em inglês.
|
||||
|
||||
As pequenas (embora importantes) diferenças entre as várias linguagens de
|
||||
montagem existentes são determinadas, essencialmente, pelas particularidades
|
||||
dos montadores que as implementam e pelas especificações das arquiteturas de
|
||||
hardware e software para as quais esses montadores foram projetados. Além
|
||||
disso, montadores como o NASM (/Netwide Assembler/) e GAS (/GNU Assembler/), por
|
||||
exemplo, não produzem arquivos binários executáveis diretamente. Isso tem
|
||||
a vantagem de possibilitar o uso dos arquivos objeto gerados de várias formas,
|
||||
mas requer que uma etapa posterior de link-edição seja realizada para que se
|
||||
tenha um binário executável. Já o montador FASM (/Fast Assembler/), também
|
||||
bastante utilizado, dispensa a etapa final de link-edição, mas sua integração
|
||||
com código em C não é tão direta ou automatizada quanto em montadores como
|
||||
o NASM, exigindo mais controle manual por parte de quem programa.
|
||||
|
||||
Neste curso, a escolha do montador NASM deve-se a alguns motivos:
|
||||
|
||||
- Utiliza a sintaxe Intel por padrão.
|
||||
- Tem um suporte a macros simples e poderoso.
|
||||
- Tem um ótimo suporte à integração com código em C.
|
||||
- É uma linguagem fartamente documentada.
|
||||
- Pode ser aprendida com muita facilidade por iniciantes.
|
||||
|
||||
* Exemplos comparativos
|
||||
|
||||
** Programa em Assembly
|
||||
|
||||
Aqui, nós temos um programa escrito em Assembly 64 bits (NASM) para imprimir
|
||||
a mensagem =Salve, simpatia!= no terminal e sair com estado de término =0=...
|
||||
|
||||
Arquivo: [[exemplos/02/salve.asm][salve.asm]]
|
||||
|
||||
#+begin_src nasm :tangle exemplos/02/salve.asm
|
||||
section .rodata
|
||||
msg db "Salve, simpatia!", 10
|
||||
len equ $ - msg
|
||||
|
||||
section .text
|
||||
global _start
|
||||
|
||||
_start:
|
||||
mov rax, 1 ; syscall write
|
||||
mov rdi, 1 ; stdout
|
||||
mov rsi, msg
|
||||
mov rdx, len
|
||||
syscall
|
||||
|
||||
mov rax, 60 ; syscall exit
|
||||
mov rdi, 0 ; código de saída 0
|
||||
syscall
|
||||
#+end_src
|
||||
|
||||
Por enquanto, não importa entender o que está no código, mas observar seu
|
||||
aspecto geral que, claramente, define passo a passo o que a CPU deve fazer
|
||||
e organiza os dados e as instruções nas seções que deverão ocupar no
|
||||
arquivo binário resultante. A mensagem que queremos imprimir, por exemplo,
|
||||
está na seção chamada de =.rodata=, que é onde dados constantes são escritos
|
||||
no arquivo binário.
|
||||
|
||||
Montando e link-editando o programa:
|
||||
|
||||
#+begin_example
|
||||
:~$ nasm -f elf64 -o salve.o salve.asm
|
||||
:~$ ld -o salve salve.o
|
||||
#+end_example
|
||||
|
||||
Como não foram exibidas mensagens de erro ou de aviso, nós sabemos que
|
||||
tudo correu bem e já podemos executar o programa:
|
||||
|
||||
#+begin_example
|
||||
:~$ ./salve
|
||||
Salve, simpatia!
|
||||
#+end_example
|
||||
|
||||
** Inspeção do arquivo binário
|
||||
|
||||
*** Tamanho do binário em bytes
|
||||
|
||||
#+begin_example
|
||||
:~$ stat -c '%s' salve
|
||||
8880
|
||||
#+end_example
|
||||
|
||||
*** Informações gerais do arquivo
|
||||
|
||||
#+begin_example
|
||||
:~$ file salve
|
||||
salve: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked,
|
||||
not stripped
|
||||
#+end_example
|
||||
|
||||
*** Cabeçalho ELF
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -h salve
|
||||
Cabeçalho ELF:
|
||||
Magia: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
|
||||
Classe: ELF64
|
||||
Dados: complemento 2, little endian
|
||||
Versão: 1 (actual)
|
||||
OS/ABI: UNIX - System V
|
||||
Versão ABI: 0
|
||||
Tipo: EXEC (ficheiro executável)
|
||||
Máquina: Advanced Micro Devices X86-64
|
||||
Versão: 0x1
|
||||
Endereço do ponto de entrada: 0x401000
|
||||
Início dos cabeçalhos do programa: 64 (bytes no ficheiro)
|
||||
Start of section headers: 8496 (bytes no ficheiro)
|
||||
Bandeiras: 0x0
|
||||
Tamanho deste cabeçalho: 64 (bytes)
|
||||
Tamanho dos cabeçalhos do programa:56 (bytes)
|
||||
Nº de cabeçalhos do programa: 3
|
||||
Tamanho dos cabeçalhos de secção: 64 (bytes)
|
||||
Nº dos cabeçalhos de secção: 6
|
||||
Índice de tabela de cadeias da secção: 5
|
||||
#+end_example
|
||||
|
||||
*** Lista de seções
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -l salve
|
||||
|
||||
Tipo de ficheiro Elf é EXEC (ficheiro executável)
|
||||
Entry point 0x401000
|
||||
There are 3 program headers, starting at offset 64
|
||||
|
||||
Cabeçalhos do programa:
|
||||
Tipo Desvio EndVirtl EndFís
|
||||
TamFich TamMem Bndrs Alinh
|
||||
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
|
||||
0x00000000000000e8 0x00000000000000e8 R 0x1000
|
||||
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
|
||||
0x0000000000000025 0x0000000000000025 R E 0x1000
|
||||
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
|
||||
0x0000000000000011 0x0000000000000011 R 0x1000
|
||||
|
||||
Secção para mapa do segmento:
|
||||
Secções do segmento...
|
||||
00
|
||||
01 .text
|
||||
02 .rodata
|
||||
#+end_example
|
||||
|
||||
*** Conteúdo (em hexa) da seção .text
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -x .text salve
|
||||
|
||||
Despejo máximo da secção ".text":
|
||||
0x00401000 b8010000 00bf0100 000048be 00204000 ..........H.. @.
|
||||
0x00401010 00000000 ba110000 000f05b8 3c000000 ............<...
|
||||
0x00401020 4831ff0f 05 H1...
|
||||
#+end_example
|
||||
|
||||
|
||||
#+begin_src sh
|
||||
file salve
|
||||
readelf -h salve
|
||||
objdump -d salve
|
||||
#+end_src
|
||||
|
||||
*** Conteúdo (em hexa) da seção .rodata
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -x .rodata salve
|
||||
|
||||
Despejo máximo da secção ".rodata":
|
||||
0x00402000 53616c76 652c2073 696d7061 74696121 Salve, simpatia!
|
||||
0x00402010 0a .
|
||||
#+end_example
|
||||
|
||||
** Versão equivalente em C
|
||||
|
||||
Tentando ser o mais próximo possível ao que foi escrito em Assembly, esta
|
||||
seria uma possível versão equivalente em C...
|
||||
|
||||
Arquivo: [[exemplos/02/salve.c][salve.c]]
|
||||
|
||||
#+begin_src C :tangle exemplos/02/salve.c
|
||||
#include <unistd.h>
|
||||
|
||||
// msg db "Salve, simpatia!", 10
|
||||
const char msg[] = "Salve, simpatia!\n";
|
||||
|
||||
// len equ $ - msg
|
||||
#define LEN sizeof(msg) - 1
|
||||
|
||||
int main(void) {
|
||||
/*
|
||||
mov rax, 1 ; syscall write
|
||||
mov rdi, 1 ; stdout
|
||||
mov rsi, msg
|
||||
mov rdx, len
|
||||
syscall
|
||||
,*/
|
||||
write(1, msg, LEN);
|
||||
|
||||
/*
|
||||
mov rax, 60 ; syscall exit
|
||||
mov rdi, 0 ; código de saída 0
|
||||
syscall
|
||||
,*/
|
||||
_exit(0);
|
||||
}
|
||||
#+end_src
|
||||
|
||||
Compilando e executando:
|
||||
|
||||
#+begin_example
|
||||
:~$ gcc -o salvec salve.c
|
||||
:~$ ./salvec
|
||||
Salve, simpatia!
|
||||
#+end_example
|
||||
|
||||
** Inspeção do binário produzido com o código em C
|
||||
|
||||
*** Tamanho do binário em bytes:
|
||||
|
||||
#+begin_example
|
||||
:~$ stat -c '%s' salvec
|
||||
16032
|
||||
#+end_example
|
||||
|
||||
*** Informações gerais do arquivo
|
||||
|
||||
#+begin_example
|
||||
:~$ file salvec
|
||||
salvec: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked,
|
||||
interpreter /lib64/ld-linux-x86-64.so.2,
|
||||
BuildID[sha1]=351e5395c71e0b758b9cc05a68f3813f8f130817, for GNU/Linux 3.2.0,
|
||||
not stripped
|
||||
#+end_example
|
||||
|
||||
*** Cabeçalho ELF
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -h salvec
|
||||
Cabeçalho ELF:
|
||||
Magia: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
|
||||
Classe: ELF64
|
||||
Dados: complemento 2, little endian
|
||||
Versão: 1 (actual)
|
||||
OS/ABI: UNIX - System V
|
||||
Versão ABI: 0
|
||||
Tipo: DYN (Position-Independent Executable file)
|
||||
Máquina: Advanced Micro Devices X86-64
|
||||
Versão: 0x1
|
||||
Endereço do ponto de entrada: 0x1060
|
||||
Início dos cabeçalhos do programa: 64 (bytes no ficheiro)
|
||||
Start of section headers: 14048 (bytes no ficheiro)
|
||||
Bandeiras: 0x0
|
||||
Tamanho deste cabeçalho: 64 (bytes)
|
||||
Tamanho dos cabeçalhos do programa:56 (bytes)
|
||||
Nº de cabeçalhos do programa: 14
|
||||
Tamanho dos cabeçalhos de secção: 64 (bytes)
|
||||
Nº dos cabeçalhos de secção: 31
|
||||
Índice de tabela de cadeias da secção: 30
|
||||
#+end_example
|
||||
|
||||
*** Lista de seções
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -l salvec
|
||||
|
||||
Tipo de ficheiro Elf é DYN (Position-Independent Executable file)
|
||||
Entry point 0x1060
|
||||
There are 14 program headers, starting at offset 64
|
||||
|
||||
Cabeçalhos do programa:
|
||||
Tipo Desvio EndVirtl EndFís
|
||||
TamFich TamMem Bndrs Alinh
|
||||
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
|
||||
0x0000000000000310 0x0000000000000310 R 0x8
|
||||
INTERP 0x0000000000000394 0x0000000000000394 0x0000000000000394
|
||||
0x000000000000001c 0x000000000000001c R 0x1
|
||||
[A pedir interpretador do programa: /lib64/ld-linux-x86-64.so.2]
|
||||
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
|
||||
0x0000000000000660 0x0000000000000660 R 0x1000
|
||||
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
|
||||
0x0000000000000179 0x0000000000000179 R E 0x1000
|
||||
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
|
||||
0x0000000000000118 0x0000000000000118 R 0x1000
|
||||
LOAD 0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0
|
||||
0x0000000000000250 0x0000000000000258 RW 0x1000
|
||||
DYNAMIC 0x0000000000002de0 0x0000000000003de0 0x0000000000003de0
|
||||
0x00000000000001e0 0x00000000000001e0 RW 0x8
|
||||
NOTE 0x0000000000000350 0x0000000000000350 0x0000000000000350
|
||||
0x0000000000000020 0x0000000000000020 R 0x8
|
||||
NOTE 0x0000000000000370 0x0000000000000370 0x0000000000000370
|
||||
0x0000000000000024 0x0000000000000024 R 0x4
|
||||
NOTE 0x00000000000020f8 0x00000000000020f8 0x00000000000020f8
|
||||
0x0000000000000020 0x0000000000000020 R 0x4
|
||||
GNU_PROPERTY 0x0000000000000350 0x0000000000000350 0x0000000000000350
|
||||
0x0000000000000020 0x0000000000000020 R 0x8
|
||||
GNU_EH_FRAME 0x0000000000002024 0x0000000000002024 0x0000000000002024
|
||||
0x000000000000002c 0x000000000000002c R 0x4
|
||||
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
|
||||
0x0000000000000000 0x0000000000000000 RW 0x10
|
||||
GNU_RELRO 0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0
|
||||
0x0000000000000230 0x0000000000000230 R 0x1
|
||||
|
||||
Secção para mapa do segmento:
|
||||
Secções do segmento...
|
||||
00
|
||||
01 .interp
|
||||
02 .note.gnu.property .note.gnu.build-id .interp .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
|
||||
03 .init .plt .plt.got .text .fini
|
||||
04 .rodata .eh_frame_hdr .eh_frame .note.ABI-tag
|
||||
05 .init_array .fini_array .dynamic .got .got.plt .data .bss
|
||||
06 .dynamic
|
||||
07 .note.gnu.property
|
||||
08 .note.gnu.build-id
|
||||
09 .note.ABI-tag
|
||||
10 .note.gnu.property
|
||||
11 .eh_frame_hdr
|
||||
12
|
||||
13 .init_array .fini_array .dynamic .got
|
||||
#+end_example
|
||||
|
||||
*** Conteúdo (hexa) da seção .text
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -x .text salvec
|
||||
|
||||
Despejo máximo da secção ".text":
|
||||
0x00001060 31ed4989 d15e4889 e24883e4 f0505445 1.I..^H..H...PTE
|
||||
0x00001070 31c031c9 488d3dce 000000ff 153f2f00 1.1.H.=......?/.
|
||||
0x00001080 00f4662e 0f1f8400 00000000 0f1f4000 ..f...........@.
|
||||
0x00001090 488d3d89 2f000048 8d05822f 00004839 H.=./..H.../..H9
|
||||
0x000010a0 f8741548 8b051e2f 00004885 c07409ff .t.H.../..H..t..
|
||||
0x000010b0 e00f1f80 00000000 c30f1f80 00000000 ................
|
||||
0x000010c0 488d3d59 2f000048 8d35522f 00004829 H.=Y/..H.5R/..H)
|
||||
0x000010d0 fe4889f0 48c1ee3f 48c1f803 4801c648 .H..H..?H...H..H
|
||||
0x000010e0 d1fe7414 488b05ed 2e000048 85c07408 ..t.H......H..t.
|
||||
0x000010f0 ffe0660f 1f440000 c30f1f80 00000000 ..f..D..........
|
||||
0x00001100 f30f1efa 803d152f 00000075 2b554883 .....=./...u+UH.
|
||||
0x00001110 3dca2e00 00004889 e5740c48 8b3df62e =.....H..t.H.=..
|
||||
0x00001120 0000e829 ffffffe8 64ffffff c605ed2e ...)....d.......
|
||||
0x00001130 0000015d c30f1f00 c30f1f80 00000000 ...]............
|
||||
0x00001140 f30f1efa e977ffff ff554889 e5ba1100 .....w...UH.....
|
||||
0x00001150 0000488d 05b70e00 004889c6 bf010000 ..H......H......
|
||||
0x00001160 00e8dafe ffffbf00 000000e8 c0feffff ................
|
||||
#+end_example
|
||||
|
||||
*** Conteúdo (hexa) da seção .rodata
|
||||
|
||||
#+begin_example
|
||||
:~$ readelf -x .rodata salvec
|
||||
|
||||
Despejo máximo da secção ".rodata":
|
||||
0x00002000 01000200 00000000 00000000 00000000 ................
|
||||
0x00002010 53616c76 652c2073 696d7061 74696121 Salve, simpatia!
|
||||
0x00002020 0a00 ..
|
||||
#+end_example
|
||||
|
||||
|
||||
* Referências
|
||||
|
||||
- man xxd
|
||||
- man 2 write
|
||||
- man 2 exit
|
||||
- man readelf
|
||||
- man bvi
|
||||
- man stat
|
||||
- man file
|
||||
- https://nasm.us/doc/
|
Loading…
Add table
Reference in a new issue