웬디의 기묘한 이야기

글 작성자: WENDYS
반응형

윈도우 개발을 하다 보면 완벽한 프로그램을 만들기를 희망하지만 경험에 의해서, 예외상황에 의해서 프로세스에 문제가 발생하기 마련입니다.

__try __exception 을 두는 것에서 한계가 있고 access violation 문제인 경우 특정 옵션을 켜지 않는 이상 __try __exception에서 잡히지 않게 되고, 고객이 사용 중에 문제가 발생한 경우 로그보다 더욱 많은 자료가 기록되어있는 dump 파일을 받을 수 있기 때문에 덤프를 사용합니다.

 

그렇다면 어떻게 이런 access violation을 포함한 모든 상황에서 process crash가 나는 예외상황을 캐치할 수 있을까요?

msdn에 보면 SetUnhandledExceptionFilter API를 제공하고있습니다.

 

 

SetUnhandledExceptionFilter

Enables an application to supersede the top-level exception handler of each thread of a process.

After calling this function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter parameter.

 

응용 프로그램이 프로세스의 각 스레드의 최상위 예외 처리기를 대체할 수 있게 합니다.
이 함수를 호출한 후 디버깅되지 않는 프로세스에서 예외가 발생하고 처리되지 않은 예외 필터에 예외가 발생하면 해당 필터는 lpTopLevelExceptionFilter 매개 변수로 지정된 예외 필터 함수를 호출합니다.

 

실제 unhandled exception이 발생했을 때 해당 callback이 호출되도록 설정한 뒤 callback에서 dump를 기록하여 처리하도록 합니다.

unahndled exception handler는 윈도우 개발 시 보험처럼 매번 사용할 수 있기 때문에 이 또한 class로 작성하여 string convert와 더불어 자신만의 라이브러리로 관리하는 게 좋습니다.

 

그렇다면 샘플을 한번 보실까요?

 

exception_handler.hpp


#include <windows.h>
#include <mutex>
#include <iostream>

#pragma warning(push)
#pragma warning(disable: 4091)  // 'typedef ': ignored on left of '' when no variable is declared
#include <dbghelp.h>
#pragma warning(pop)

#pragma comment(lib, "Dbghelp.lib")

//
// exception handler.
//

class exception_handler {

private:
    static exception_handler* volatile _instance;
    static std::mutex _mutex;

    exception_handler() {}
    exception_handler(const exception_handler& other);
    ~exception_handler() {}

public:
    static exception_handler* volatile instance() {
        if (_instance == nullptr) {

            std::lock_guard<std::mutex> lock(_mutex);

            if (_instance == nullptr) {
                _instance = new exception_handler();
            }
        }
        return _instance;
    }

public:

    DWORD initialize(
        __in LPCTSTR dump_file_name,
        __in const MINIDUMP_TYPE dump_type = MINIDUMP_TYPE::MiniDumpNormal
        );

    DWORD run();

    static LONG WINAPI exception_callback(
        __in struct _EXCEPTION_POINTERS* exceptioninfo
        );

private:
    
    std::wstring _dump_file_name;
    MINIDUMP_TYPE _dump_type;
    LPTOP_LEVEL_EXCEPTION_FILTER _prev_filter;
};
  

 

 

exception_handler.cpp


//
// static members
//

exception_handler* volatile exception_handler::_instance = nullptr;
std::mutex exception_handler::_mutex;

//
// exception_handler.
//

DWORD exception_handler::initialize(
    __in LPCTSTR dump_file_name,
    __in const MINIDUMP_TYPE dump_type
    ) {

    DWORD error = ERROR_SUCCESS;

    do {

        if (nullptr == dump_file_name) {
            error = ERROR_INVALID_PARAMETER;
            break;
        }

        _dump_file_name.assign(dump_file_name);
        _dump_type = dump_type;

    } while (false);

    return error;
}

DWORD exception_handler::run() {
    _prev_filter = ::SetUnhandledExceptionFilter(exception_callback);
    return ERROR_SUCCESS;
}

LONG exception_handler::exception_callback(
    __in struct _EXCEPTION_POINTERS* exceptioninfo
    ) {

    do {

        if (nullptr == exceptioninfo) {
            break;
        }

        SYSTEMTIME st = { 0 };
        ::GetLocalTime(&st);

        std::wstring dump_file_name;
        dump_file_name.assign(exception_handler::instance()->_dump_file_name);

        //
        // create dump file.
        //

        HANDLE dump_file_handle = ::CreateFile(
                                        dump_file_name.c_str(),
                                        GENERIC_WRITE,
                                        0,
                                        nullptr,
                                        CREATE_ALWAYS,
                                        FILE_ATTRIBUTE_NORMAL,
                                        nullptr
                                        );

        if (INVALID_HANDLE_VALUE == dump_file_handle) {
            break;
        }

        MINIDUMP_EXCEPTION_INFORMATION ex_info = { 0 };

        ex_info.ThreadId = ::GetCurrentThreadId(); // Threae ID 설정
        ex_info.ExceptionPointers = exceptioninfo; // Exception 정보 설정
        ex_info.ClientPointers = FALSE;

        //
        // write dump file.
        //

        if (FALSE == ::MiniDumpWriteDump(
            ::GetCurrentProcess(),
            ::GetCurrentProcessId(),
            dump_file_handle,
            exception_handler::instance()->_dump_type,
            &ex_info,
            nullptr, nullptr
            )) {
            
            break;
        }

    } while (false);

    return (exception_handler::instance()->_prev_filter) ? exception_handler::instance()->_prev_filter(exceptioninfo) : EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
    exception_handler::instance()->initialize(L"handler.dmp");
    exception_handler::instance()->run();

    LPBYTE test = nullptr;
    *test = 1234;
}

 

 

해당 sample에선 singleton class를 사용했습니다. main thread에서 한 번만 생성하여 최상위 thread에서 처리하게 되고 소멸이 되지 않아야 하기 때문입니다. 만약 singleton class를 사용하지 않는 경우 main class에서 소멸되지 않도록 전역 변수로 처리해도 상관없습니다.

 

 

sample download

exception_handler.zip
0.01MB

반응형