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