Où est passée mon erreur de segmentation ?
(rmpr.xyz)- En compilant et en exécutant à répétition un programme C avec
entr,hellos’est terminé anormalement, mais le messageSegmentation faultn’est pas apparu après la sortie de compilation - Ce message n’est pas affiché par le programme qui a planté, mais par le shell parent qui récupère le processus enfant et constate qu’il s’est terminé avec
SIGSEGV - Même en indiquant explicitement
bash -c, s’il ne reste effectivement qu’une seule commande à exécuter, Bash peut éviter de créer un nouveau processus et se remplacer lui-même viaexecve, ce qui fait disparaître le shell parent chargé d’afficher le message - Dans un script séparé, le Bash lancé par le shebang exécute
./hellocomme processus enfant et l’attend ; il constate donc leSIGSEGVet afficheSegmentation fault (core dumped) - En exécutant
./hellodans un sous-shell(./hello), ou en ajoutant; trueaprès pour laisser une tâche suivante, on empêche l’optimisationexecet on peut voir le message d’erreur
Le chemin d’exécution où le message disparaît
-
Avec la commande
entrsuivante, la compilation et l’exécution sont relancées chaque fois quehello.cchangels hello.c | entr -s "gcc -o hello hello.c && ./hello" -
hellos’est terminé sur une erreur de segmentation, mais aucune sortie n’est restée visible, ni messageSegmentation fault, ni état de sortie non nul perceptible -
Même en enveloppant la commande avec
bash -c, le résultat était identiquels hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'" -
En revanche, si l’on déplace la même commande dans
run.shet qu’on l’exécute, l’erreur s’affiche#!/bin/bash gcc -o hello hello.c && ./hellols hello.c | entr -s ./run.sh ./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello -
Qui affiche
Segmentation fault?- Le programme qui a planté est déjà terminé ; c’est le shell parent qui récupère le processus enfant et, après avoir constaté qu’il est mort à cause de
SIGSEGV, affiche le message - Par conséquent, s’il ne reste aucun shell parent pour afficher le message, rien n’apparaît
- Dans
bash -c "some_command", si le travail à exécuter se résume effectivement à cette commande, Bash peut ne pas forker de nouveau processus et se remplacer lui-même viaexecve - Cette optimisation est invisible de l’extérieur dans la plupart des exécutions
- Avec la méthode du script,
entrexécute./run.shcomme processus enfant, et le shebang démarre un nouveau Bash - Ce nouveau Bash exécute
gcc, puis fork./helloet l’attend ; il peut donc constater que l’enfant s’est terminé avecSIGSEGVet laisser un message d’erreur
- Le programme qui a planté est déjà terminé ; c’est le shell parent qui récupère le processus enfant et, après avoir constaté qu’il est mort à cause de
Deux façons de conserver un shell parent
- Si l’on exécute la commande qui plante dans un sous-shell, le message réapparaît
ls hello.c | entr -s "gcc -o hello hello.c && (./hello)" hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )- Les parenthèses forcent
./helloà s’exécuter dans un sous-shell forké - Bash conservant le rôle de parent, il peut récupérer la fin du sous-shell par
SIGSEGVet afficher le message
- Les parenthèses forcent
- Laisser une tâche à exécuter après la commande qui plante permet aussi d’empêcher l’optimisation
execls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'" bash: line 1: 109516 Segmentation fault (core dumped) ./hello- À cause de
; true, Bash a encore quelque chose à faire après la fin de./helloet ne se remplace donc pas par ce programme - On peut ainsi voir le message d’erreur de segmentation sans script wrapper séparé
- À cause de
1 commentaires
Avis sur Lobste.rs
Les distributions Linux récentes désactivent les core dumps par défaut, ce qui est souvent pénible
On peut les réactiver avec
ulimit -c unlimited, mais le fichier core ne semble même pas être créé dans le même répertoire que l’exécutable, et systemd paraît le déplacer quelque partCe serait bien, comme sur OpenBSD, d’activer les core dumps par défaut et de les placer dans le même répertoire que l’exécutable, mais aujourd’hui, quand un crash se produit, il faut en général relancer l’exécutable dans gdb
coredumpctlOn n’irait de toute façon pas chercher un core dump dans un endroit comme
/usr/bin, et ce type d’emplacement se trouve souvent sur un système de fichiers de petite taille ou en lecture seuleComme emplacement par défaut, le répertoire de travail ne serait-il pas bien meilleur ?
On a même l’impression que, même lorsque l’utilisateur active les fichiers core, on part du principe qu’il ne sait pas ce qu’il fait, au point de les cacher dans un endroit difficile à trouver, même pour root
Si l’on écrit un programme en C, en D, etc., et qu’on veut connaître la cause de sa fin, il suffit de vérifier la valeur de retour de l’appel système
waitpour savoir si le programme s’est terminé à cause d’un signalLe fait que le système d’exploitation redirige le core dump ailleurs présente aussi l’avantage de permettre un débogage immédiat en cas de besoin, ou l’envoi d’un rapport d’erreur à la distribution
Lorsqu’il crée un core dump, le noyau Linux lit un fichier spécial nommé
/proc/sys/kernel/core_patternpour déterminer l’emplacement de sauvegardeAu lieu d’écrire dans un fichier, il peut aussi le transmettre par pipe à un programme, ce qui permet d’afficher une boîte de dialogue GUI, de lancer immédiatement un débogueur, ou d’envoyer un rapport par e-mail au développeur
systemd définit le
core_patterndu noyau sur une valeur qu’il gère lui-même, mais on peut utiliser la même fonctionnalité du noyau sans systemdOn raconte aussi que, pendant le développement de Windows XP, l’envoi de core dumps à Microsoft a permis de corriger de nombreux bugs dont l’existence même était inconnue ; et sur Windows, si l’on installe les outils de développement, le débogueur à la demande est assez pratique lorsqu’on développe ses propres programmes
Cette fonctionnalité a été introduite dans Linux en 2003, et davantage de détails sont disponibles dans
man 5 coreLes core dumps sont excellents pour le débogage, mais lorsqu’ils sont placés à côté de fichiers apparemment sans rapport, ils perturbent les utilisateurs ordinaires, et ils peuvent être catastrophiques comme mécanisme de rapport de diagnostic
J’ai été stupéfait la première fois que j’ai vu la quantité de données contenues dans un rapport de crash Windows soumis
Si l’on part du principe qu’un système de rapport de crash protège la vie privée, il faut apprendre à déduire la cause à partir des seules informations de pile et de registres, afin de pouvoir diagnostiquer les crashes remontés
Le fait que Bash
execparfois directement des commandes à l’intérieur d’un script m’était totalement inconnu, et c’est une fonctionnalité surprenanteLa surcharge du processus Bash lui-même n’est pas énorme, et j’ai de toute façon l’habitude d’utiliser
execlà où il le faut, donc cela donne l’impression d’une fonctionnalité quelque peu mal conçueCela dit, en additionnant le nombre total d’exécutions, on peut imaginer que la différence devienne plus importante que prévu