Programing

Linux에서 분할 오류를 포착하는 방법은 무엇입니까?

lottogame 2020. 11. 15. 10:49
반응형

Linux에서 분할 오류를 포착하는 방법은 무엇입니까?


타사 라이브러리 정리 작업에서 세분화 오류를 잡아야합니다. 이것은 때때로 내 프로그램이 종료되기 직전에 발생하며 실제 이유를 수정할 수 없습니다. Windows 프로그래밍에서 __try-__catch로이 작업을 수행 할 수 있습니다. 동일한 작업을 수행하는 교차 플랫폼 또는 플랫폼 별 방법이 있습니까? Linux, gcc에서 필요합니다.


리눅스에서는 이것도 예외로 할 수 있습니다.

일반적으로 프로그램이 분할 오류를 수행하면 SIGSEGV신호 가 전송 됩니다. 이 신호에 대한 자체 핸들러를 설정하고 결과를 완화 할 수 있습니다. 물론 상황에서 회복 있다는 것을 정말로 확신해야합니다 . 귀하의 경우에는 대신 코드를 디버그해야한다고 생각합니다.

주제로 돌아갑니다. 최근에 그러한 신호를 예외로 변환 하는 라이브러리 ( 짧은 매뉴얼 )를 만났 으므로 다음과 같은 코드를 작성할 수 있습니다.

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

그래도 확인하지 않았습니다. x86-64 Gentoo 상자에서 작동합니다. 플랫폼 별 백엔드 (gcc의 자바 구현에서 빌려온)가 있으므로 많은 플랫폼에서 작동 할 수 있습니다. 기본적으로 x86 및 x86-64 만 지원하지만 gcc 소스에있는 libjava에서 백엔드를 가져올 수 있습니다.


다음은 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;
}

여기에 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);
    }
}

때때로 우리 SIGSEGV는 포인터가 유효한지, 즉 유효한 메모리 주소를 참조하는지 알아보기 위해 a를 잡으려고합니다 . (또는 임의의 값이 포인터인지 확인하십시오.)

한 가지 옵션은 다음으로 확인하는 것입니다 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 () 를 호출하는 것은 좋지 않을 수 있습니다. 함수가 반환 될 때까지 메모리가 해제되지 않을 수 있습니다.


이식성 std::signal을 위해 표준 C ++ 라이브러리에서 사용해야 하지만 신호 처리기가 수행 할 수있는 작업에는 많은 제한이 있습니다. 불행히도 사양에 다음과 같이 명시되어 있기 때문에 정의되지 않은 동작을 도입하지 않고 C ++ 프로그램 내에서 SIGSEGV를 잡을 수 없습니다 .

  1. it is undefined behavior to call any library function from within the handler other than a very narrow subset of the standard library functions (abort, exit, some atomic functions, reinstall current signal handler, memcpy, memmove, type traits, move, forward, and some more).
  2. it is undefined behavior if handler use a throw expression.
  3. it is undefined behavior if the handler returns when handling SIGFPE, SIGILL, SIGSEGV

This proves that it is impossible to catch SIGSEGV from within a program using strictly standard and portable C++. SIGSEGV is still caught by the operating system and is normally reported to the parent process when a wait family function is called.

You will probably run into the same kind of trouble using POSIX signal because there is a clause that says in 2.4.3 Signal Actions:

The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(), sigqueue(), or raise().

A word about the longjumps. Assuming we are using POSIX signals, using longjump to simulate stack unwinding won't help:

Although longjmp() is an async-signal-safe function, if it is invoked from a signal handler which interrupted a non-async-signal-safe function or equivalent (such as the processing equivalent to exit() performed after a return from the initial call to main()), the behavior of any subsequent call to a non-async-signal-safe function or equivalent is undefined.

This means that the continuation invoked by the call to longjump cannot reliably call usually useful library function such as printf, malloc or exit or return from main without inducing undefined behavior. As such, the continuation can only do a restricted operations and may only exit through some abnormal termination mechanism.

To put things short, catching a SIGSEGV and resuming execution of the program in a portable is probably infeasible without introducing UB. Even if you are working on a Windows platform for which you have access to Structured exception handling, it is worth mentioning that MSDN suggest to never attempt to handle hardware excetpions: Hardware Exceptions

참고URL : https://stackoverflow.com/questions/2350489/how-to-catch-segmentation-fault-in-linux

반응형