11 KiB
3 – O formato binário ELF
Objetivos
- Compreender a estrutura do formato ELF.
- Compreender a definição das seções do programa com a linguagem Assembly.
- Explorar as seções do binário com ferramentas
readelf
,objdump
ehexdump
. - Relacionar o binário ELF com seu conteúdo em Assembly.
O que é o formato ELF
O formato binário ELF, de Executable and Linking Format (Formato Executável e de Ligação) foi originalmente desenvolvido e publicado como parte da Interface Binária de Aplicações (ABI) do Unix System V Release 4 (SVR4). Desde então, tornou-se o padrão adotado pela maioria dos sistemas Unix-like para representar arquivos objeto binários, como executáveis e bibliotecas.
Principais tipos de arquivos objeto
Os arquivos objeto, criados por montadores e editores de ligações (link-editores), são representações binárias de programas destinados a serem executados diretamente por um processador. As especificações do ELF para o Linux definem três tipos principais de arquivos objeto:
- Arquivo relocável
- Contém código e dados preparados para serem ligados a outros objetos para criar um executável ou um objeto compartilhado.
- Arquivo executável
- O arquivo de um programa que pode ser executado.
- Arquivo objeto compartilhado
- Contém código e dados que podem ser ligados de duas formas: com outros arquivos relocáveis para criar um outro objeto, ou com um executável e outros objetos compartilhados para formar a imagem de um processo.
Formato do arquivo
Os arquivos objeto participam tanto da link-edição quanto da execução de programas. Portanto, seu formato deve acomodar as estruturas necessárias para ambas as atividades. Na link-edição, o foco está nas seções e na tabela de cabeçalhos de seções, enquanto que, na execução, o carregador (loader) utiliza os cabeçalhos do programa para mapear segmentos na memória.
LINK-EDIÇÃO (ARQUIVO) EXECUÇÃO (MEMÓRIA) ┌────────────────────────┐ ┌────────────────────────┐ │ CABEÇALHO ELF │ │ CABEÇALHO ELF │ ├────────────────────────┤ ├────────────────────────┤ │ TABELA DE CABEÇALHOS │ │ TABELA DE CABEÇALHOS │ │ DO PROGRAMA (OPCIONAL) │ │ DO PROGRAMA │ ├────────────────────────┤ ├────────────────────────┤ │ SEÇÃO 1 │ │ │ ├────────────────────────┤ │ SEGMENTO 1 │ │ SEÇÃO 2 │ │ │ ├────────────────────────┤ ├────────────────────────┤ │ ... │ │ │ ├────────────────────────┤ │ SEGMENTO 2 │ │ SEÇÃO N │ │ │ ├────────────────────────┤ ├────────────────────────┤ │ ... │ │ ... │ ├────────────────────────┤ ├────────────────────────┤ │ TABELA DE CABEÇALHOS │ │ TABELA DE CABEÇALHOS │ │ DE SEÇÕES │ │ DE SEÇÕES (OPCIONAL) │ └────────────────────────┘ └────────────────────────┘
Importante!
- O posicionamento real das tabelas de seções e do programa pode ser diferente de como está representado nos diagramas.
- Do mesmo modo, as seções e os seguimentos não têm uma ordem específica.
- Só o cabeçalho ELF tem uma posição fixa no arquivo.
- Um segmento na memória pode conter várias seções do arquivo.
No diagrama…
- Cabeçalho ELF
-
Escrito nos primeiros 52 ou 64 bytes do arquivo, o cabeçalho ELF contém
um resumo da sua organização e diversas informações, como o formato do
arquivo (32 ou 64 bits), a ordem de escrita dos bytes de dados (little
ou big endian), o tipo do arquivo objeto (se é relocável, executável
ou compartilhado), a arquitetura do conjunto de instruções, entre outras
definições. Os primeiros 4 bytes do cabeçalho ELF contêm a assinatura do
formato, o seu número mágico: o byte
0x7F
seguido dos bytes dos caracteresE
,L
eF
na tabela ASCII (45 4C 46
). - Seções
- Contêm a organização dos dados do programa para efeito da edição das ligações no arquivo, como as instruções do código, dados globais contantes e variáveis, símbolos, informações de relocação, etc.
- Tabela de cabeçalhos do programa
- Se existir, diz ao programa como montar uma imagem do programa quando ele for carregado na memória para execução. Logo, se o arquivo objeto for executável, ele terá que conter uma tabela de cabeçalhos do programa, o que é desnecessário em arquivos objeto relocáveis.
- Tabela de cabeçalhos de seções
- Contém a descrição das seções do arquivo. Cada seção tem uma entrada na tabela com informações como seu nome, seu tamanho, a partir de onde pode ser encontrada no arquivo (offset), etc. Arquivos utilizados durante a edição de ligações precisam ter a tabela de cabeçalhos de seções, mas ela é desnecessária no caso de arquivos objeto que só serão ligados dinamicamente em tempo de execução.
Seções especiais
Várias seções de um arquivo ELF são predefinidas para conter informações de controle utilizadas pelo sistema operacional. Na pŕática, programas executáveis são formados vários arquivos objeto e bibliotecas vinculados através do processo de ligação, que pode ser:
- Estática: Vários arquivo objeto são combinados em um só arquivo executável.
- Dinâmica: O objeto executável é ligado na memoria, em tempo de execução, com objetos compartilhados e bibliotecas disponíveis no sistema.
No fim das contas, o resultado será sempre um programa completo na memória, a diferença está em quando o objeto executável é construído e em quem resolve e processa as ligações.
No GNU/Linux:
- Ligações estáticas: Processadas pelo editor de ligações (
ld
, da suíte GNU Binutils). - Ligações dinâmicas: Processadas pelo carregador e ligador dinâmico do
sistema (
ld-linux
, daglibc
).
Seja a ligação realizada estaticamente, durante a construção do executável, ou dinamicamente, no momento da execução, o correto processamento das ligações depende da organização interna do arquivo ELF. Essa organização é descrita por suas seções especiais, entre as quais podemos destacar:
Seção | Descrição |
---|---|
.bss |
Contém dados globais não inicializados ou inicializados com zero; ocupa espaço na memória, mas não ocupa espaço no arquivo executável. |
.data |
Contém dados globais inicializados; ocupa espaço tanto no arquivo quanto na memória. |
.rodata |
Contém dados que não podem ser alterados (read only), geralmente usada para constantes e strings literais. |
.text |
Seção que contém o código executável do programa (instruções da CPU). |
.symtab |
Tabela de símbolos completa, usada pelo ligador e depuradores para localizar símbolos. |
.strtab |
Tabela de strings que armazena os nomes dos símbolos referenciados em .symtab . |
Tipos de segmentos
Na execução de um programa ELF, o sistema operacional utiliza a Tabela de Cabeçalhos do Programa para saber quais partes do arquivo devem ser carregadas na memória e como tratá-las. Cada entrada nessa tabela descreve um segmento que representa uma região de interesse para o carregador, como código do programa, seus dados, informações de ligação dinâmica e o caminho do carregador e ligador dinâmico. Esses segmentos são identificados por tipos padronizados, cada um com uma finalidade específica no processo de carregamento e execução do programa.
Aqui estão alguns tipos de segmentos:
Tipo | Descrição |
---|---|
LOAD |
Segmento que deve ser carregado na memória, contendo código ou dados. |
INTERP |
Indica o caminho do carregador dinâmico (ld-linux ) para execução. |
DYNAMIC |
Contém informações para ligação dinâmica, como símbolos e dependências. |
GNU_STACK |
Define permissões da pilha (stack), como se pode ou não ser executável. |
NOTE |
Armazena metadados usados pelo sistema operacional. |
PHDR |
Contém a própria Tabela de Cabeçalhos do Programa, usada por depuradores. |
GNU_EH_FRAME |
Informações para suporte à pilha de exceções (C, C++, etc.). |
GNU_RELRO |
Segmento de dados que será tornado somente leitura após inicialização. |
Exemplo de programa mínimo em Assembly
section .text
global _start
_start:
mov rax, 60
xor rdi, rdi
syscall
Compilação
nasm -f elf64 -o elf_min.o elf_min.asm
ld -o elf_min elf_min.o
Inspeção do ELF
file elf_min
readelf -h elf_min
readelf -S elf_min # Seções
readelf -l elf_min # Segmentos (program headers)
objdump -d elf_min
hexdump -C elf_min | less
Versão em C para comparação
int main() {
return 0;
}
Exercícios
- Compare os headers do binário gerado em C com o de Assembly.
- Use `readelf -x .text` para inspecionar o código de máquina.
- Modifique o binário com `hexedit` para alterar manualmente um byte do código.
- Gere um programa com `.data` e `.bss` e localize essas seções no ELF.