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?
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?
Respostas:
O GCC não pode fazer isso, mas o GDB (um depurador ) com certeza pode. Compile seu programa usando o -g
switch, 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.
bt
como uma abreviação de backtrace
.
Além disso, você pode valgrind
tentar: se você instalar valgrind
e 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.
--leak-check=full
não ajudará a depurar segfaults. É útil apenas para depurar vazamentos de memória.
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 -g
sinalizador.
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.
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+ .
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.
ulimit -c unlimited
de ativar os core dumps em primeiro lugar.
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)