Question

Si, par miracle, une erreur de segmentation se produit dans notre programme, je souhaite intercepter le SIGSEGV et informer l'utilisateur (éventuellement un client d'interface graphique), 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);
}

J'entends les cris d'horreur avec ce qui précède, comme je l'ai lu dans cette thread 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?

Était-ce utile?

La solution

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

En utilisant la commande 'write' de cette table, la commande suivante "relativement moche" la solution, espérons-le, fera l'affaire:

#include <csignal>

#ifdef _WINDOWS_
#define _exit _Exit
#else
#include <unistd.h>
#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
}

MODIFIER: Construire sur des fenêtres.

Après avoir essayé de construire cette fenêtre, il apparaît que 'STDERR_FILENO' n'est pas défini. Dans la documentation, sa valeur semble être "2".

#include <io.h>
#define STDIO_FILENO 2

EDIT: "Exit" ne doit pas non plus être appelé depuis le gestionnaire de signaux!

Comme indiqué par fizzer , appeler _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 drapeau avec "volatile sig_atomic_t" type peut être utilisé pour notifier 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

Autres conseils

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 par le standard C.

De même, 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 sont livrés, alors vous serez peut-être dans un code de bibliothèque qui doit être complété pour rester 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 flux 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 flux standard à la discrétion de la mise en oeuvre.

To bk1e (commenter quelques posts) Le fait que SIGSEGV soit synchrone explique pourquoi 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 des signaux asynchrones pour deux raisons:

  • Les gestionnaires de signaux asynchrones sont (généralement) dans l'espoir de revenir et reprendre l’exécution normale du programme. UNE gestionnaire pour un signal synchrone est (généralement) va se terminer de toute façon, de sorte que vous n'avez pas beaucoup perdu si vous vous écrasez.
  • 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é. Sauf si le code d’E / S de l’opérateur est lui-même la cause du défaut - par ex. sortie d'un caractère incorrect * - son message d'erreur a une chance raisonnable de succès.

Écrivez un programme de lancement pour exécuter votre programme et signalez le code de sortie anormal à l'utilisateur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top