Introdução
O formato ELF (Executable and Linkable Format) é o padrão para arquivos de objeto em sistemas baseados em Unix, incluindo o Linux. Esses arquivos, criados por compiladores e linkers, existem em três formas principais:
- Arquivos Relocáveis (Relocatable Files): Contêm código e dados gerados pelo compilador. O linker os combina com outros arquivos para criar executáveis ou bibliotecas compartilhadas. Geralmente têm a extensão
.o. - Arquivos Executáveis (Executable Files): São os programas que executamos diretamente.
- Arquivos de Objeto Compartilhado (Shared Object Files): São as bibliotecas, geralmente com a extensão
.so. Podem ser processados pelo linker ou carregados em tempo de execução pelo linker dinâmico para formar a imagem de um processo.
Este guia foca na estrutura interna dos arquivos ELF.
Visões de Link e Execução
Para otimizar tanto o link quanto a execução, os arquivos ELF oferecem duas visões de seu conteúdo:
Visão de Link:
- Cabeçalho ELF (ELF Header): Descreve a organização geral do arquivo.
- Tabela de Cabeçalhos de Seção (Section Header Table): Contém informações descritivas sobre cada seção do arquivo (nome, tamanho, etc.). Arquivos destinados ao link devem possuir esta tabela.
- Seções (Sections): Contêm a maior parte das informações usadas na visão de link, como código, dados, tabelas de símbolos e informações de realocação.
- Tabela de Cabeçalhos de Programa (Program Header Table): Se presente, informa ao sistema como criar um processo a partir do arquivo. Arquivos executáveis e objetos compartilhados precisam desta tabela, mas arquivos relocáveis não.
Visão de Execução:
Nesta visão, as seções são agrupadas em segmentos, que são usados para carregar o programa na memória. Segmentos são a unidade de mapeamento de memória.
Nota: A ordem dos componentes no arquivo não é estritamente definida, exceto pelo cabeçalho ELF que sempre aparece no início.
Representação de Dados
O formato ELF suporta arquiteturas de 32 e 64 bits. Ele contém informações sobre a arquitetura para interpretação correta, permitindo a compilação cruzada. Todas as estruturas de dados em ELF seguem regras de alinhamento e tamanho natural. A portabilidade é garantida pela ausência de campos de bits e pelo uso de tipos de dados definidos como Elf32_Addr, Elf32_Half, Elf32_Off, Elf32_Sword, Elf32_Word e unsigned char.
Cabeçalho ELF (ELF Header)
O cabeçalho ELF fornece um resumo do arquivo e permite acessar todas as outras informações. A estrutura Elf32_Ehdr é definida como:
typedef struct {
unsigned char e_ident[16]; // Identificação do arquivo
Elf32_Half e_type; // Tipo do arquivo
Elf32_Half e_machine; // Arquitetura da máquina
Elf32_Word e_version; // Versão do arquivo ELF
Elf32_Addr e_entry; // Endereço de entrada
Elf32_Off e_phoff; // Deslocamento da tabela de cabeçalhos de programa
Elf32_Off e_shoff; // Deslocamento da tabela de cabeçalhos de seção
Elf32_Word e_flags; // Flags específicas do processador
Elf32_Half e_ehsize; // Tamanho do cabeçalho ELF
Elf32_Half e_phentsize; // Tamanho de cada entrada na tabela de cabeçalhos de programa
Elf32_Half e_phnum; // Número de entradas na tabela de cabeçalhos de programa
Elf32_Half e_shentsize; // Tamanho de cada entrada na tabela de cabeçalhos de seção
Elf32_Half e_shnum; // Número de entradas na tabela de cabeçalhos de seção
Elf32_Half e_shstrndx; // Índice da seção de tabela de strings de cabeçalhos de seção
} Elf32_Ehdr;
e_ident
Este array de 16 bytes (EI_NIDENT) contém informações cruciais para a interpretação do arquivo:
e\_ident\[EI\_MAG0\]ae\_ident\[EI\_MAG3\]: Os primeiros 4 bytes ("magic numbers") identificam o arquivo como ELF (0x7f, 'E', 'L', 'F').e\_ident\[EI\_CLASS\]: Especifica a classe do arquivo (ELFCLASS32para 32 bits,ELFCLASS64para 64 bits).e\_ident\[EI\_DATA\]: Indica a codificação de dados (ELFDATA2LSBpara little-endian,ELFDATA2MSBpara big-endian).e\_ident\[EI\_VERSION\]: Versão do formato ELF.e\_ident\[EI\_OSABI\]: Identificador ABI do sistema operacional.e\_ident\[EI\_ABIVERSION\]: Versão ABI.e\_ident\[EI\_PAD\]: Bytes de preenchimento.
e_type
Define o tipo do arquivo:
ET_NONE: Tipo de arquivo inválido.ET_REL: Arquivo relocável.ET_EXEC: Arquivo executável.ET_DYN: Objeto de biblioteca compartilhado.ET_CORE: Core dump file.
e_machine
Especifica a arquitetura de hardware para a qual o arquivo foi compilado (ex: EM_386 para Intel 80386).
e_version
A versão do formato ELF. Atualmente, EV_CURRENT.
e_entry
O endereço virtual onde a execução do programa começa.
e_phoff
O deslocamento em bytes do início do arquivo até a Tabela de Cabeçalhos de Programa.
e_shoff
O deslocamento em bytes do início do arquivo até a Tabela de Cabeçalhos de Seção.
e_flags
Flags específicas do processador.
e_ehsize
Tamanho do cabeçalho ELF em bytes.
e_phentsize
Tamanho de cada entrada na Tabela de Cabeçalhos de Programa.
e_phnum
Número de entradas na Tabela de Cabeçalhos de Programa.
e_shentsize
Tamanho de cada entrada na Tabela de Cabeçalhos de Seção.
e_shnum
Número de entradas na Tabela de Cabeçalhos de Seção.
e_shstrndx
Índice na Tabela de Cabeçalhos de Seção que aponta para a seção de strings usada para os nomes das seções.
Tabela de Cabeçalhos de Programa (Program Header Table)
Esta tabela descreve os segmentos de um arquivo ELF, que são as unidades de mapeamento de memória. Cada entrada, do tipo Elf32_Phdr, descreve um segmento:
typedef struct {
ELF32_Word p_type; // Tipo do segmento
ELF32_Off p_offset; // Deslocamento do início do arquivo até o segmento
ELF32_Addr p_vaddr; // Endereço virtual do segmento na memória
ELF32_Addr p_paddr; // Endereço físico (pouco usado em sistemas modernos)
ELF32_Word p_filesz; // Tamanho do segmento no arquivo
ELF32_Word p_memsz; // Tamanho do segmento na memória
ELF32_Word p_flags; // Permissões do segmento (leitura, escrita, execução)
ELF32_Word p_align; // Alinhamento do segmento
} Elf32_Phdr;
Tipos de Segmento (p_type)
PT_NULL: Segmento nulo.PT_LOAD: Segmento carregável na memória.PT_DYNAMIC: Informações de ligação dinâmica.PT_INTERP: Caminho do interpretador (ex:/lib/ld-linux.so.2).PT_NOTE: Informações adicionais.PT_SHLIB: Reservado.PT_PHDR: Tabela de cabeçalhos de programa em si.
Permissões (p_flags)
PF_R: Leitura.PF_W: Escrita.PF_X: Execução.
Tabela de Cabeçalhos de Seção (Section Header Table)
Localizada geralmente no final do arquivo, esta tabela descreve as seções do arquivo ELF. Cada entrada, do tipo Elf32_Shdr, descreve uma seção:
typedef struct {
ELF32_Word sh_name; // Índice do nome da seção na tabela de strings
ELF32_Word sh_type; // Tipo da seção
ELF32_Word sh_flags; // Flags da seção (escrita, alocação, execução)
ELF32_Addr sh_addr; // Endereço virtual da seção na memória
ELF32_Off sh_offset; // Deslocamento do início do arquivo até a seção
ELF32_Word sh_size; // Tamanho da seção em bytes
ELF32_Word sh_link; // Índice de outra seção (depende do tipo)
ELF32_Word sh_info; // Informação adicional (depende do tipo)
ELF32_Word sh_addralign; // Requisito de alinhamento de endereço
ELF32_Word sh_entsize; // Tamanho de cada entrada (para tabelas)
} Elf32_Shdr;
Tipos de Seção (sh_type)
SHT_NULL: Seção nula.SHT_PROGBITS: Informações definidas pelo programa.SHT_SYMTAB: Tabela de símbolos (para linkagem).SHT_STRTAB: Tabela de strings.SHT_RELA: Entradas de realocação com addend.SHT_HASH: Tabela hash de símbolos.SHT_DYNAMIC: Informações de ligação dinâmica.SHT_NOTE: Informações de nota.SHT_NOBITS: Seção que não ocupa espaço no arquivo (ex: .bss).SHT_REL: Entradas de realocação sem addend.SHT_DYNSYM: Tabela de símbolos dinâmica.
Flags de Seção (sh_flags)
SHF_WRITE: Seção gravável.SHF_ALLOC: Seção alocada na memória.SHF_EXECINSTR: Seção contém instruções executáveis.
Subscritos Especiais
SHN_UNDEF: Índice indefinido.SHN_ABS: Valor absoluto.SHN_COMMON: Símbolo comum (variáveis não inicializadas).
Seções Notáveis
.text: Contém o código executável do programa. Geralmente com flagsSHF_ALLOC | SHF_EXECINSTR..data: Contém dados inicializados. Geralmente com flagsSHF_ALLOC | SHF_WRITE..rodata: Contém dados somente leitura. Geralmente com flagsSHF_ALLOC..bss: Contém dados não inicializados. TipoSHT_NOBITS. Ocupa espaço na memória, mas não no arquivo..symtab: Tabela de símbolos completa para linkagem..dynsym: Tabela de símbolos para ligação dinâmica..strtab: Tabela de strings padrão (nomes de símbolos)..dynstr: Tabela de strings para ligação dinâmica..rela.dyn: Informações de realocação para dados na ligação dinâmica..rela.plt: Informações de realocação para chamadas de função na ligação dinâmica (Procedure Linkage Table)..got: Tabela de Deslocamento Global (Global Offset Table)..plt: Tabela de Ligação de Procedimentos (Procedure Linkage Table)..comment: Informações de controle de versão..eh_frame: Dados para tratamento de exceções.
Tabela de Símbolos (Symbol Table)
A estrutura Elf32_Sym define uma entrada na tabela de símbolos:
typedef struct {
Elf32_Word st_name; // Índice do nome na tabela de strings
Elf32_Addr st_value; // Valor do símbolo (endereço ou deslocamento)
Elf32_Word st_size; // Tamanho do símbolo
unsigned char st_info; // Tipo e binding do símbolo
unsigned char st_other; // Visibilidade (padrão)
Elf32_Half st_shndx; // Índice da seção onde o símbolo está definido
} Elf32_Sym;
- Tipo (
STT_...): Indica se o símbolo é uma função, objeto, seção, etc. - Binding (
STB_...): Determina a visibilidade do símbolo (STB_LOCAL,STB_GLOBAL,STB_WEAK).
Realocação (Relocation)
As seções de realocação (.rel.* ou .rela.*) contêm informações que o linker usa para ajustar endereços e referências após a combinação de arquivos. A estrutura Elf32_Rel ou Elf32_Rela descreve uma entrada de realocação:
typedef struct {
Elf32_Addr r_offset; // Endereço a ser realocado
Elf32_Word r_info; // Símbolo e tipo de realocação
Elf32_Sword r_addend; // Addend (para Elf32_Rela)
} Elf32_Rela;
O campo r_info codifica o índice do símbolo na tabela de símbolos e o tipo de realocação específico da arquitetura (ex: R_386_PC32).
Tabela de Deslocamento Global (GOT) e Tabela de Ligação de Procedimentos (PLT)
Essas tabelas são cruciais para a ligação dinâmica e para a execução de código independente de posição (PIC - Position Independent Code).
- GOT: Contém endereços de variáveis globais e outras referências que precisam ser resolvidas em tempo de execução. O primeiro elemento geralmente aponta para a estrutura
_DYNAMIC. - PLT: Usado para chamadas de funções em bibliotecas compartilhadas. A primeira vez que uma função é chamada, a PLT a redireciona para o resolvedor dinâmico, que encontra o endereço real da função e atualiza a GOT. Chamadas subsequentes vão diretamente para a função através da GOT.