atualização da aula 2
This commit is contained in:
parent
4321a16da0
commit
38aaa19866
1 changed files with 199 additions and 55 deletions
|
@ -1,15 +1,19 @@
|
||||||
#+title: 2 -- Linguagens de Montagem e a Compilação
|
#+title: 2 -- Linguagens, montagem e compilação
|
||||||
#+author: Blau Araujo
|
#+author: Blau Araujo
|
||||||
#+email: cursos@blauaraujo.com
|
#+email: cursos@blauaraujo.com
|
||||||
|
|
||||||
|
|
||||||
#+options: toc:3
|
#+options: toc:3
|
||||||
|
|
||||||
* Objetivos
|
* Objetivos
|
||||||
|
|
||||||
- Compreender o papel das linguagens de montagem.
|
- Distinguir as etapas de tradução de códigos-fonte para código de máquina.
|
||||||
- Distinguir montagem, compilação e linkagem.
|
- Conhecer algumas das formas como linguagens de programação são implementadas.
|
||||||
- Conhecer o funcionamento do NASM e do `ld`.
|
- Utilizar o NASM para montar arquivos objeto de programas em Assembly.
|
||||||
- Utilizar ferramentas como `objdump` e `readelf`.
|
- Utilizar o ligador =ld= para gerar binários executáveis a partir de objetos.
|
||||||
|
- Utilizar o =gcc= para compilar programas em C.
|
||||||
|
- Conhecer algumas ferramentas do GNU/Linux para inspecionar arquivos binários.
|
||||||
|
- Utilizar o programa =objdump= para desmontar conteúdos de binários executáveis.
|
||||||
|
|
||||||
* Do código-fonte ao binário
|
* Do código-fonte ao binário
|
||||||
|
|
||||||
|
@ -158,18 +162,21 @@ Aqui, estão os códigos de operação (/OpCodes/) da arquitetura x86 e seus
|
||||||
respectivos operandos. Em Assembly 32 bits, eles corresponderiam a:
|
respectivos operandos. Em Assembly 32 bits, eles corresponderiam a:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
b8 01 00 00 00 -> mov eax, 0x00000001 ; Registrar o valor 1 em EAX (syscall exit).
|
b8 01 00 00 00 -> mov eax, 0x00000001 ; Registrar o valor 1 em EAX
|
||||||
bb 00 00 00 00 -> mov ebx, 0x00000000 ; Registrar o valor 0 em EBX (estado de término).
|
bb 00 00 00 00 -> mov ebx, 0x00000000 ; Registrar o valor 0 em EBX
|
||||||
cd 80 -> int 0x80 ; Executar a interrupção 0x80 (executa a syscall).
|
cd 80 -> int 0x80 ; Executar a interrupção 0x80
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
Nós falaremos sobre registradores e chamadas de sistemas mais adiante, mas
|
Nós falaremos sobre registradores e chamadas de sistemas mais adiante no curso,
|
||||||
esta tradução "reversa" (de código de máquina para Assembly) mostra que,
|
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
|
seguindo as convenções de chamadas de sistema do Linux, nos permite ver que:
|
||||||
=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
|
- O valor =1= foi copiado no registrador =EAX=, para informar que eu queria
|
||||||
terminar (estado de término) e mandei executar a interrupção =0x80= que,
|
executar a chamada de sistema =exit=;
|
||||||
em arquiteturas 32 bits, é como as chamadas de sistema são executadas.
|
- O valor =0= foi copiado no registrador =EBX=, para definir o valor que seria
|
||||||
|
retornado pelo programa ao terminar (estado de término);
|
||||||
|
- E a interrupção =0x80= deveria ser executada para invocar a chamada de sistema
|
||||||
|
definida em =EAX= com o argumento em =EBX=.
|
||||||
|
|
||||||
Resumindo, meu programa não faz nada além de terminar com estado =0= (que
|
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
|
significa /sucesso/, para o sistema). Para confirmar, vamos dar permissão
|
||||||
|
@ -277,6 +284,8 @@ máquina.
|
||||||
/"como fazer"/.
|
/"como fazer"/.
|
||||||
#+end_quote
|
#+end_quote
|
||||||
|
|
||||||
|
* Sistemas de tradução de linguagens
|
||||||
|
|
||||||
Independentemente de estar escrito em uma linguagem de baixo ou alto nível,
|
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
|
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
|
para que possa ser executado pela CPU. Isso pode ocorrer por meio de tradução
|
||||||
|
@ -419,9 +428,7 @@ Neste curso, a escolha do montador NASM deve-se a alguns motivos:
|
||||||
- É uma linguagem fartamente documentada.
|
- É uma linguagem fartamente documentada.
|
||||||
- Pode ser aprendida com muita facilidade por iniciantes.
|
- Pode ser aprendida com muita facilidade por iniciantes.
|
||||||
|
|
||||||
* Exemplos comparativos
|
* Um programa em Assembly
|
||||||
|
|
||||||
** Programa em Assembly
|
|
||||||
|
|
||||||
Aqui, nós temos um programa escrito em Assembly 64 bits (NASM) para imprimir
|
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=...
|
a mensagem =Salve, simpatia!= no terminal e sair com estado de término =0=...
|
||||||
|
@ -452,7 +459,7 @@ 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
|
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
|
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,
|
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
|
está na seção chamada de =.rodata=, que é onde dados constantes serão escritos
|
||||||
no arquivo binário.
|
no arquivo binário.
|
||||||
|
|
||||||
Montando e link-editando o programa:
|
Montando e link-editando o programa:
|
||||||
|
@ -470,24 +477,57 @@ tudo correu bem e já podemos executar o programa:
|
||||||
Salve, simpatia!
|
Salve, simpatia!
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
** Inspeção do arquivo binário
|
A partir daqui, vamos utilizar diversas ferramentas disponíveis para o GNU/Linux
|
||||||
|
que nos ajudarão o obter informações sobre arquivos binários de programas.
|
||||||
|
|
||||||
*** Tamanho do binário em bytes
|
** Tamanho do binário em bytes
|
||||||
|
|
||||||
|
Com o utilitário =ls=:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ ls -l salve
|
||||||
|
-rwxrwxr-x 1 blau blau 8880 mai 16 10:15 salve
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
Com o utilitário =du=:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ du -b salve
|
||||||
|
8880
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
Com o utilitário =stat=:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ stat -c '%s' salve
|
:~$ stat -c '%s' salve
|
||||||
8880
|
8880
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Informações gerais do arquivo
|
** Informações gerais do arquivo
|
||||||
|
|
||||||
|
Com o utilitário =file=:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ file salve
|
:~$ file salve
|
||||||
salve: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked,
|
salve: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically
|
||||||
not stripped
|
linked, not stripped
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Cabeçalho ELF
|
Com o utilitário =stat=:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ stat salve
|
||||||
|
Arquivo: salve
|
||||||
|
Tamanho: 8880 Blocos: 24 bloco de E/S: 4096 regular file
|
||||||
|
Dispositivo: 8,18 Inode: 4856471 Ligações: 1
|
||||||
|
Acesso: (0775/-rwxrwxr-x) Uid: ( 1000/ blau) Gid: ( 1000/ blau)
|
||||||
|
Acesso: 2025-05-16 10:15:50.611903536 -0300
|
||||||
|
Modificação: 2025-05-16 10:15:46.279952507 -0300
|
||||||
|
Alteração: 2025-05-16 10:15:46.279952507 -0300
|
||||||
|
Criação: 2025-05-16 10:15:46.275952553 -0300
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
** Cabeçalho do formato ELF
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -h salve
|
:~$ readelf -h salve
|
||||||
|
@ -513,7 +553,7 @@ Cabeçalho ELF:
|
||||||
Índice de tabela de cadeias da secção: 5
|
Índice de tabela de cadeias da secção: 5
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Lista de seções
|
** Lista de seções do programa
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -l salve
|
:~$ readelf -l salve
|
||||||
|
@ -539,7 +579,7 @@ Cabeçalhos do programa:
|
||||||
02 .rodata
|
02 .rodata
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Conteúdo (em hexa) da seção .text
|
** Despejo do conteúdo (em hexa) da seção .text
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -x .text salve
|
:~$ readelf -x .text salve
|
||||||
|
@ -550,14 +590,7 @@ Despejo máximo da secção ".text":
|
||||||
0x00401020 4831ff0f 05 H1...
|
0x00401020 4831ff0f 05 H1...
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
|
** Despejo do conteúdo (em hexa) da seção .rodata
|
||||||
#+begin_src sh
|
|
||||||
file salve
|
|
||||||
readelf -h salve
|
|
||||||
objdump -d salve
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
*** Conteúdo (em hexa) da seção .rodata
|
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -x .rodata salve
|
:~$ readelf -x .rodata salve
|
||||||
|
@ -567,10 +600,10 @@ Despejo máximo da secção ".rodata":
|
||||||
0x00402010 0a .
|
0x00402010 0a .
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
** Versão equivalente em C
|
* Uma versão equivalente em C
|
||||||
|
|
||||||
Tentando ser o mais próximo possível ao que foi escrito em Assembly, esta
|
Tentando manter a maior proximidade possível com o que foi escrito em Assembly,
|
||||||
seria uma possível versão equivalente em C...
|
esta seria uma possível versão equivalente em C...
|
||||||
|
|
||||||
Arquivo: [[exemplos/02/salve.c][salve.c]]
|
Arquivo: [[exemplos/02/salve.c][salve.c]]
|
||||||
|
|
||||||
|
@ -610,26 +643,28 @@ Compilando e executando:
|
||||||
Salve, simpatia!
|
Salve, simpatia!
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
** Inspeção do binário produzido com o código em C
|
Apesar de fazer a mesma coisa praticamente do mesmo modo, o arquivo binário
|
||||||
|
gerado a partir de um código em C tem diferenças importantes, como veremos
|
||||||
|
a seguir.
|
||||||
|
|
||||||
*** Tamanho do binário em bytes:
|
** Tamanho do binário em bytes:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ stat -c '%s' salvec
|
:~$ du -b salvec
|
||||||
16032
|
16032
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Informações gerais do arquivo
|
** Informações gerais do arquivo
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ file salvec
|
:~$ file salvec
|
||||||
salvec: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked,
|
salvec: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically
|
||||||
interpreter /lib64/ld-linux-x86-64.so.2,
|
linked, interpreter /lib64/ld-linux-x86-64.so.2,
|
||||||
BuildID[sha1]=351e5395c71e0b758b9cc05a68f3813f8f130817, for GNU/Linux 3.2.0,
|
BuildID[sha1]=351e5395c71e0b758b9cc05a68f3813f8f130817, for GNU/Linux 3.2.0,
|
||||||
not stripped
|
not stripped
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Cabeçalho ELF
|
** Cabeçalho do formato ELF
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -h salvec
|
:~$ readelf -h salvec
|
||||||
|
@ -655,7 +690,7 @@ Cabeçalho ELF:
|
||||||
Índice de tabela de cadeias da secção: 30
|
Índice de tabela de cadeias da secção: 30
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Lista de seções
|
** Lista de seções do programa
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -l salvec
|
:~$ readelf -l salvec
|
||||||
|
@ -715,7 +750,7 @@ Cabeçalhos do programa:
|
||||||
13 .init_array .fini_array .dynamic .got
|
13 .init_array .fini_array .dynamic .got
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Conteúdo (hexa) da seção .text
|
** Despejo do conteúdo (em hexa) da seção .text
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -x .text salvec
|
:~$ readelf -x .text salvec
|
||||||
|
@ -740,7 +775,7 @@ Despejo máximo da secção ".text":
|
||||||
0x00001160 00e8dafe ffffbf00 000000e8 c0feffff ................
|
0x00001160 00e8dafe ffffbf00 000000e8 c0feffff ................
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
*** Conteúdo (hexa) da seção .rodata
|
** Despejo do conteúdo (em hexa) da seção .rodata
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ readelf -x .rodata salvec
|
:~$ readelf -x .rodata salvec
|
||||||
|
@ -751,14 +786,123 @@ Despejo máximo da secção ".rodata":
|
||||||
0x00002020 0a00 ..
|
0x00002020 0a00 ..
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
|
* Desmontagem comparativa
|
||||||
|
|
||||||
|
Com a opção =-d=, o utilitário =objdump= pode desmontar arquivos binários objeto ou
|
||||||
|
executáveis, ou seja, ele exibe um código em Assembly que corresponde ao código
|
||||||
|
de máquina encontrado no binário. Por exemplo, com o binário do nosso programa
|
||||||
|
=salve.asm=, nós podemos executar:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ objdump -d salve
|
||||||
|
|
||||||
|
salve: formato de ficheiro elf64-x86-64
|
||||||
|
|
||||||
|
|
||||||
|
Desmontagem da secção .text:
|
||||||
|
|
||||||
|
0000000000401000 <_start>:
|
||||||
|
401000: b8 01 00 00 00 mov $0x1,%eax
|
||||||
|
401005: bf 01 00 00 00 mov $0x1,%edi
|
||||||
|
40100a: 48 be 00 20 40 00 00 movabs $0x402000,%rsi
|
||||||
|
401011: 00 00 00
|
||||||
|
401014: ba 11 00 00 00 mov $0x11,%edx
|
||||||
|
401019: 0f 05 syscall
|
||||||
|
40101b: b8 3c 00 00 00 mov $0x3c,%eax
|
||||||
|
401020: 48 31 ff xor %rdi,%rdi
|
||||||
|
401023: 0f 05 syscall
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
O resultado foi a impressão de um código em Assembly escrito com a sintaxe
|
||||||
|
AT&T. Se quisermos que a desmontagem seja feita com a sintaxe Intel, nós
|
||||||
|
precisaremos incluir a opção =-M intel=:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ objdump -d -M intel salve
|
||||||
|
|
||||||
|
salve: formato de ficheiro elf64-x86-64
|
||||||
|
|
||||||
|
|
||||||
|
Desmontagem da secção .text:
|
||||||
|
|
||||||
|
0000000000401000 <_start>:
|
||||||
|
401000: b8 01 00 00 00 mov eax,0x1
|
||||||
|
401005: bf 01 00 00 00 mov edi,0x1
|
||||||
|
40100a: 48 be 00 20 40 00 00 movabs rsi,0x402000
|
||||||
|
401011: 00 00 00
|
||||||
|
401014: ba 11 00 00 00 mov edx,0x11
|
||||||
|
401019: 0f 05 syscall
|
||||||
|
40101b: b8 3c 00 00 00 mov eax,0x3c
|
||||||
|
401020: 48 31 ff xor rdi,rdi
|
||||||
|
401023: 0f 05 syscall
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
Se colocarmos o código desmontado ao lado do código original, nós veremos que
|
||||||
|
eles são praticamente os mesmos:
|
||||||
|
|
||||||
|
| Código original | Código desmontado | Diferença |
|
||||||
|
|-----------------+----------------------+------------------------------------------------------------------------|
|
||||||
|
| =mov rax, 1= | =mov eax, 0x1= | Utilizados apenas 4 bytes de RAX (EAX) |
|
||||||
|
| =mov rdi, 1= | =mov edi, 0x1= | Utilizados apenas 4 bytes de RDI (EDI) |
|
||||||
|
| =mov rsi, msg= | =movabs rsi, 0x402000= | Rótulo =msg= substituído pelo seu endereço absoluto |
|
||||||
|
| =mox rdx, len= | =mov edx, 0x11= | Macro =len= substituída pelo seu valor (~0x11~ = ~17~) |
|
||||||
|
| =syscall= | =syscall= | |
|
||||||
|
| =mov rax, 60= | =move eax, 0x3c= | Utilizados apenas 4 bytes de RAX (EAX) |
|
||||||
|
| =mov rdi, 0= | =xor rdi, rdi= | O valor =0= foi obtido por uma operação XOR bit a bit com o operador RDI |
|
||||||
|
| =syscall= | =syscall= | |
|
||||||
|
|
||||||
|
Essas diferenças não foram "inventadas" pelo =objdump=: elas estão no binário
|
||||||
|
gerado pelo montador NASM e resultam do preprocessamento do fonte e de várias
|
||||||
|
estratégias de otimização, como:
|
||||||
|
|
||||||
|
- Antes da montagem começar, a expressão =equ=, no rótulo =len=, é avaliada e o
|
||||||
|
valor resultante é substituído em todas as ocorrências de =len= no fonte.
|
||||||
|
- Rótulos de dados, como =msg=, são substituídos pelo deslocamento de endereços
|
||||||
|
(/offset/) que representam (=0x402000=, no nosso arquivo binário).
|
||||||
|
- Por que ocupar 8 bytes (RAX) com um valor numérico que pode ser escrito com
|
||||||
|
apenas 4 bytes (EAX)?
|
||||||
|
- Por que ocupar 5 bytes (=mov rdi, 0=) se o mesmo resultado pode ser obtido com
|
||||||
|
apenas 3 bytes (=xor rdi, rdi=)?
|
||||||
|
- O mnemônico =movabs= é uma convenção de alguns montadores 64 bits (como o GAS)
|
||||||
|
para explicitar que o valor copiado para o registrador deve ser escrito com
|
||||||
|
8 bytes e não pode ser otimizado para 4 bytes.
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
*Nota:* O NASM trata automaticamente a necessidade, ou não, de utilizar os 64bits
|
||||||
|
do registrador e não precisamos utilizar =movabs=. Na desmontagem, porém, o =objdump=
|
||||||
|
tenta deixar explícito que é isso que está acontecendo no binário e, portanto,
|
||||||
|
escreve =mov rdi, <valor>= como =movabs rdi, <valor>=. Contudo, os dados binários
|
||||||
|
ainda correspondem à operação =mov= (=48 BE <valor com 8 bytes>=).
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
* Exercícios propostos
|
||||||
|
|
||||||
|
** 1. Desmonte e analise o programa em C
|
||||||
|
|
||||||
|
Desmonte o binário executável =salvc=, pesquise e analise as causas das muitas
|
||||||
|
diferenças encontradas em relação à desmontagem do programa =salve=.
|
||||||
|
|
||||||
|
** 2. Desmonte e analise o programa em código de máquina
|
||||||
|
|
||||||
|
Com o comando abaixo, desmonte o binário =ok.bin=, pesquise e analise o código
|
||||||
|
em assembly exibido:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
objdump -b binary -m i386 -M intel -D ok.bin
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
No mínimo, tente localizar e analisar o código relativo ao que o programa
|
||||||
|
efetivamente faz.
|
||||||
|
|
||||||
* Referências
|
* Referências
|
||||||
|
|
||||||
- man xxd
|
- =man xxd=
|
||||||
- man 2 write
|
- =man 2 write=
|
||||||
- man 2 exit
|
- =man 2 exit=
|
||||||
- man readelf
|
- =man bvi=
|
||||||
- man bvi
|
- =man readelf=
|
||||||
- man stat
|
- =man du=
|
||||||
- man file
|
- =man stat=
|
||||||
|
- =man file=
|
||||||
|
- =man objdump=
|
||||||
- https://nasm.us/doc/
|
- https://nasm.us/doc/
|
||||||
|
|
Loading…
Add table
Reference in a new issue