SIGNAL 발생 시 분석기가 작동하지 않는 대책

13513 단어 C++tech

개시하다


C/C++처럼 포인터를 처리할 때
접근이 사전에 정해진 범위를 넘어서 초기화되지 않은 변수에 잘못 접근했습니다
SIGSEGV(Segmantation Fault)가 발생했습니다...나는 이것이 흔히 있는 잘못이라고 생각한다.
나 혼자야?
물론 SIGSEGV의 결말을 지어서는 안 된다?이런 질문도 있고요.
다른 작품 프로그램을 병합해 시작하는 경우도 있고, 외면하기 어려운 경우도 있다.
이 경우 프로그램을 통해 장치에 접근할 수 있다
예를 들어 클래스 구조기(초기화 처리 시)에 device create, 분석기(처리 종료 시)에 device release 등 프로그램을 쓰는 경우
SIGSEGV를 통한 device release 없이 비정상적으로 종료되어 프로그램에서 장치에 액세스할 수 없습니다...무슨 일인데?
핫플러그 기능이 있는 USB 장치라면 괜찮지만, 일반적인 지원되지 않는 PCI Express 장치라면 다시 시작해야 할 수도 있다.
실제로 상술한 상황에 부딪히면 내가 대응하는 방법을 기록해 두어라.
시그널은 원래 뭐지?을 참조하십시오.

SIGSEGV 발생 시 행동


우선 이런 프로그램을 만들었다고 가정해 보자.
[test.cpp]

#include<iostream>
using namespace std;

class TestClass{
public:
    TestClass(){ cout << "constructer." << endl;}
    ~TestClass(){ cout << "destructor." << endl;}
};

int* segfault_gen = nullptr;
TestClass* globalptr = new TestClass;

int main()
{
    cout << *segfault_gen << endl;

    return 0;
}
이 프로그램을 실행하면nulpttr의segfault젠에 액세스하기 위해 SIGSEGV가 발생합니다.
$ ./test.elf
constructer.
Segmentation fault (コアダンプ)
여기서 보듯이 SIGSEGV가 발생한 상황에서terminate의 강제로 끝난다
분석기를 부르지 못하면 프로그램이 끝난다.
try-catch의 예외와 다르기 때문에 예외 처리라도 처리할 수 없습니다.

SIGSEGV 발생 시 분석기 호출


SIGSEGV가 나왔다고 생각해요. 어떻게든 석구기를 부르고 싶은 장면도 당연하죠.
그때는 신호 처리기를 사용했다.
여기.,시스템 호출는 실현할 수 있으며 signal 함수에 이식성이 있는 문제가 있어 POSIX를 추천하지 않습니다.
여기에 두 가지 수법을 소개한다.

signal 설치


사용 방법은 다음과 같다.
#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t sighandler);  
시험적으로 설치합니다.
[test2_signal.cpp]

#include<iostream>
#include<signal.h>
using namespace std;

class TestClass{
public:
    TestClass(){ cout << "constructer." << endl;}
    ~TestClass(){ cout << "destructor." << endl;}
};

int* segfault_gen = nullptr;
TestClass* globalptr = new TestClass;

void sigsegv_handler(int signal_number){
    cout << "SIGNAL: " << signal_number << endl;
    delete globalptr;
    exit(EXIT_FAILURE);
}

int main()
{
    if(SIGERR == signal(SIGSEGV, sigsegv_handler)){
        cerr << "cannot adapter signal handler." << endl;
        return -1;
    }
    cout << *segfault_gen << endl;
    return 0;
}
이 코드를 실행한 후 출력은 다음과 같다.
$ ./test2_signal.elf 
constructer.
SIGNAL: 11
destructor.
위에서 말한 바와 같이 SIGNAL:11 SIGSEGV의 signal 번호가 출력되고 해상도가 시작됩니다.
참고로 시그널을 신호 처리기로 사용하는 것은 피해야 한다
SIG는 마이그레이션 문제 없이 Signal을 사용하는 경우DFL(기본 작업) 또는 SIG동작은 IGN(무시)만 보장됩니다.
기본적으로sigaction을 사용하는 것이 좋습니다.

sigaction 설치


사용 방법은 다음과 같다.
#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                struct sigaction *oldact);
시험적으로 설치합니다.
[test2_sigaction.cpp]
#include<iostream>
#include<signal.h>
#include<string.h>
using namespace std;

class TestClass{
public:
    TestClass(){ cout << "constructer." << endl;}
    ~TestClass(){ cout << "destructor." << endl;}
};

int* segfault_gen = nullptr;
TestClass* globalptr = new TestClass;

void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
    cout << "SIGNAL: " << info->si_signo << endl;
    delete globalptr;
    exit(EXIT_FAILURE);
}

int main()
{
    struct sigaction sa_sigsegv;
    memset(&sa_sigsegv, 0, sizeof(sa_sigsegv)); // if not bss section, init value is indefinite.
    sa_sigsegv.sa_sigaction = sigsegv_handler;  // signal handler
    sa_sigsegv.sa_flags = SA_SIGINFO;           // if use signal handler, set SIGINFO.

    if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
        cerr << "cannot adapt signal handler." << endl;
        return -1;
    }

    cout << *segfault_gen << endl;
    return 0;
}

이 코드를 실행한 후 출력은 다음과 같다.
$ ./test2_sigaction.elf 
constructer.
SIGNAL: 11
destructor.
시그널에 비해 설치량이 늘어나겠지만 이식성을 고려해 실시한다면
이쪽의 실복을 채택한 것이 확실하다고 볼 수 있다.

local 변수 대응


지금까지 코드에는 글로벌 변수라는 분석기가 있었다.
그러나 실제로 글로벌 변수의 사용은 거의 피할 수 있다
클라스는커녕
로컬 변수의 분석기를 호출할 때의 대응 방법을 고려해 보세요.

호출된 글로벌 변수 포인터로 호출


간단하게 실시하는 방법입니다.
[test3_global.cpp]

#include<iostream>
#include<signal.h>
#include<string.h>
using namespace std;

class TestClass{
public:
    TestClass(){ cout << "constructer." << endl;}
    ~TestClass(){ cout << "destructor." << endl;}
};

class TestClass* sig_handle = nullptr;
int* segfault_gen = nullptr;

void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
    cout << "SIGNAL: " << info->si_signo << endl;
    if(sig_handle != nullptr) delete sig_handle;
    exit(EXIT_FAILURE);
}

int main()
{
    struct sigaction sa_sigsegv;
    memset(&sa_sigsegv, 0, sizeof(sa_sigsegv)); // if not bss section, init value is indefinite.
    sa_sigsegv.sa_sigaction = sigsegv_handler;  // signal handler
    sa_sigsegv.sa_flags = SA_SIGINFO;           // if use signal handler, set SIGINFO.

    if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
        cerr << "cannot adapt signal handler." << endl;
        return -1;
    }

    TestClass* autoptr = new TestClass;
    sig_handle = autoptr;

    cout << *segfault_gen << endl;
    return 0;
}

이 코드를 실행한 후 출력은 다음과 같다.
$ ./test3_global.elf 
constructer.
SIGNAL: 11
destructor.
하지만 이 방법은 다음과 같은 걱정거리가 있다.
  • 어느새 글로벌화된sig아마 핸들이 다른 걸 가리킬 거예요.
  • 실수로 글로벌 변수가 발생했습니다.namespace 등으로 처리할 수 있지만 늘리고 싶지 않아요.
  • templateclass에 대처하기 어렵다.
  • 특히 세 번째는 귀찮아요.
    예를 들어 다음처럼templateclass를 설치하려면 번거로운 처리가 필요합니다.
    [test3_global.cpp NG]
    #include<iostream>
    #include<signal.h>
    #include<string.h>
    using namespace std;
    
    template<typename T>
    class TestClass{
    public:
        TestClass(){ cout << "constructer." << endl;}
        ~TestClass(){ cout << "destructor." << endl;}
    };
    
    class TestClass<float>* sig_handle = nullptr;
    int* segfault_gen = nullptr;
    
    void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
        cout << "SIGNAL: " << info->si_signo << endl;
        if(sig_handle != nullptr) delete sig_handle;
        exit(EXIT_FAILURE);
    }
    
    int main()
    {
        struct sigaction sa_sigsegv;
        memset(&sa_sigsegv, 0, sizeof(sa_sigsegv)); // if not bss section, init value is indefinite.
        sa_sigsegv.sa_sigaction = sigsegv_handler;  // signal handler
        sa_sigsegv.sa_flags = SA_SIGINFO;           // if use signal handler, set SIGINFO.
    
        if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
            cerr << "cannot adapt signal handler." << endl;
            return -1;
        }
    
        TestClass<int>* autoptr = new TestClass<int>;
        sig_handle = autoptr;
    
        cout << *segfault_gen << endl;
        return 0;
    }
    
    물론 형식이 일치하지 않아 컴파일 오류가 발생했습니다.
    $ g++ test3_global.cpp -o test3_global.elf
    test3_global.cpp: In function ‘int main()’:
    test3_global.cpp:35:18: error: cannot convert ‘TestClass<int>*’ to ‘TestClass<float>*’ in assignment
         sig_handle = autoptr;
    
    이번 상황에서 TestClass만 하면 처리 가능
    실제적으로 고려할 수 있는 실례만 준비하고 분석기를 각각 호출해야 한다는 생각은 상당히 불편하다.
    다음 면접에서 실시하기로 했습니다.
    [test3_global_class.cpp]
    #include<iostream>
    #include<signal.h>
    #include<string.h>
    using namespace std;
    
    template<typename T>
    class TestClass{
    public:
        TestClass(){ cout << "constructer." << endl;}
        ~TestClass(){ cout << "destructor." << endl;}
    };
    
    class TestClass<float>* sig_handle_float = nullptr;
    class TestClass<int>* sig_handle_int = nullptr;
    
    int* segfault_gen = nullptr;
    
    void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
        cout << "SIGNAL: " << info->si_signo << endl;
        if(sig_handle_float != nullptr) delete sig_handle_float;
        if(sig_handle_int != nullptr) delete sig_handle_int;
        exit(EXIT_FAILURE);
    }
    
    int main()
    {
        struct sigaction sa_sigsegv;
        memset(&sa_sigsegv, 0, sizeof(sa_sigsegv)); // if not bss section, init value is indefinite.
        sa_sigsegv.sa_sigaction = sigsegv_handler;  // signal handler
        sa_sigsegv.sa_flags = SA_SIGINFO;           // if use signal handler, set SIGINFO.
    
        if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
            cerr << "cannot adapt signal handler." << endl;
            return -1;
        }
    
        TestClass<int>* autoptr = new TestClass<int>;
        sig_handle_int = autoptr;
    
        TestClass<float>* autoptr2 = new TestClass<float>;
        sig_handle_float = autoptr2;
    
        cout << *segfault_gen << endl;
        return 0;
    }
    
    이 코드를 실행한 후 출력은 다음과 같다.
    $ ./test3_global_template.elf 
    constructer.
    constructer.
    SIGNAL: 11
    destructor.
    destructor.
    
    이동은 가능하지만 이동만 하면 되는 코드의 상태.

    국부 함수화된 신호 처리 프로그램 정의 및 호출


    일반적으로 C++에서는 함수에 로컬 함수를 정의할 수 없습니다.
    단, 로컬 구조체와 로컬 클래스를 정의하는 데 문제가 없습니다.
    구조체와class를 Wrapper로 만들어 로컬 신호 처리 프로그램을 강제로 만들 수 있다.
    주의점으로 삼다
  • 로컬 구조체 내에서 구조체 밖의 변수를 접촉할 때static의 변수를 준비한다.따라서 정적 영역의 제한에 주의해야 한다.
  • 신호 처리 프로그램도static로 설정하지 않으면 컴파일 오류가 발생합니다.함수 포인터가 접근할 때static이어야 하기 때문입니다.
  • [test3_localfunc.cpp]
    #include<iostream>
    #include<signal.h>
    #include<string.h>
    using namespace std;
    
    class TestClass{
    public:
        TestClass(){ cout << "constructer." << endl;}
        ~TestClass(){ cout << "destructor." << endl;}
    };
    
    int* segfault_gen = nullptr;
    
    int main()
    {
        static TestClass* autoptr = new TestClass;
    
        struct LocalFunc{
            static void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
                cout << "SIGNAL: " << info->si_signo << endl;
                if(autoptr != nullptr) delete autoptr;
                exit(EXIT_FAILURE);
            }
        };
    
        struct sigaction sa_sigsegv;
        memset(&sa_sigsegv, 0, sizeof(sa_sigsegv));           // if not bss section, init value is indefinite.
        sa_sigsegv.sa_sigaction = LocalFunc::sigsegv_handler; // signal handler
        sa_sigsegv.sa_flags = SA_SIGINFO;                     // if use signal handler, set SIGINFO.
    
        if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
            cerr << "cannot adapt signal handler." << endl;
            return -1;
        }
    
        cout << *segfault_gen << endl;
        return 0;
    }
    
    이 코드를 실행한 후 출력은 다음과 같다.
    $ ./test3_localfunc.elf 
    constructer.
    SIGNAL: 11
    destructor.
    
    이것만으로는 맛이 없지만template를 사용할 때 상당히 편리한 신호 처리 프로그램을 정의할 수 있다.
    [test3_localfunc_template.cpp]
    #include<iostream>
    #include<signal.h>
    #include<string.h>
    using namespace std;
    
    template<typename T>
    class TestClass{
    public:
        TestClass(){ cout << "constructer." << endl;}
        ~TestClass(){ cout << "destructor." << endl;}
    };
    
    int* segfault_gen = nullptr;
    
    template<typename T>
    void test_func(){
        static TestClass<T>* autoptr = new TestClass<T>;
    
        struct LocalFunc{
            static void sigsegv_handler(int signal_number, siginfo_t* info, void* ctx){
                cout << "SIGNAL: " << info->si_signo << endl;
                if(autoptr != nullptr) delete autoptr;
                exit(EXIT_FAILURE);
            }
        };
    
        struct sigaction sa_sigsegv;
        memset(&sa_sigsegv, 0, sizeof(sa_sigsegv));           // if not bss section, init value is indefinite.
        sa_sigsegv.sa_sigaction = LocalFunc::sigsegv_handler;  // signal handler
        sa_sigsegv.sa_flags = SA_SIGINFO;                     // if use signal handler, set SIGINFO.
    
        if( sigaction(SIGSEGV, &sa_sigsegv, NULL) < 0 ){
            cerr << "cannot adapt signal handler." << endl;
            return;
        }
    
        cout << *segfault_gen << endl;
    }
    
    int main()
    {
        test_func<float>();
        return 0;
    }
    
    이 코드를 실행한 후 출력은 다음과 같다.
    $ ./test3_localfunc_template.elf 
    constructer.
    SIGNAL: 11
    destructor.
    
    이 경우 flat에서 int로 호출되더라도 특별한 추가 대응이 필요하지 않습니다.
    또한 상술한 코드는 비최적화 전제이다
    최적화를 실행할 때 exit 처리를 먼저 할 수 있습니다.
    optimize pragma directionative 등 대응하는 곳만 미리 방어하는 것이 좋다.

    최후


    내가 실제로 마주한 문제의 처리 방법을 잊지 않기 위해 정리한 메모다.
    나 자신도 아직 공부하고 있기 때문에 더 적합한 방법이 있다고 생각한다.
    더 좋은 방법 등을 알고 계시다면 자세한 분은 댓글로 남겨주세요.

    좋은 웹페이지 즐겨찾기