Determinar a linha de código que causa uma falha de segmentação?

151

Como se determina onde está o erro no código que causa uma falha de segmentação ?

Meu compilador ( gcc) pode mostrar a localização da falha no programa?

Trilarion
fonte
5
Nenhum gcc / gdb não pode. Você pode descobrir onde ocorreu o segfault, mas o erro real pode estar em um local totalmente diferente.

Respostas:

218

O GCC não pode fazer isso, mas o GDB (um depurador ) com certeza pode. Compile seu programa usando o -gswitch, desta forma:

gcc program.c -g

Então use gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

Aqui está um bom tutorial para você começar a usar o GDB.

Onde o segfault ocorre geralmente é apenas uma pista de onde "o erro que causa" está no código. O local fornecido não é necessariamente onde o problema reside.

nc3b
fonte
28
Observe que onde o segfault ocorre geralmente é apenas uma pista de onde "o erro que causa" está no código. Uma pista importante, mas não é necessariamente onde o problema reside.
mpez0
9
Você também pode usar (bt full) para obter mais detalhes.
ant2009
1
Acho isso útil: gnu.org/software/gcc/bugs/segfault.html
Loves Probability
2
Use btcomo uma abreviação de backtrace.
Rustyx 27/05/19
43

Além disso, você pode valgrindtentar: se você instalar valgrinde executar

valgrind --leak-check=full <program>

Em seguida, ele executará seu programa e exibirá rastreamentos de pilha para quaisquer segfaults, além de leituras ou gravações inválidas de memória e vazamentos de memória. É realmente bastante útil.

jwkpiano1
fonte
2
+1, o Valgrind é muito mais rápido / fácil de usar para detectar erros de memória. Em compilações não otimizadas com símbolos de depuração, ele informa exatamente onde um segfault aconteceu e por quê.
Tim Post
1
Infelizmente, meu segfault desaparece ao compilar com -g -O0 e combinado com valgrind.
precisa saber é o seguinte
2
--leak-check=fullnão ajudará a depurar segfaults. É útil apenas para depurar vazamentos de memória.
precisa saber é o seguinte
@JohnMudd Eu tenho um segfault que aparece apenas 1% dos arquivos de entrada testados; se você repetir a entrada com falha, ela não falhará. Meu problema foi causado por multithreading. Até agora, não descobri a linha de código que está causando esse problema. Estou usando a nova tentativa para encobrir esse problema por enquanto. Se usar a opção -g, a falha desaparecerá!
Kemin Zhou
18

Você também pode usar um dump principal e examiná-lo com gdb. Para obter informações úteis, você também precisa compilar com o -gsinalizador.

Sempre que você receber a mensagem:

 Segmentation fault (core dumped)

um arquivo principal é gravado em seu diretório atual. E você pode examiná-lo com o comando

 gdb your_program core_file

O arquivo contém o estado da memória quando o programa travou. Um dump principal pode ser útil durante a implantação do seu software.

Verifique se o seu sistema não define o tamanho do arquivo de despejo principal como zero. Você pode configurá-lo para ilimitado com:

ulimit -c unlimited

Cuidado embora! esse core dumps pode se tornar enorme.

Lucas
fonte
Mudei para o arch-linux recentemente. Meu diretório atual não contém o arquivo de despejo principal. Como posso gerá-lo?
Abhinav
Você não o gera; Linux faz. Os core dumps são armazenados em locais diferentes em diferentes Linuces - Google ao redor. Para o Arch Linux, leia este wiki.archlinux.org/index.php/Core_dump
Mawg diz que restabelece Monica
7

Existem várias ferramentas disponíveis que ajudam a depurar falhas de segmentação e eu gostaria de adicionar minha ferramenta favorita à lista: Desinfetantes de endereços (geralmente abreviado como ASAN) .

Os compiladores modernos vêm com o prático -fsanitize=address sinalizador , adicionando algum tempo de compilação e sobrecarga de tempo de execução, o que faz mais verificação de erros.

De acordo com a documentação, essas verificações incluem a captura de falhas de segmentação por padrão. A vantagem aqui é que você obtém um rastreamento de pilha semelhante à saída do gdb, mas sem executar o programa dentro de um depurador. Um exemplo:

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

A saída é um pouco mais complicada do que o que o gdb produziria, mas existem vantagens:

  • Não há necessidade de reproduzir o problema para receber um rastreamento de pilha. Basta ativar a flag durante o desenvolvimento.

  • Os ASANs capturam muito mais do que apenas falhas de segmentação. Muitos acessos fora dos limites serão capturados, mesmo que essa área de memória esteja acessível ao processo.


¹ Isso é Clang 3.1+ e GCC 4.8+ .

assíncronas
fonte
Isso é muito útil para mim. Eu tenho um bug muito sutil que acontece aleatoriamente com uma frequência de cerca de 1%. Eu processo um grande número de arquivos de entrada com (16 etapas principais; cada uma executada por um binário C ou C ++ diferente). Uma etapa posterior acionará a falha de segmentação apenas aleatoriamente devido ao multiencadeamento. É difícil depurar. Esta opção acionou a saída de informações de depuração, pelo menos, me deu um ponto de partida para a revisão de código para encontrar a localização do bug.
Kemin Zhou 15/04
2

A resposta de Lucas sobre lixões centrais é boa. No meu .cshrc eu tenho:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

para exibir o backtrace digitando 'core'. E o carimbo de data, para garantir que eu estou olhando para o arquivo certo :(.

Adicionado : se houver um bug de corrupção de pilha , o backtrace aplicado ao dump principal geralmente será lixo. Nesse caso, a execução do programa no gdb pode fornecer melhores resultados, conforme a resposta aceita (assumindo que a falha é facilmente reproduzível). E também tenha cuidado com vários processos despejando núcleo simultaneamente; alguns SOs adicionam o PID ao nome do arquivo principal.

Joseph Quinsey
fonte
4
e não se esqueça ulimit -c unlimitedde ativar os core dumps em primeiro lugar.
James Morris
@ James: Correto. Lucas já mencionou isso. E para aqueles de nós que ainda estão presos no csh, use 'limit'. E nunca consegui ler os stackdumps do CYGWIN (mas não tentei por 2 ou 3 anos).
Joseph Quinsey
2

Todas as respostas acima estão corretas e recomendadas; esta resposta é planejada apenas como último recurso, se nenhuma das abordagens mencionadas puder ser usada.

Se tudo mais falhar, você sempre pode recompilar seu programa com várias instruções temporárias de depuração-impressão (por exemplo, fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__); ) espalhadas pelo que você acredita serem as partes relevantes do seu código. Em seguida, execute o programa e observe qual foi a última impressão de depuração impressa pouco antes da falha - você sabe que seu programa chegou tão longe, então a falha deve ter acontecido após esse ponto. Adicione ou remova debug-prints, recompile e execute o teste novamente, até reduzi-lo a uma única linha de código. Nesse ponto, você pode corrigir o erro e remover todas as impressões de depuração temporárias.

É bastante entediante, mas tem a vantagem de trabalhar em qualquer lugar - as únicas vezes que não podem ser, se você não tiver acesso ao stdout ou stderr por algum motivo, ou se o bug que você está tentando corrigir for uma corrida -condição cujo comportamento muda quando o tempo do programa muda (uma vez que as impressões de depuração desaceleram o programa e alteram o tempo)

Jeremy Friesner
fonte