如何在Linux中捕获分段错误?

时间:2010-02-28 08:11:51

标签: c++ segmentation-fault try-catch

我需要在第三方库清理操作中捕获分段错误。这有时会在我的程序退出之前发生,我无法解决这个问题的真正原因。在Windows编程中,我可以使用__try - __catch执行此操作。是否有跨平台或平台特定的方式来做同样的事情?我需要在Linux中使用它,gcc。

5 个答案:

答案 0 :(得分:62)

在Linux上,我们也可以将这些视为例外。

通常,当您的程序执行分段错误时,会发送SIGSEGV信号。您可以为此信号设置自己的处理程序并减轻后果。当然,你应该确定可以从这种情况中恢复过来。在您的情况下,我认为,您应该调试代码。

回到主题。我最近遇到a libraryshort manual)将这些信号转换为异常,因此您可以编写如下代码:

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

但是没有检查它。在我的x86-64 Gentoo盒子上工作。它有一个特定于平台的后端(借用gcc的java实现),因此它可以在许多平台上运行。它只支持开箱即用的x86和x86-64,但你可以从libjava获得后端,它位于gcc源代码中。

答案 1 :(得分:34)

以下是如何在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;
}

答案 2 :(得分:7)

此处找到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);
    }
}

答案 3 :(得分:3)

有时我们想要捕获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()对于Android日志是printf()FIRST_UNUSED_BIT()定义为here

PPS在循环中调用 alloca()可能不是一个好主意 - 在函数返回之前,内存可能不会被释放。

答案 4 :(得分:1)

出于可移植性,可能应该使用标准C ++库中的std::signal,但是对于信号处理程序的功能有很多限制。不幸的是,如果不引入未定义的行为,就不可能从C ++程序中捕获 SIGSEGV ,因为该规范说:

  1. 从处理程序中调用任何库函数都是未定义的行为,除了标准库函数的非常狭窄的子集(异常终止,退出,某些原子函数,重新安装当前信号处理程序,memcpy,memmove,类型特征,移动,向前等等)。
  2. 如果处理程序使用throw表达式,这是未定义的行为。
  3. 如果处理程序在处理SIGFPE,SIGILL,SIGSEGV时返回,则为未定义行为

这证明不可能使用严​​格的标准和可移植C ++从程序中捕获 SIGSEGV 。 SIGSEGV仍被操作系统捕获,通常在调用 wait 家族函数时报告给父进程。

使用POSIX信号可能会遇到同样的麻烦,因为2.4.3 Signal Actions中有一个子句:

  

进程的行为通常是从不是由kill(),sigqueue()或raise()生成的SIGBUS,SIGFPE,SIGILL或SIGSEGV信号的信号捕获函数返回后,不确定其行为。 / p>

有关longjump的字词。假设我们使用的是POSIX信号,那么使用longjump模拟堆栈展开将无济于事:

  

尽管longjmp()是异步信号安全函数,但如果它是从中断非异步信号安全函数或等效函数的信号处理程序中调用的(例如,在从最初对main()的调用返回,则随后对非异步信号安全函数或等效函数的任何调用的行为均未定义。

这意味着对longjump的调用所调用的继续不能可靠地调用通常有用的库函数,例如printfmallocexit或从main返回而不会引起未定义的行为。因此,延续只能执行受限的操作,并且只能通过某种异常终止机制退出。

简而言之,如果不引入UB,捕获SIGSEGV 并在便携式计算机中恢复执行该程序可能是不可行的。即使您在可以访问结构化异常处理的Windows平台上工作,也值得一提的是MSDN建议不要尝试处理硬件异常:Hardware Exceptions