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?
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?
Réponses:
GCC ne peut pas faire cela mais GDB (un débogueur ) le peut. Compilez votre programme en utilisant le -g
commutateur, 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.
bt
comme raccourci pour backtrace
.
En outre, vous pouvez valgrind
essayer: si vous installez valgrind
et 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.
--leak-check=full
n'aidera pas à déboguer les segfaults. Il n'est utile que pour déboguer les fuites de mémoire.
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' -g
indicateur.
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.
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=address
indicateur 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+ .
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.
ulimit -c unlimited
d'activer les vidages de mémoire en premier lieu.
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)