Déterminer la ligne de code qui provoque une erreur de segmentation?

151

Comment déterminer où se trouve l'erreur dans le code qui provoque une erreur de segmentation ?

Mon compilateur ( gcc) peut-il afficher l'emplacement de l'erreur dans le programme?

Trilarion
la source
5
Aucun gcc / gdb ne le peut. Vous pouvez savoir l'erreur de segmentation s'est produite, mais l'erreur réelle pourrait se trouver à un endroit totalement différent.

Réponses:

218

GCC ne peut pas faire cela mais GDB (un débogueur ) le peut. Compilez votre programme en utilisant le -gcommutateur, comme ceci:

gcc program.c -g

Ensuite, utilisez gdb:

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

Voici un joli tutoriel pour vous familiariser avec GDB.

L'endroit où se produit l'erreur de segmentation n'est généralement qu'un indice quant à l'endroit où «l'erreur qui la cause» se trouve dans le code. L'emplacement donné n'est pas nécessairement celui où réside le problème.

nc3b
la source
28
Notez que l'endroit où se produit l'erreur de segmentation n'est généralement qu'un indice quant à l'endroit où «l'erreur qui la cause» se trouve dans le code. Un indice important, mais ce n'est pas nécessairement là où réside le problème.
mpez0
9
Vous pouvez également utiliser (bt full) pour obtenir plus de détails.
ant2009
1
Je trouve cela utile: gnu.org/software/gcc/bugs/segfault.html
Loves Probability
2
Utilisez btcomme raccourci pour backtrace.
rustyx
43

En outre, vous pouvez valgrindessayer: si vous installez valgrindet exécutez

valgrind --leak-check=full <program>

puis il exécutera votre programme et affichera les traces de pile pour tous les segfaults, ainsi que toutes les lectures ou écritures de mémoire invalides et les fuites de mémoire. C'est vraiment très utile.

jwkpiano1
la source
2
+1, Valgrind est tellement plus rapide / plus facile à utiliser pour repérer les erreurs de mémoire. Sur les builds non optimisés avec des symboles de débogage, il vous indique exactement où un segfault s'est produit et pourquoi.
Tim Post
1
Malheureusement, mon segfault disparaît lors de la compilation avec -g -O0 et combiné avec valgrind.
JohnMudd
2
--leak-check=fulln'aidera pas à déboguer les segfaults. Il n'est utile que pour déboguer les fuites de mémoire.
ks1322
@JohnMudd J'ai un segfault n'apparaît qu'environ 1% des fichiers d'entrée testés, si vous répétez l'entrée échouée, cela n'échouera pas. Mon problème était dû au multithreading. Jusqu'à présent, je n'ai pas compris la ligne de code à l'origine de ce problème. J'utilise retry pour couvrir ce problème pour le moment. Si vous utilisez l'option -g, l'erreur disparaît!
Kemin Zhou le
18

Vous pouvez également utiliser un vidage de mémoire, puis l'examiner avec gdb. Pour obtenir des informations utiles, vous devez également compiler avec l' -gindicateur.

Chaque fois que vous recevez le message:

 Segmentation fault (core dumped)

un fichier core est écrit dans votre répertoire actuel. Et vous pouvez l'examiner avec la commande

 gdb your_program core_file

Le fichier contient l'état de la mémoire lorsque le programme s'est écrasé. Un vidage de mémoire peut être utile lors du déploiement de votre logiciel.

Assurez-vous que votre système ne définit pas la taille du fichier de vidage de mémoire sur zéro. Vous pouvez le définir sur illimité avec:

ulimit -c unlimited

Attention cependant! que les décharges de base peuvent devenir énormes.

Lucas
la source
Je suis récemment passé à arch-linux. Mon répertoire actuel ne contient pas le fichier de vidage de mémoire. Comment puis-je le générer?
Abhinav
Vous ne le générez pas; Linux le fait. Les vidages de mémoire sont stockés à différents endroits sur différents Linuces - Google autour. Pour Arch Linux, lisez ce wiki.archlinux.org/index.php/Core_dump
Mawg dit de réintégrer Monica le
7

Il existe un certain nombre d'outils disponibles qui aident à déboguer les erreurs de segmentation et je voudrais ajouter mon outil préféré à la liste: Address Sanitizers (souvent abrégé ASAN) .

Les compilateurs modernes sont livrés avec l' -fsanitize=addressindicateur pratique , ajoutant un peu de temps de compilation et de temps d'exécution, ce qui fait plus de vérification des erreurs.

Selon la documentation, ces contrôles incluent la détection des défauts de segmentation par défaut. L'avantage ici est que vous obtenez une trace de pile similaire à la sortie de gdb, mais sans exécuter le programme dans un débogueur. Un exemple:

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

La sortie est légèrement plus compliquée que ce que gdb produirait mais il y a des avantages:

  • Il n'est pas nécessaire de reproduire le problème pour recevoir une trace de pile. Il suffit d'activer le drapeau pendant le développement.

  • Les ASAN détectent bien plus que de simples défauts de segmentation. De nombreux accès hors limites seront interceptés même si cette zone de mémoire était accessible au processus.


¹ C'est Clang 3.1+ et GCC 4.8+ .

asynchrones
la source
Cela m'est très utile. J'ai un bug très subtil qui se produit au hasard avec une fréquence d'environ 1%. Je traite un grand nombre de fichiers d'entrée avec (16 étapes principales; chacune effectuée par un binaire C ou C ++ différent). Une étape ultérieure déclenchera une erreur de segmentation uniquement de manière aléatoire en raison du multi-threading. Il est difficile de déboguer. Cette option a déclenché la sortie des informations de débogage, au moins elle m'a donné un point de départ pour la révision du code pour trouver l'emplacement du bogue.
Kemin Zhou
2

La réponse de Lucas sur les vidages de mémoire est bonne. Dans mon .cshrc j'ai:

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

pour afficher la trace arrière en saisissant «core». Et le cachet de la date, pour m'assurer que je regarde le bon fichier :(.

Ajouté : S'il y a un bogue de corruption de pile , alors la trace appliquée au vidage de mémoire est souvent des ordures. Dans ce cas, l'exécution du programme dans gdb peut donner de meilleurs résultats, selon la réponse acceptée (en supposant que l'erreur est facilement reproductible). Et méfiez-vous également des processus multiples vidant le noyau simultanément; certains OS ajoutent le PID au nom du fichier core.

Joseph Quinsey
la source
4
et n'oubliez pas ulimit -c unlimitedd'activer les vidages de mémoire en premier lieu.
James Morris
@James: C'est exact. Lucas l'a déjà mentionné. Et pour ceux d'entre nous qui sont toujours coincés dans le csh, utilisez «limit». Et je n'ai jamais pu lire les stackdumps CYGWIN (mais je n'ai pas essayé depuis 2 ou 3 ans).
Joseph Quinsey
2

Toutes les réponses ci-dessus sont correctes et recommandées; cette réponse n'est destinée qu'en dernier recours si aucune des approches susmentionnées ne peut être utilisée.

Si tout le reste échoue, vous pouvez toujours recompiler votre programme avec diverses instructions de débogage temporaires (par exemple fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);) saupoudrées dans ce que vous pensez être les parties pertinentes de votre code. Ensuite, exécutez le programme et observez la dernière impression de débogage imprimée juste avant le crash - vous savez que votre programme est arrivé jusque-là, donc le crash a dû se produire après ce point. Ajoutez ou supprimez des impressions de débogage, recompilez et réexécutez le test, jusqu'à ce que vous l'ayez réduit à une seule ligne de code. À ce stade, vous pouvez corriger le bogue et supprimer toutes les impressions de débogage temporaires.

C'est assez fastidieux, mais cela a l'avantage de fonctionner à peu près n'importe où - les seules fois où ce n'est peut-être pas si vous n'avez pas accès à stdout ou stderr pour une raison quelconque, ou si le bogue que vous essayez de corriger est une course -condition dont le comportement change lorsque le timing du programme change (puisque les impressions de débogage ralentiront le programme et changeront son timing)

Jeremy Friesner
la source