web-dev-qa-db-ja.com

Linuxでセグメンテーション違反をキャッチする方法は?

サードパーティのライブラリクリーンアップ操作でセグメンテーションエラーをキャッチする必要があります。これは私のプログラムが終了する直前に起こることがあり、その本当の理由を修正することはできません。 Windowsプログラミングでは、__ try-__catchでこれを行うことができます。同じことを行うクロスプラットフォームまたはプラットフォーム固有の方法はありますか? Linuxではgccが必要です。

66
Alex F

Linuxでは、これらも例外として使用できます。

通常、プログラムがセグメンテーション違反を実行すると、SIGSEGV信号が送信されます。このシグナル用に独自のハンドラーをセットアップし、結果を軽減できます。もちろん、あなたは本当にcanが状況から回復することを確認する必要があります。あなたの場合、代わりにコードをデバッグする必要があると思います。

トピックに戻ります。私は最近、そのような信号を例外に変換する ライブラリ短いマニュアル )に遭遇したので、次のようなコードを書くことができます:

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

しかし、チェックしませんでした。 x86-64 Gentooボックスで動作します。プラットフォーム固有のバックエンド(gccのJava実装)から借用されているため、多くのプラットフォームで動作します。そのままx86とx86-64をサポートしますが、バックエンドを取得できます。 gccソースにあるlibjavaから。

64
P Shved

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

ここにある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);
    }
}
7
revo

ポインタが有効かどうか、つまり有効なメモリアドレスを参照しているかどうかを確認するために、SIGSEGVをキャッチしたい場合があります。 (または、任意の値がポインターであるかどうかを確認します。)

1つのオプションは、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 log。printf()が定義されている here に対するFIRST_UNUSED_BIT()です。

PPSループ内でalloca()を呼び出すのは得策ではないかもしれません-メモリは関数が戻るまで解放されないかもしれません。

移植性のために、おそらくstd::signalは標準C++ライブラリのものですが、シグナルハンドラが実行できることには多くの制限があります。残念ながら、仕様に次のように記載されているため、未定義の動作を導入せずにSIGSEGV C++プログラム内からをキャッチすることはできません。

  1. 標準ライブラリ関数の非常に狭いサブセット以外のハンドラーからライブラリ関数を呼び出すことは未定義の動作です(中止、終了、一部のアトミック関数、現在のシグナルハンドラーの再インストール、memcpy、memmove、型特性、移動、転送、もう少し)。
  2. ハンドラーがthrow式を使用する場合の動作は未定義です。
  3. sIGFPE、SIGILL、SIGSEGVを処理するときにハンドラーが戻る場合の動作は未定義です

これは、厳密に標準で移植可能なC++を使用してSIGSEGV プログラム内からをキャッチすることが不可能であることを証明しています。 SIGSEGVは依然としてオペレーティングシステムによってキャッチされ、通常はwaitファミリ関数が呼び出されたときに親プロセスに報告されます。

2.4.3 Signal Actions にある節があるので、おそらくPOSIXシグナルを使用して同じ種類のトラブルに遭遇するでしょう。

プロセスの動作は、kill()、sigqueue()、またはraise()によって生成されなかったSIGBUS、SIGFPE、SIGILL、またはSIGSEGVシグナルのシグナルキャッチ関数から正常に戻った後は未定義です。

longjumpsに関する言葉。 POSIXシグナルを使用していると仮定すると、longjumpを使用してスタックの巻き戻しをシミュレートしても役に立ちません。

Longjmp()は非同期シグナルセーフ関数ですが、非同期シグナルセーフでない関数または同等の関数(割り込みからの復帰後に実行されるexit()と同等の処理など)を中断したシグナルハンドラから呼び出された場合main()への最初の呼び出し)、非非同期シグナルセーフ関数または同等の関数への後続の呼び出しの動作は未定義です。

これは、longjumpの呼び出しによって呼び出される継続が、printfmallocexitなどの通常有用なライブラリ関数を確実に呼び出したり、未定義の動作を引き起こさずにmainから戻ったりできないことを意味します。そのため、継続は制限された操作のみを実行でき、何らかの異常終了メカニズムを介してのみ終了できます。

簡単に言うと、SIGSEGV andをキャッチしてポータブルでプログラムの実行を再開することは、おそらくUBを導入しないと実行不可能です。構造化例外処理にアクセスできるWindowsプラットフォームで作業している場合でも、MSDNがハードウェア例外の処理を試みないことを示唆していることに言及する価値があります。 ハードウェア例外