- Antes de o programa ser executado, uma análise técnica explora o processo pelo qual o kernel cria e inicializa um processo por meio da chamada de sistema
execve
- Essa chamada passa o caminho do executável, os argumentos e as variáveis de ambiente, e com base nisso o kernel carrega o executável no formato ELF
- O arquivo ELF inclui código, dados, símbolos e informações de ligação dinâmica, e o kernel o interpreta para realizar o mapeamento de memória e a inicialização da pilha
- Em seguida, o kernel transfere o controle para o ponto de entrada
_start, e só depois da inicialização do runtime específico da linguagem a função main definida pelo usuário é chamada
- Esse processo mostra a estrutura de colaboração entre sistema operacional, compilador e runtime e é importante para entender como a execução de programas acontece em nível de sistema
O ponto de partida da execução de programas: chamada execve
- No Linux, a execução de programas começa com a chamada de sistema
execve
- No formato
execve(const char *filename, char *const argv[], char *const envp[]), ela recebe o nome do executável, a lista de argumentos e a lista de variáveis de ambiente
- Com isso, o kernel determina qual programa será executado e em que ambiente
- Em linguagens de alto nível, essa chamada costuma estar encapsulada pela API de execução de processos da biblioteca padrão
- Exemplo:
std::process::Command do Rust chama execve internamente
- Também realiza o processo de converter o nome do comando em um caminho completo, de forma semelhante à busca no PATH do shell
- No caso de scripts com shebang (
#!), o kernel executa o programa usando o interpretador especificado
- Exemplo:
#!/usr/bin/python3 → executado com o interpretador Python
ELF: a estrutura do executável
- No Linux, os executáveis seguem o formato ELF (Executable and Linkable Format)
- ELF é um formato padrão de arquivo executável que inclui código, dados, símbolos e informações de relocação
- Outros sistemas operacionais usam formatos diferentes, como Mach-O (macOS) e PE (Windows)
- O cabeçalho ELF contém informações sobre a estrutura do arquivo e o layout em memória
- Exemplos de campos:
ELF Magic, Class, Entry point address, Program headers, Section headers
Entry point address é o endereço da primeira instrução a ser executada pelo programa
- No exemplo do cabeçalho ELF, trata-se de um executável ELF32 para a arquitetura RISC-V, com o endereço
0x10358 definido como ponto de entrada
Componentes internos do ELF
- Um arquivo ELF é composto por várias seções (section)
.text: código executável
.data: variáveis globais inicializadas
.bss: variáveis globais não inicializadas
.plt: tabela para chamadas de bibliotecas compartilhadas
.symtab, .strtab: tabelas de símbolos e strings
- A PLT (Procedure Linkage Table) dá suporte a chamadas de funções de bibliotecas compartilhadas
- Exemplo:
printf, malloc da libc
- A seção
PT_INTERP do ELF especifica o vinculador dinâmico (interpretador)
- O kernel lê o ELF, posiciona em memória as seções carregáveis e, quando necessário, aplica recursos de segurança como ASLR e bit NX
Tabela de símbolos e ligação em runtime
- A tabela de símbolos (
symtab) do ELF contém informações de endereço de funções e variáveis
- Exemplo: há entradas como
_start, main, __libc_start_main
- Até um programa simples de “Hello, World!” pode conter mais de 2.300 símbolos
- Isso vem, em grande parte, da biblioteca padrão e do código de inicialização do runtime
- Porque implementações de
libc como musl ou glibc estão vinculadas
- Depois de carregar cada seção do ELF, o kernel transfere o controle para o interpretador (vinculador dinâmico)
- O interpretador cuida de relocação, randomização de endereços (ASLR), definição de permissões de execução (bit NX) etc.
Processo de inicialização da pilha
- Antes de executar o programa, o kernel precisa montar diretamente a pilha (stack)
- A pilha é usada para variáveis locais, frames de chamada de função, passagem de argumentos etc.
- Os
argv e envp passados na chamada execve são armazenados na pilha
- O programa acessa por eles os argumentos de linha de comando e as variáveis de ambiente
- O kernel também inclui na pilha o vetor auxiliar ELF (
auxv)
- Ele contém cerca de 30 itens, como tamanho de página, metadados ELF e informações do sistema
- Exemplo:
AT_PAGESZ define o tamanho da página de memória (por exemplo, 4KiB)
- No exemplo do emulador RISC-V, o ponteiro da pilha (
sp) começa em um endereço alto, e os argumentos, variáveis de ambiente e vetor auxiliar são empilhados em ordem inversa
Ponto de entrada e função _start
- O ponto de entrada do ELF é definido como o endereço da função
_start
_start é o primeiro código em espaço de usuário para o qual o kernel transfere o controle
- Na maioria das linguagens,
_start realiza a inicialização do runtime antes de chamar main
- Exemplo:
std::rt::lang_start no Rust, __libc_start_main em C
- No exemplo em Rust, é possível definir
_start diretamente sem runtime usando os atributos #![no_std] e #![no_main]
- Dentro de
_start, lê-se argc, argv e envp da pilha e chama-se o ponteiro para main
- O runtime de cada linguagem executa tarefas de inicialização específicas da linguagem, como construtores globais, armazenamento local de thread e tratamento de exceções
Fluxo completo até a chamada de main()
- O processo completo pode ser resumido assim
- Chamada
execve → o kernel carrega o arquivo ELF
- Interpretação do ELF → mapeamento das seções de código/dados, definição do interpretador
- Montagem da pilha → armazenamento de argumentos, variáveis de ambiente e vetor auxiliar
- Execução do ponto de entrada
_start
- Inicialização do runtime e chamada de
main()
- Essa sequência mostra a estrutura de cooperação entre o kernel do sistema operacional, o formato ELF e o runtime da linguagem
- O kernel Linux real inclui também lógicas internas adicionais, como espaço de endereçamento, tabela de processos e gerenciamento de grupos, mas este texto explica o fluxo essencial da etapa anterior
Conclusão e correção
- O processo de execução antes de
main() é uma combinação de inicialização em nível de kernel e configuração de runtime
- Mesmo um programa simples de “Hello, World!” é executado somente depois de passar por uma estrutura ELF complexa e pela inicialização do runtime
- Na versão inicial do texto, parte da lógica de carregamento de seções havia sido atribuída ao kernel, mas foi corrigido que isso na verdade é papel do interpretador ELF
- Esta análise serve como material básico útil para compreender programação de sistemas, compiladores e arquitetura de sistemas operacionais
Ainda não há comentários.