Как отловить ошибку сегментации в Linux? - PullRequest
64 голосов
/ 28 февраля 2010

Мне нужно отследить ошибку сегментации в операциях очистки сторонней библиотеки. Иногда это происходит перед выходом из моей программы, и я не могу установить истинную причину этого. В программировании Windows я мог сделать это с помощью __try - __catch. Есть ли кроссплатформенный или платформо-зависимый способ сделать то же самое? Мне нужно это в Linux, GCC.

Ответы [ 5 ]

62 голосов
/ 28 февраля 2010

В Linux мы можем использовать их как исключения.

Обычно, когда ваша программа выполняет ошибку сегментации, ей отправляется сигнал SIGSEGV. Вы можете настроить свой собственный обработчик для этого сигнала и смягчить последствия. Конечно, вы действительно должны быть уверены, что вы сможете оправиться от ситуации. В вашем случае, я думаю, вы должны отладить свой код.

Вернуться к теме. Недавно я обнаружил библиотеку ( краткое руководство ), которая преобразует такие сигналы в исключения, поэтому вы можете написать код, подобный этому:

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

Не проверял, однако. Работает на моем x86-64 Gentoo box. Он имеет специфичный для платформы бэкэнд (заимствованный из Java-реализации gcc), поэтому он может работать на многих платформах. Он просто поддерживает x86 и x86-64 из коробки, но вы можете получить backends из libjava, которая находится в исходных кодах gcc.

34 голосов
/ 13 марта 2010

Вот пример того, как это сделать на 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;
}
7 голосов
/ 23 декабря 2012

C ++ решение найдено здесь (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);
    }
}
4 голосов
/ 06 июня 2014

Иногда мы хотим поймать SIGSEGV, чтобы выяснить, является ли указатель действительным, то есть ссылается ли он на действительный адрес памяти.(Или даже проверьте, может ли какое-то произвольное значение быть указателем.)

Один из вариантов - проверить его с помощью isValidPtr() (работает на 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);
}

Другой вариант - прочитатьатрибуты защиты памяти, которые немного сложнее (работает на 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() - это printf() для журнала Android.FIRST_UNUSED_BIT() определено здесь .

PPS Возможно, не стоит вызывать alloca () в цикле - память может быть не освобождена дофункция возвращает.

2 голосов
/ 22 ноября 2018

Для переносимости, вероятно, следует использовать std::signal из стандартной библиотеки C ++, но есть много ограничений на то, что может делать обработчик сигнала. К сожалению, невозможно поймать SIGSEGV из программы на C ++ без введения неопределенного поведения, потому что спецификация гласит:

  1. это неопределенное поведение для вызова любой библиотечной функции из обработчика, кроме очень узкого подмножества стандартных библиотечных функций (прерывание, выход, некоторые атомарные функции, переустановка текущего обработчика сигнала, memcpy, memmove, type traits, move, вперед и еще немного).
  2. это неопределенное поведение, если обработчик использует выражение броска.
  3. это неопределенное поведение, если обработчик возвращается при обработке SIGFPE, SIGILL, SIGSEGV

Это доказывает, что невозможно поймать SIGSEGV из программы с использованием строго стандартного и переносимого C ++. SIGSEGV по-прежнему перехватывается операционной системой и обычно сообщается родительскому процессу при вызове функции семейства wait .

Вероятно, вы столкнетесь с такими же проблемами при использовании сигнала POSIX, потому что в 2.4.3 Действия с сигналом есть выражение :

Поведение процесса не определено после того, как он обычно возвращается из функции перехвата сигнала для сигнала SIGBUS, SIGFPE, SIGILL или SIGSEGV, который не был сгенерирован kill (), sigqueue () или Повышать ().

Слово о longjump с. Предполагая, что мы используем сигналы POSIX, использование longjump для имитации раскрутки стека не поможет:

Хотя longjmp () является функцией, безопасной для асинхронного сигнала, если она вызывается из обработчика сигнала, который прервал функцию, не безопасную для асинхронного сигнала или эквивалентную ей (например, обработку, эквивалентную exit (), выполненную после При возврате из начального вызова main ()) поведение любого последующего вызова не асинхронно безопасной для сигнала функции или ее эквивалента не определено.

Это означает, что продолжение, вызванное вызовом longjump, не может надежно вызвать обычно полезную библиотечную функцию, такую ​​как printf, malloc или exit, или вернуться из main, не вызывая неопределенного поведения. Таким образом, продолжение может выполнять только ограниченные операции и может выходить только через какой-то ненормальный механизм завершения.

Короче говоря, перехват SIGSEGV и возобновления выполнения программы в портативном устройстве, вероятно, невозможен без введения UB. Даже если вы работаете на платформе Windows, для которой у вас есть доступ к структурированной обработке исключений, стоит упомянуть, что MSDN рекомендует никогда не пытаться обрабатывать аппаратные исключения: Аппаратные исключения

...