web-dev-qa-db-fra.com

Comment attraper l'erreur de segmentation sous Linux?

Je dois détecter l'erreur de segmentation dans les opérations de nettoyage de la bibliothèque tierce. Cela se produit parfois juste avant la fin de mon programme et je ne peux pas en expliquer la véritable raison. En programmation Windows, je pourrais le faire avec __try - __catch. Existe-t-il un moyen multi-plateforme ou spécifique à une plateforme de faire la même chose? J'ai besoin de cela sous Linux, gcc.

66
Alex F

Sous Linux, nous pouvons également avoir ces exceptions.

Normalement, lorsque votre programme effectue une erreur de segmentation, un signal SIGSEGV lui est envoyé. Vous pouvez configurer votre propre gestionnaire pour ce signal et atténuer les conséquences. Bien sûr, vous devez vraiment être sûr que vous pourrez vous remettre de la situation. Dans votre cas, je pense que vous devriez plutôt déboguer votre code.

Retour au sujet. J'ai récemment rencontré ne bibliothèque ( short manual ) qui transforme de tels signaux en exceptions, vous pouvez donc écrire du code comme celui-ci:

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

Je n'ai 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 de Java) de gcc), de sorte qu'il peut fonctionner sur de nombreuses plates-formes. de libjava, qui réside dans les sources de gcc.

64
P Shved

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;
}
34
JayM

La solution C++ se trouve 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);
    }
}
7
revo

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

Une option consiste à le vérifier avec isValidPtr() (fonctionnant sous 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 consiste à lire les attributs de protection de la mémoire, ce qui est un peu plus compliqué (travaillé sur Android):

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() à Android journal. FIRST_UNUSED_BIT() est défini ici .

PPS Ce n'est peut-être pas une bonne idée d'appeler alloca () en boucle - la mémoire peut ne pas être libérée avant le retour de la fonction.

4

Pour la portabilité, on devrait probablement utiliser std::signal de la bibliothèque standard C++, mais il y a beaucoup de restrictions sur ce qu'un gestionnaire de signaux peut faire. Malheureusement, il n'est pas possible d'attraper un SIGSEGV depuis un programme C++ sans introduire de comportement indéfini car la spécification indique:

  1. le comportement non défini consiste à appeler une fonction de la bibliothèque à partir du gestionnaire autre qu'un sous-ensemble très étroit des fonctions de la bibliothèque standard (abandonner, quitter, certaines fonctions atomiques, réinstaller le gestionnaire de signal actuel, memcpy, memmove, traits de type, déplacer, avancer, etc. un peu plus).
  2. c'est un comportement indéfini si le gestionnaire utilise une expression throw.
  3. il s'agit d'un comportement indéfini si le gestionnaire retourne lors de la manipulation de SIGFPE, SIGILL, SIGSEGV

Cela prouve qu'il est impossible de capturer SIGSEGV depuis un programme en utilisant du C++ strictement standard et portable. SIGSEGV est toujours pris par le système d'exploitation et est normalement signalé au processus parent lorsqu'une fonction de la famille wait est appelée.

Vous rencontrerez probablement le même type de problème avec le signal POSIX car il existe une clause qui dit dans 2.4.3 Actions du signal :

Le comportement d'un processus n'est pas défini après son retour normal d'une fonction de capture de signal pour un signal SIGBUS, SIGFPE, SIGILL ou SIGSEGV qui n'a pas été généré par kill (), sigqueue () ou raise ().

Un mot sur le longjumps. En supposant que nous utilisions des signaux POSIX, utiliser longjump pour simuler le déroulement de la pile n'aidera pas:

Bien que longjmp () soit une fonction async-signal-safe-safe, si elle est invoquée à partir d'un gestionnaire de signaux qui a interrompu une fonction non async-signal-safe ou son équivalent (tel que le traitement équivalent à exit () effectué après un retour du initial call to main ()), le comportement de tout appel ultérieur vers une fonction non-async-signal-safe ou équivalente est indéfini.

Cela signifie que la continuation invoquée par l'appel de longjump ne peut pas appeler de manière fiable une fonction de bibliothèque généralement utile telle que printf, malloc ou exit ou revenir de main sans induire de comportement indéfini. En tant que tel, la poursuite ne peut effectuer que des opérations restreintes et ne peut se terminer que par un mécanisme de terminaison anormal.

Bref, attraper un SIGSEGV et reprendre l’exécution du programme dans un ordinateur portable est probablement impossible sans introduire UB. Même si vous travaillez sur une plate-forme Windows pour laquelle vous avez accès à la gestion des exceptions structurées, il convient de mentionner que MSDN suggère de ne jamais tenter de gérer les excétpions matérielles: Hardware Exceptions