Capture de segfaults en C

J’ai un programme qui segfaults de l’arithmétique de pointeur parfois. Je sais que cela se produit, mais je ne peux pas facilement vérifier à l’avance si elle constitue ou non une erreur par segment. Soit je peux “pré-parsingr” les données d’entrée pour voir si elles provoqueront une erreur par segment (ce qui peut être impossible à déterminer), ou je peux le réajuster pour ne pas utiliser l’arithmétique de pointeur, ce qui nécessiterait une quantité de travail beaucoup plus grande, ou je peux essayer d’attraper une erreur de segmentation. Donc ma question:

1) Comment, en C, puis-je attraper une erreur de segmentation? Je sais que quelque chose dans le système d’exploitation cause une erreur de segmentation, mais que peut faire un programme C dans le cas où une erreur de segmentation disparaît plus gracieusement qu’une simple Segmentation fault ?

2) Est-ce portable?

J’imagine qu’il s’agit d’un comportement hautement instable. Si vous publiez du code pour détecter un segfault, dites-moi ce que cela fonctionne. Je suis sous Mac OS X, mais j’aimerais que mon programme fonctionne sur autant de plates-formes que possible et je veux voir quelles sont mes options.

Et ne vous inquiétez pas. En gros, tout ce que je veux, c’est imprimer un message d’erreur plus convivial, libérer de la mémoire malloc() , puis mourir. Je ne prévois pas d’ignorer tous les segfaults que j’ai et de foncer.

Vous devez définir un gestionnaire de signal. Ceci est fait sur les systèmes Unix en utilisant la fonction sigaction . Je l’ai fait avec le même code sur Fedora 64 et 32 ​​bits et sur Sun Solaris.

Eh bien, SIGSEGV est piégable et il s’agit de POSIX; il est donc portable dans ce sens.

Mais je crains que vous ne sembliez vouloir gérer le segfault plutôt que résoudre le problème qui le cause. Si je devais choisir s’il s’agissait du système d’exploitation en cause ou de mon propre code, je saurais lequel je choisirais. Je vous suggère de rechercher ce bogue, de le corriger, puis d’écrire un scénario de test pour vous assurer qu’il ne vous mord jamais plus.

Vous pouvez utiliser la fonction signal pour installer un nouveau gestionnaire de signal pour le signal:

  #include  void (*signal(int signum, void (*sighandler)(int)))(int); 

Quelque chose comme le code suivant:

 signal(SIGINT , clean_exit_on_sig); signal(SIGABRT , clean_exit_on_sig); signal(SIGILL , clean_exit_on_sig); signal(SIGFPE , clean_exit_on_sig); signal(SIGSEGV, clean_exit_on_sig); // <-- this one is for segmentation fault signal(SIGTERM , clean_exit_on_sig); void clean_exit_on_sig(int sig_num) { printf ("\n Signal %d received",sig_num); } 

Les actions sécurisées dans un gestionnaire de signaux sont très limitées. Il est dangereux d’appeler une fonction de bibliothèque dont on sait qu’elle est ré-entrante, ce qui exclura, par exemple, free() et printf() . La meilleure pratique consiste à définir une variable et à renvoyer, mais cela ne vous aide pas beaucoup. Il est également sûr d’utiliser des appels système tels que write() .

Notez que dans les deux exemples de trace trouvés ici, la fonction backtrace_symbols_fd() sera sans danger car elle utilise directement le fd brut, mais l’appel à fprintf() est incorrect et doit être remplacé par une utilisation de write() .

Je pense que vous essayez de résoudre un problème qui n’existe pas. Au moins, vous travaillez du mauvais côté. Vous ne pourrez pas détecter une erreur de segmentation, car cette erreur / exception est générée par le système d’exploitation (elle est provoquée par votre programme, le système d’exploitation la détecte ).

Je vous conseillerais de repenser votre stratégie en ce qui concerne la saisie: pourquoi est-il impossible de l’assainir? Le plus important est la vérification de la taille. C stdlib a pour cela des fonctions appropriées. Ensuite, bien sûr, vous devez vérifier la validité des entrées concernant le contenu. Oui, cela nécessitera probablement beaucoup de travail, mais c’est la seule façon d’écrire un programme robuste.

EDIT: Je ne suis pas très expert en C, je ne savais pas que même un défaut de segmentation pouvait être traité par un gestionnaire de signaux. Néanmoins, je pense que ce n’est pas la bonne façon de faire pour les raisons mentionnées ci-dessus.

Vous devrez fournir un gestionnaire SIGSEGV, celui-ci est plutôt correct.

le traitement du signal est (relativement) portable sur les machines unix (y compris mac et linux). Les grandes différences se trouvent dans le détail de l’exception, qui est passé comme argument à la routine de traitement du signal. Sorrty, mais vous aurez probablement besoin d’un tas de #ifdefs pour cela, si vous voulez imprimer des messages d’erreur plus raisonnables (tels que où et pour quelle adresse le défaut est survenu) …

ok, voici un fragment de code avec lequel commencer:

 #include  /* reached when a segv occurrs */ void SEGVFunction( SIGARGS ) { ... } ... main(...) { signal(SIGSEGV, SEGVFunction); /* tell the OS, where to go in case... */ ... ... do your work ... } 

Votre tâche consiste à:

  • Vérifiez ce qu’est SIGARGS (dépend du système d’exploitation, utilisez donc un ifdef)
  • voyez comment extraire l’adresse de faute et l’ordinateur à partir des informations sur l’exception dans sigArgs
  • imprimer un message raisonnable
  • sortie

en théorie, vous pouvez même patcher le PC dans le gestionnaire de signal (après l’instruction défaillante) et continuer. Cependant, les gestionnaires de signaux typiques quittent () ou retournent un longjmp () vers un emplacement de sauvegarde dans le répertoire principal.

Cordialement

Voici un exemple de capture de SIGSEGV et d’impression d’une trace de stack à l’aide de la fonction backtrace () de glibc:

comment générer un stacktrace quand mon application C ++ plante

Vous pouvez utiliser ceci pour récupérer votre erreur de segmentation et nettoyer, mais soyez averti: vous ne devriez pas faire trop de choses dans un gestionnaire de signaux, en particulier des choses impliquant des appels comme malloc (). Il y a beaucoup d’appels qui ne sont pas sécurisés, et vous pouvez finir par vous tirer une balle dans le pied si vous appelez Malloc, par exemple, depuis Malloc.