Un moyen portable pour capter les signaux et signaler le problème à l’utilisateur

Si, par miracle, une erreur de segmentation se produit dans notre programme, je souhaite intercepter le SIGSEGV et informer l’utilisateur (éventuellement un client GUI), avec un code de retour unique, qu’un grave problème s’est produit. Dans le même temps, j’aimerais afficher des informations sur la ligne de commande pour indiquer quel signal a été capturé.

Aujourd’hui, notre gestionnaire de signal se présente comme suit:

void catchSignal (int reason) { std :: cerr << "Caught a signal: " << reason << std::endl; exit (1); } 

Je peux entendre les cris d’horreur avec ce qui précède, car j’ai lu dans ce fil qu’il est mauvais d’appeler une fonction non réentrante à partir d’un gestionnaire de signaux.

Existe-t-il un moyen portable de gérer le signal et de fournir des informations aux utilisateurs?

EDIT: Ou du moins portable dans le cadre POSIX?

Ce tableau répertorie toutes les fonctions que POSIX garantit d’être asynchrones-signal-safe et qui peuvent donc être appelées à partir d’un gestionnaire de signal.

En utilisant la commande ‘write’ de cette table, la solution relativement “laide” suivante fera l’affaire, espérons-le:

 #include  #ifdef _WINDOWS_ #define _exit _Exit #else #include  #endif #define PRINT_SIGNAL(X) case X: \ write (STDERR_FILENO, #X ")\n" , sizeof(#X ")\n")-1); \ break; void catchSignal (int reason) { char s[] = "Caught signal: ("; write (STDERR_FILENO, s, sizeof(s) - 1); switch (reason) { // These are the handlers that we catch PRINT_SIGNAL(SIGUSR1); PRINT_SIGNAL(SIGHUP); PRINT_SIGNAL(SIGINT); PRINT_SIGNAL(SIGQUIT); PRINT_SIGNAL(SIGABRT); PRINT_SIGNAL(SIGILL); PRINT_SIGNAL(SIGFPE); PRINT_SIGNAL(SIGBUS); PRINT_SIGNAL(SIGSEGV); PRINT_SIGNAL(SIGTERM); } _Exit (1); // 'exit' is not async-signal-safe } 

EDIT: Construire sur les fenêtres.

Après avoir essayé de construire cette fenêtre, il apparaît que ‘STDERR_FILENO’ n’est pas défini. D’après la documentation, sa valeur semble être «2».

 #include  #define STDIO_FILENO 2 

EDIT: “exit” ne doit pas non plus être appelé depuis le gestionnaire de signaux!

Comme l’a souligné fizzer , l’appel de _Exit dans ce qui précède est une approche de type marteau pour signaux tels que HUP et TERM. Idéalement, lorsque ces signaux sont capturés, un indicateur de type “volatile sig_atomic_t” peut être utilisé pour informer le programme principal qu’il doit quitter.

Ce qui suit m’a paru utile dans mes recherches.

  1. Introduction à la programmation de signaux Unix
  2. Extension des signaux traditionnels

FWIW, 2 est également une erreur standard sous Windows, mais vous aurez besoin d’une compilation conditionnelle car leur write () s’appelle _write (). Vous voudrez aussi

 #ifdef SIGUSR1 /* or whatever */ 

etc. autour de toutes les références aux signaux dont la définition n’est pas garantie.

En outre, comme indiqué ci-dessus, vous ne souhaitez pas gérer SIGUSR1, SIGHUP, SIGINT, SIGQUIT et SIGTERM comme ceci.

Richard, toujours pas assez de karma pour commenter, donc je crains une nouvelle réponse. Ce sont des signaux asynchrones; vous ne savez pas quand ils seront livrés, vous serez donc peut-être dans un code de bibliothèque qui doit être complété pour restr cohérent. Les gestionnaires de signaux pour ces signaux doivent donc être renvoyés. Si vous appelez exit (), la bibliothèque effectuera certains travaux post-main (), notamment en appelant les fonctions enregistrées avec atexit () et en nettoyant les stream standard. Ce traitement peut échouer si, par exemple, votre signal est arrivé dans une fonction d’E / S de bibliothèque standard. Par conséquent, en C90, vous n’êtes pas autorisé à appeler exit (). Je vois maintenant que C99 assouplit l’exigence en fournissant une nouvelle fonction _Exit () dans stdlib.h. _Exit () peut être appelé en toute sécurité depuis un gestionnaire pour un signal asynchrone. _Exit () n’appelle pas les fonctions atexit () et peut omettre de nettoyer les stream standard à la discrétion de l’implémentation.

Pour bk1e (commenter quelques posts plus haut) Le fait que SIGSEGV soit synchrone est la raison pour laquelle vous ne pouvez pas utiliser de fonctions qui ne sont pas conçues pour être réentrantes. Que se passe-t-il si la fonction qui s’est bloquée avait un verrou et que la fonction appelée par le gestionnaire de signaux tente d’acquérir le même verrou?

C’est une possibilité, mais ce n’est pas «le fait que SIGSEGV soit synchrone» qui pose problème. L’appel de fonctions non réentrantes à partir du gestionnaire est bien pire avec les signaux asynchrones pour deux raisons:

  • Les gestionnaires de signaux asynchrones espèrent (généralement) retourner et reprendre l’exécution normale du programme. Un gestionnaire pour un signal synchrone va (généralement) se terminer de toute façon, donc vous n’avez pas perdu grand-chose en cas de plantage.
  • Dans un sens pervers, vous avez le contrôle absolu lorsqu’un signal synchrone est délivré – cela se produit lorsque vous exécutez votre code défectueux et à aucun autre moment. Vous n’avez aucun contrôle du tout lorsqu’un signal asynchrone est délivré. À moins que le code d’E / S de l’OP ne soit lui-même la cause du défaut – par exemple, la sortie d’un caractère incorrect *, son message d’erreur a une chance raisonnable de succès.

Ecrivez un programme de lancement pour exécuter votre programme et rapportez un code de sortie anormal à l’utilisateur.