2010-02-28 3 views
49

J'ai besoin d'intercepter une erreur de segmentation dans les opérations de nettoyage de bibliothèques tierces. Cela arrive parfois juste avant la fin de mon programme, et je ne peux pas corriger la vraie raison de cela. Dans la programmation Windows, je pourrais le faire avec __try - __catch. Existe-t-il une manière multi-plateforme ou spécifique à la plateforme de faire la même chose? J'ai besoin de ça sous Linux, gcc.Comment faire pour attraper la faute de segmentation sous Linux?

Répondre

50

Sur Linux, nous pouvons les avoir comme exceptions, aussi.

Normalement, lorsque votre programme exécute un défaut de segmentation, il reçoit un signal SIGSEGV. Vous pouvez configurer votre propre gestionnaire pour ce signal et atténuer les conséquences. Bien sûr, vous devriez vraiment être sûr que vous pouvez récupérer de la situation. Dans votre cas, je pense, vous devriez déboguer votre code à la place.

Retour au sujet. J'ai récemment rencontré a library (short manual) qui transforme ces signaux à des exceptions, de sorte que vous pouvez écrire du code comme ceci:

try 
{ 
    *(int*) 0 = 0; 
} 
catch (std::exception& e) 
{ 
    std::cerr << "Exception catched : " << e.what() << std::endl; 
} 

n'a pas vérifié, cependant. Fonctionne sur ma boîte Gentoo x86-64. Il a un backend spécifique à la plate-forme (emprunté à l'implémentation java de gcc), donc il peut fonctionner sur de nombreuses plateformes. Il prend en charge uniquement x86 et x86-64, mais vous pouvez obtenir des backend à partir de libjava, qui réside dans les sources gcc.

+5

+1 pour être sûr que vous pouvez récupérer avant d'attraper sig segfault__ –

+7

Votre lien vers le manuel abrégé est cassé. – Ponkadoodle

+8

Lancer à partir d'un gestionnaire de signal est une chose très dangereuse à faire. La plupart des compilateurs supposent que seuls les appels peuvent générer des exceptions et configurer les informations de déroulement en conséquence.Les langages qui transforment les exceptions matérielles en exceptions logicielles, comme Java et C#, sont conscients que tout peut être lancé; ce n'est pas le cas avec C++. Avec GCC, vous avez au moins besoin de '-fnon-call-exceptions' pour vous assurer que cela fonctionne - et cela entraîne des coûts de performance. Il y a aussi un risque que vous lanciez à partir d'une fonction sans exception support (comme une fonction C) et que vous fuyiez/plantiez plus tard. – zneak

25

Voici un exemple de la façon de le faire en C.

#include <signal.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

void segfault_sigaction(int signal, siginfo_t *si, void *arg) 
{ 
    printf("Caught segfault at address %p\n", si->si_addr); 
    exit(0); 
} 

int main(void) 
{ 
    int *foo = NULL; 
    struct sigaction sa; 

    memset(&sa, 0, sizeof(struct sigaction)); 
    sigemptyset(&sa.sa_mask); 
    sa.sa_sigaction = segfault_sigaction; 
    sa.sa_flags = SA_SIGINFO; 

    sigaction(SIGSEGV, &sa, NULL); 

    /* Cause a seg fault */ 
    *foo = 1; 

    return 0; 
} 
+9

sizeof (sigaction) ==> sizeof (struct sigaction), sinon vous obtenez une erreur ISO C++ qui compile la chose. –

+1

Puis-je obtenir une trace de pile quand elle est signalée? – daisy

+3

Faire des E/S dans un gestionnaire de signal est une recette pour un désastre. –

6

C++ solution trouvée ici (http://www.cplusplus.com/forum/unices/16430/)

#include <signal.h> 
#include <stdio.h> 
#include <unistd.h> 
void ouch(int sig) 
{ 
    printf("OUCH! - I got signal %d\n", sig); 
} 
int main() 
{ 
    struct sigaction act; 
    act.sa_handler = ouch; 
    sigemptyset(&act.sa_mask); 
    act.sa_flags = 0; 
    sigaction(SIGINT, &act, 0); 
    while(1) { 
     printf("Hello World!\n"); 
     sleep(1); 
    } 
} 
+5

Je sais que c'est juste un exemple que vous n'avez pas écrit, mais faire des E/S dans un gestionnaire de signal est une recette pour un désastre. –

+0

@TimSeguine: répéter ce qui est au mieux très trompeur n'est pas une bonne idée (cf https://stackoverflow.com/questions/2350489/how-to-catch-segmentation-fault-in-linux#comment81651055_2436368) – stefanct

+0

@ stefanct Les précautions nécessaires pour utiliser printf en toute sécurité dans un gestionnaire de signal ne sont pas triviales. Il n'y a rien de trompeur à ce sujet. Ceci est un exemple de jouet. Et même dans cet exemple de jouet, il est possible de faire une impasse si vous chronométrez correctement le SIGINT. Les deadlocks sont dangereux précisément parce qu'ils sont rares. Si vous pensez que ce conseil était trompeur, alors éloignez-vous de mon code, car je ne vous fais pas confiance à moins d'un kilomètre de celui-ci. –

2

Parfois, nous voulons attraper un SIGSEGV pour savoir si un pointeur est valide, c'est-à-dire s'il fait référence à une adresse mémoire valide. (Ou même vérifier si une valeur arbitraire peut être un pointeur.)

Une option est de vérifier avec isValidPtr() (a travaillé sur Android):

int isValidPtr(const void*p, int len) { 
    if (!p) { 
    return 0; 
    } 
    int ret = 1; 
    int nullfd = open("/dev/random", O_WRONLY); 
    if (write(nullfd, p, len) < 0) { 
    ret = 0; 
    /* Not OK */ 
    } 
    close(nullfd); 
    return ret; 
} 
int isValidOrNullPtr(const void*p, int len) { 
    return !p||isValidPtr(p, len); 
} 

Une autre option est de lire les attributs de protection de la mémoire, qui est un peu plus compliqué (a travaillé sur les applications):

re_mprot.c:

#include <errno.h> 
#include <malloc.h> 
//#define PAGE_SIZE 4096 
#include "dlog.h" 
#include "stdlib.h" 
#include "re_mprot.h" 

struct buffer { 
    int pos; 
    int size; 
    char* mem; 
}; 

char* _buf_reset(struct buffer*b) { 
    b->mem[b->pos] = 0; 
    b->pos = 0; 
    return b->mem; 
} 

struct buffer* _new_buffer(int length) { 
    struct buffer* res = malloc(sizeof(struct buffer)+length+4); 
    res->pos = 0; 
    res->size = length; 
    res->mem = (void*)(res+1); 
    return res; 
} 

int _buf_putchar(struct buffer*b, int c) { 
    b->mem[b->pos++] = c; 
    return b->pos >= b->size; 
} 

void show_mappings(void) 
{ 
    DLOG("-----------------------------------------------\n"); 
    int a; 
    FILE *f = fopen("/proc/self/maps", "r"); 
    struct buffer* b = _new_buffer(1024); 
    while ((a = fgetc(f)) >= 0) { 
    if (_buf_putchar(b,a) || a == '\n') { 
     DLOG("/proc/self/maps: %s",_buf_reset(b)); 
    } 
    } 
    if (b->pos) { 
    DLOG("/proc/self/maps: %s",_buf_reset(b)); 
    } 
    free(b); 
    fclose(f); 
    DLOG("-----------------------------------------------\n"); 
} 

unsigned int read_mprotection(void* addr) { 
    int a; 
    unsigned int res = MPROT_0; 
    FILE *f = fopen("/proc/self/maps", "r"); 
    struct buffer* b = _new_buffer(1024); 
    while ((a = fgetc(f)) >= 0) { 
    if (_buf_putchar(b,a) || a == '\n') { 
     char*end0 = (void*)0; 
     unsigned long addr0 = strtoul(b->mem, &end0, 0x10); 
     char*end1 = (void*)0; 
     unsigned long addr1 = strtoul(end0+1, &end1, 0x10); 
     if ((void*)addr0 < addr && addr < (void*)addr1) { 
      res |= (end1+1)[0] == 'r' ? MPROT_R : 0; 
      res |= (end1+1)[1] == 'w' ? MPROT_W : 0; 
      res |= (end1+1)[2] == 'x' ? MPROT_X : 0; 
      res |= (end1+1)[3] == 'p' ? MPROT_P 
       : (end1+1)[3] == 's' ? MPROT_S : 0; 
      break; 
     } 
     _buf_reset(b); 
    } 
    } 
    free(b); 
    fclose(f); 
    return res; 
} 

int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask) { 
    unsigned prot1 = read_mprotection(addr); 
    return (prot1 & prot_mask) == prot; 
} 

char* _mprot_tostring_(char*buf, unsigned int prot) { 
    buf[0] = prot & MPROT_R ? 'r' : '-'; 
    buf[1] = prot & MPROT_W ? 'w' : '-'; 
    buf[2] = prot & MPROT_X ? 'x' : '-'; 
    buf[3] = prot & MPROT_S ? 's' : prot & MPROT_P ? 'p' : '-'; 
    buf[4] = 0; 
    return buf; 
} 

re_mprot.h:

#include <alloca.h> 
#include "re_bits.h" 
#include <sys/mman.h> 

void show_mappings(void); 

enum { 
    MPROT_0 = 0, // not found at all 
    MPROT_R = PROT_READ,         // readable 
    MPROT_W = PROT_WRITE,        // writable 
    MPROT_X = PROT_EXEC,         // executable 
    MPROT_S = FIRST_UNUSED_BIT(MPROT_R|MPROT_W|MPROT_X), // shared 
    MPROT_P = MPROT_S<<1,        // private 
}; 

// returns a non-zero value if the address is mapped (because either MPROT_P or MPROT_S will be set for valid addresses) 
unsigned int read_mprotection(void* addr); 

// check memory protection against the mask 
// returns true if all bits corresponding to non-zero bits in the mask 
// are the same in prot and read_mprotection(addr) 
int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask); 

// convert the protection mask into a string. Uses alloca(), no need to free() the memory! 
#define mprot_tostring(x) (_mprot_tostring_((char*)alloca(8) , (x))) 
char* _mprot_tostring_(char*buf, unsigned int prot); 

PS DLOG() est printf() dans le journal Android. FIRST_UNUSED_BIT() est défini here.

PPS Ce n'est peut-être pas une bonne idée d'appeler alloca() dans une boucle - la mémoire peut ne pas être libérée jusqu'à ce que la fonction retourne.

Questions connexes