diff --git a/artigos/alto-e-baixo-nivel.org b/artigos/alto-e-baixo-nivel.org new file mode 100644 index 0000000..86dcf92 --- /dev/null +++ b/artigos/alto-e-baixo-nivel.org @@ -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 + +// 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/