2010-02-28 12 views
49

Ich muss Segmentierungsfehler in Bibliotheksbereinigungsoperationen von Drittanbietern abfangen. Das passiert manchmal, kurz bevor mein Programm beendet wird, und ich kann den wahren Grund dafür nicht beheben. In Windows-Programmierung könnte ich dies mit __try - __catch tun. Gibt es einen plattform- oder plattformspezifischen Weg, um das Gleiche zu tun? Ich brauche das in Linux, gcc.Wie fangen Segmentierungsfehler in Linux?

Antwort

50

Unter Linux können wir diese als Ausnahmen haben, auch.

Normalerweise, wenn Ihr Programm einen Segmentation Fault durchführt, wird es ein SIGSEGV Signal gesendet. Sie können für dieses Signal einen eigenen Handler einrichten und die Folgen mindern. Natürlich sollten Sie wirklich sicher sein, dass Sie können aus der Situation erholen. In Ihrem Fall, denke ich, sollten Sie stattdessen Ihren Code debuggen.

Zurück zum Thema. Ich traf vor kurzem a library (short manual), die solche Signale zu Ausnahmen transformiert, so können Sie Code wie folgt schreiben:

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

Hat es nicht überprüfen, though. Funktioniert auf meiner x86-64 Gentoo-Box. Es hat ein plattformspezifisches Backend (entlehnt von der Java-Implementierung von gcc), so dass es auf vielen Plattformen funktionieren kann. Es unterstützt nur x86 und x86-64 out of the box, aber Sie können Backends von libjava erhalten, die in GCC-Quellen befindet.

+5

+1 für __be sicher, dass Sie sig vor Fang erholen können segfault__ –

+7

Ihr Link zur Kurzanleitung gebrochen ist. – Ponkadoodle

+8

Werfen von einem Signal-Handler ist eine sehr gefährliche Sache zu tun. Die meisten Compiler gehen davon aus, dass nur Aufrufe Ausnahmen generieren können, und richten Abwicklungsinformationen entsprechend ein.Sprachen, die Hardware-Ausnahmen in Software-Ausnahmen wie Java und C# umwandeln, wissen, dass alles möglich ist. Dies ist bei C++ nicht der Fall. Mit GCC benötigen Sie mindestens "-fnon-call-exceptions", um sicherzustellen, dass es funktioniert - und das verursacht Leistungseinbußen. Es besteht auch die Gefahr, dass Sie von einer Funktion ohne Ausnahme-Unterstützung (wie eine C-Funktion) und Leck/Crash später werfen. – zneak

25

Hier ist ein Beispiel dafür, wie es in C zu tun, herauszufinden,

#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), sonst bekommt man einen ISO C++ Fehler, der das Ding kompiliert. –

+1

Kann ich einen Stack-Trace bekommen, wenn er signalisiert wird? – daisy

+3

Doing IO in einem Signal-Handler ist ein Rezept für eine Katastrophe. –

6

C++ Lösung hier (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

Ich weiß, dies ist nur ein Beispiel, dass Sie nicht geschrieben haben, aber IO in einem Signal-Handler zu tun ist ein Rezept für eine Katastrophe. –

+0

@TimSeguine: Dinge zu wiederholen, die bestenfalls sehr irreführend sind, ist keine gute Idee (vgl. Https://stackoverflow.com/questions/2350489/how-to-catch-segmentierung-fault-in-linux#comment81651055_2436368) – stefanct

+0

@ stefanct Die Vorsichtsmaßnahmen, die notwendig sind, um printf sicher in einem Signal-Handler zu verwenden, sind nicht trivial. Daran ist nichts irreführend. Dies ist ein Spielzeugbeispiel. Und selbst in diesem Spielzeug-Beispiel ist es möglich, das Deadlock zu stoppen, wenn Sie das SIGINT richtig einstellen. Deadlocks sind genau deshalb gefährlich, weil sie selten sind. Wenn Sie meinen, dieser Rat sei irreführend, dann bleiben Sie fern von meinem Kodex, denn ich vertraue Ihnen nicht innerhalb einer Meile. –

2

Manchmal wollen wir ein SIGSEGV fangen, wenn ein Zeiger gültig, das heißt, wenn es eine gültige Speicheradresse referenziert. (Oder auch zu prüfen, ob einiger beliebiger Wert ein Zeiger sein kann.)

Eine Möglichkeit ist es mit isValidPtr() zu überprüfen (auf Android gearbeitet):

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); 
} 

Eine weitere Option ist das Speicherschutzattribut zu lesen, die ein wenig komplizierter wird (bearbeitet 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() ist printf() auf das Android-Protokoll. FIRST_UNUSED_BIT() ist here definiert.

PPS Es ist möglicherweise keine gute Idee, alloca() in einer Schleife aufzurufen - der Speicher kann nicht freigegeben werden, bis die Funktion zurückkehrt.