O que é falha de segmentação

Gustavo Costa
4 min readAug 1, 2021

--

Se você já programou em C, provavelmente já teve seu programa encerrado seguido de uma mensagem como “Falha de segmentação (imagem do núcleo gravada)”. Mas por que isso ocorre? Como essa mensagem é exibida?

Nest post, vamos entender o que é uma falha de segmentação (segmentation fault, em Inglês) e como funciona o mecanismo de interrupção do programa no Linux.

Segmento de memória

No contexto de falha de segmentação, segmento refere-se ao espaço de endereço de memória do programa. Apenas o espaço de memória do programa é legível. Deste espaço, apenas a pilha (stack) e parte do segmento de dados (data segment) são graváveis. Outras partes do segmento de dados e do segmento de texto (text segment) não são graváveis.

O segmento de dados é onde fica as variáveis globais ou estáticas. O segmento de texto, também chamado de segmento de código, é onde as instruções do programa ficam armazenadas.

Os segmentos têm tamanho e permissões, como de escrita, leitura e execução.

Ocorrência

A falha de segmentação ocorre quando um programa tenta acessar uma memória restrita, como tentar escrever no segmento de texto ou ler um endereço de memória inexistente. A falha é reportada pelo sistema de proteção de memória da Unidade de Gerenciamento de Memória ou MMU (do Inglês Memory Management Unit). O MMU é o hardware responsável por traduzir o endereço da memória virtual para endereços físicos.

Assim que o MMU nota uma violação de acesso de memória, ele informa ao Kernel o problema. No caso do Linux, o Kernel envia um sinal para o programa, o SEGV (abreviação de segmentation violation, violação de segmentação, em Português). Ao receber o sinal, geralmente o programa é encerrado.

Exemplos em C

Falhas de segmentação são mais comuns na linguagem C, por ser uma linguagem que provê acesso de baixo nível a memória. A maioria das linguagens de programação implementam mecanismos para evitar isso: Rust implementa um sistema chamado ownership; outras, como Go e Python, garbage collection.

Em C, as falhas de segmentação ocorrem geralmente quando se lida com ponteiros e alocação dinâmica. Também pode ocorrer com funções recursivas.

Código em C que tenta modificar uma variável alocada no segmento de dados.

O código acima é encerrado com falha de segmentação. A string hello, world!, que está no segmento de dados (não gravável), não pode ser modificada. Em *s = 'H' (o mesmo que s[0] = 'H'), o programa tenta escrever no segmento; daí a violação de memória:

[gusta@fedora Desktop]$ ./segv
Falha de segmentação (imagem do núcleo gravada)

Outra forma de causar uma falha de segmentação é desreferenciar um ponteiro nulo, seja para ler o valor ou escrever algo nele.

Código em C que tenta desreferenciar um ponteiro nulo.

Buffer overflow e stack overflow também podem causar falhas de segmentação quando acessam memória restrita.

Código em C que leva a SEGV quando a pilha transborda.

O sinal SEGV (SIGSEGV)

Para encerrar o programa com falha de segmentação, o Kernel manda um sinal chamado SEGV, para o processo. Quando recebido, o programa se encerra retornando um erro. No bash, você pode checar o retorno do último comando executado com a variável $?.

[gusta@fedora Desktop]$ ./segv
Falha de segmentação (imagem do núcleo gravada)
[gusta@fedora Desktop]$ echo $?
139

A mensagem “Falha de segmentação (imagem do núcleo gravada)” é do shell, não do Kernel. Também pode existir shell que não trate o erro. No fish, a mensagem é um pouco diferente do bash:

[gusta@fedora Desktop]$ ./segv
fish: Tarefa 1, './segv' encerrada pelo sinal SIGSEGV (Erro de fronteira de endereço (Falha de segmentação))

Matando processos com SIGSEGV

Como o SEGV é apenas um sinal, podemos enviá-lo para qualquer programa. O utilitário kill, do util-linux, é capaz de enviar sinais para processos. Podemos enviar o SEGV com kill -s SEGV e o PID em seguida.

Como exemplo, execute o cat e deixe-o em background cat &. Após, execute kill -s SEGV passando o PID do cat. O processo será terminado e, quando retornarmos a ele com fg, veremos a mensagem de erro.

[gusta@fedora Desktop]$ cat & 
[1] 14515
[gusta@fedora Desktop]$ kill -s SEGV 14515

[1]+ Parado cat
[gusta@fedora Desktop]$ fg
cat
Falha de segmentação (imagem do núcleo gravada)

Burlando o SIGSEGV em C

Podemos “burlar” o SIGSEGV lidando com o sinal. O header signal.h nos oferece maneiras de lidar com sinais. Com a função signal, é possível executar uma rotina sempre que um sinal for recebido. No código abaixo, sempre que um sinal SEGV é recebido, a função handle é chamada.

Código em C que lida com o sinal SIGSEGV.

Executar o programa acima não gera nenhum erro do shell:

[gusta@fedora Desktop]$ ./burlando  
Recebi um SIGSEGV
[gusta@fedora Desktop]$ echo $?
0

Correção: nos exemplos de modificação de string literal, é incorreto dizer que o programa levará a uma falha de segmentação. É, na verdade, possível que leve. A especificação da linguagem C define a modificação de uma string literal como um undefined behavior. Os códigos compilados com o TCC, Tiny C Compiler, 0.9.27 para Linux x86_64, não levam a uma violação. O TCC põe strings literais na seção .data, em contraste com o GCC 11.2.1, que na compilação as colocou na seção .rodata (read-only data).

--

--

Gustavo Costa
Gustavo Costa

Written by Gustavo Costa

Um pouco do que mais gosto em bits.

No responses yet