Comment pouvons-nous déterminer où se trouve l'erreur dans notre code qui cause un erreur de segmentation ?
Après avoir écrit du code, afin de déterminer où j’ai un problème de segmentation, mon compilateur (gcc
) peut-il me montrer l’emplacement du défaut dans mon programme?
GCC ne peut pas faire cela, mais GDB (un débogueur ) peut le faire. Compilez votre programme en utilisant le -g
_ switch, comme ceci:
gcc program.c -g
Ensuite, utilisez gdb:
$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>
Here est un bon tutoriel pour vous familiariser avec GDB.
Vous pouvez également essayer Valgrind: si vous installez Valgrind et exécutez valgrind --leak-check = full, il exécutera votre programme et affichera les traces de pile de tous les segfaults, ainsi que des lectures ou écritures de mémoire non valides et des fuites de mémoire. . C'est vraiment très utile.
Vous pouvez également utiliser un core dump, puis l'examiner avec gdb. Pour obtenir des informations utiles, vous devez également compiler avec le -g
drapeau.
Chaque fois que vous recevez le message:
Segmentation fault (core dumped)
un fichier de base 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 est tombé en panne. Un vidage 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 principal à zéro. Vous pouvez le définir en illimité avec:
ulimit -c unlimited
Attention cependant! ces dépotoirs peuvent devenir énormes.
Il existe un certain nombre d'outils disponibles qui aident à déboguer les erreurs de segmentation et j'aimerais ajouter mon outil préféré à la liste: Désinfecteurs d'adresses (souvent abrégé ASAN) .
Les compilateurs modernes¹ sont livrés avec le pratique -fsanitize=address
drapeau, ajoutant du temps de compilation et une surcharge de temps d’exécution qui permet de vérifier davantage les erreurs.
Selon la documentation , ces vérifications incluent la capture des erreurs 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 existe des avantages:
Il n'est pas nécessaire de reproduire le problème pour recevoir une trace de pile. Activer simplement le drapeau pendant le développement suffit.
Les ASAN attrapent bien plus que des erreurs de segmentation. De nombreux accès hors limites seront capturés même si cette zone de mémoire était accessible au processus.
¹ C'est-à-dire Clang 3.1 + et GCC 4.8 + .
La réponse de Lucas à propos des dépotoirs est bonne. Dans mon .cshrc j'ai:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
pour afficher la trace en entrant 'core'. Et le timbre à date, pour m'assurer que je regarde le bon fichier :(.
Ajouté: S'il y a un bogue de corruption de pile , la trace de retour appliquée au dump principal est souvent fouillie. Dans ce cas, l'exécution du programme dans gdb peut donner de meilleurs résultats, conformément à la réponse acceptée (en supposant que le défaut soit facilement reproductible). Et aussi méfiez-vous des processus multiples déchargeant le noyau simultanément; Certains OS ajoutent le PID au nom du fichier core.