Estrutura do Formato ELF: Um Guia Detalhado

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\] a e\_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 (ELFCLASS32 para 32 bits, ELFCLASS64 para 64 bits).
  • e\_ident\[EI\_DATA\]: Indica a codificação de dados (ELFDATA2LSB para little-endian, ELFDATA2MSB para 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 flags SHF_ALLOC | SHF_EXECINSTR.
  • .data: Contém dados inicializados. Geralmente com flags SHF_ALLOC | SHF_WRITE.
  • .rodata: Contém dados somente leitura. Geralmente com flags SHF_ALLOC.
  • .bss: Contém dados não inicializados. Tipo SHT_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.

Tags: ELF Formato de Arquivo Ligação Dinâmica Realocação GOT

Publicado em 6-9 02:20 por Thomas