2.오류덤프시스템 활용하기

Edit

2.1C 런타임 오류를 오류 덤프 시스템에서 가로채기

STL에서 out-of-the-range 오류나 pure virtual function이 run time에서 호출되는 오류는 기본적으로 오류 덤프 시스템에서 감지하지 못합니다. 오류 덤프 시스템은 Structured Exception만을 처리하는데, 상기 오류는 C runtime library에서 소화되기 때문입니다.

따라서 이들 오류가 C runtime library에서 소화되기 전에 Structured Exception으로 넘겨져야, 즉 우회시켜야 오류 덤프를 남길 수 있습니다.

아래는 오류 덤프를 남기는 방법입니다.

"Pure virtual function called" 오류를 오류 덤프 시스템에서 받도록 우회시키기.

void myPurecallHandler(void)
{
    printf("In _purecall_handler.");

    int* a = 0;
    *a = 1; // 크래시 유발. 오류 덤프 시스템으로 우회시키기.
}

int main()
{
    /* pure virtual function called 에러 핸들러를 사용자 정의 함수로 우회시킨다.
    프로그램 처음 시작시에만 넣어주면 된다.*/
    _set_purecall_handler(myPurecallHandler);
    ...
}

STL의 "out of the range 오류"를 오류 덤프 시스템에서 받도록 우회시키기.

주의!! : _CrtSetReportHook은 ATLTRACE를 사용하면 설정한 함수에 들어 올수 있으므로, retportType == _CRT_WARN은 무시토록 해야 합니다.
int YourReportHook(int reportType, char *message, int *returnValue)
{
    //_CRT_WARN or 0는 무시합니다.
    if (reprotType != _CRT_WARN)
    {
        int* a = 0;
        *a = 1; // 크래시 유발. 오류 덤프 시스템으로 우회시키기.
    }

    return 1;
}

int main()
{
    /* C runtime library error의 핸들러를 사용자 정의 함수로 우회시킨다.
    프로그램 처음 시작시에만 넣어주면 된다.*/
    _CrtSetReportHook(YourReportHook);

    std::vector<int> a;
    a[1] = 0; // 오류 핸들러가 우회됐는지 시험

}

2.2중단없는 오류덤프시스템(Exception Logger)

ProudNet에서는 프로그램에서 오류가 발생할 경우 프로그램을 종료하지 않고 오류 위치를 계속해서 남기는 기능이 제공됩니다. 이를 중단없는 오류덤프시스템(Exception Logger) 이라고 부릅니다.

일반적인 게임 서버는 크래쉬가 발생할 경우 즉시 상황을 덤프로 남긴 후(오류 덤프 시스템 구축하기 튜토리얼 참고) 프로그램을 재시작합니다. 하지만 부득이한 경우, 프로그램을 재시작하지 않은 채로 오류가 나는 상황을 유지하면서 억지로라도 게임 서버의 실행 유지를 강행해야 하는 경우도 있습니다. 그러나 이미 메모리 상태가 망가진 서버 프로그램이 계속 실행되는 것은 위험한 일이니 주의해야 합니다.

중단없는 오류덤프시스템 제작전 체크사항

중단없는 오류덤프시스템 제작 예제

주의 :
• Windows XP와 Windows 2003 Server 또는 그 이후의 버전의 운영체제에서 동작합니다.
• CExceptionLogger 클래스와 ATLTRACE() 또는 OutputDebugString() 를 혼용하여 사용할 경우, 로그 기록의 부하로 프로그램 성능이 하향될 수도 있습니다.
본 예제 소스 파일은 <설치 폴더>\Sample\SimpleExceptionLogger 에 있습니다.
#include "stdafx.h"
#include <atlpath.h>
#include "../../include/DumpCommon.h"
#include "../../include/MiniDumper.h"

using namespace Proud;

class CIExceptionLoggerDelegate : public IExceptionLoggerDelegate
{
    public:
    virtual String GetDumpDirectory()
    {
        return L"";
    }
};

CIExceptionLoggerDelegate g_loggerInfo;

void AccessViolation()
{
    try
    {
        int* a = 0;

        // 이 루틴은 크래쉬를 의도적으로 발생시킵니다.
        // This routine incurs crash on purpose.
        // 该例程将会故意造成崩溃。
        // このルーティンはクラッシュを意図的に発生させます
        *a = 1;
    }
    catch (...) // catch(...) syntax itself is the usable C++ keyword!
    {
        // 위 try 구문에 의해 크래쉬가 발생할 경우
        // 프로그램이 종료되지 않고 여기로 실행 지점이 오게 됩니다.
        // 한편 exception logger에 의해 오류 로그가 파일로 남게 됩니다.
        // When crash occurs by the above try syntax,
        // the execution point moves to here without terminating the program.
        // At the same time, exception logger leaves an error log file.
    }
}

void main(int argc, char* argv[])
{
    int menu = 0;
    CExceptionLogger::Instance().Init(&g_loggerInfo);

    while (1)
    {
        puts("MENU: 1. Access Violation('a')");
        printf("> ");

        menu = getchar();

        switch (menu)
        {
        case 'a':
            AccessViolation();
            break;
        default:
            break;
        }
    }
}

2.3프로세스의 현재 상태를 덤프 파일로 남기기

2. 오류덤프시스템 활용하기에서는 오류 상황이 아니더라도 프로세스의 현재 상태를 덤프 파일로 남기는 기능이 있습니다.

저장된 덤프 파일은 해당 덤프가 된 프로그램을 빌드할 때 같이 생성된 디버그 정보 파일(.pdb)과 함께 열면 프로세스의 실행중이던 상황을 소스 수준에서 볼 수 있습니다.

그림 2-1이 기능을 통해서 디버깅이 어려운 환경에서도 프로세스의 현재 상태를 덤프로 남길 수 있습니다.

Proud.CMiniDumper.WriteDumpFromHere를 호출하면 호출한 시점에서의 프로세스 내의 모든 스레드의 호출 스택을 덤프 파일로 저장합니다.

2.4NTService에서 오류덤프시스템(MiniDump) 구축하기

NTService에서 오류덤프시스템 제작전 체크사항

그림 2-2오류 덤프 시스템을 사용하기 위한 컴파일 설정”

Visual Studio 2003의 경우 위와 같이 설정할 수 없습니다. 따라서 No를 선택 후 Configuration Properties->C/C++->Command Line->Additional Options에서 /EHa 를 추가해야 합니다.

NTService에서 오류덤프시스템 예제

#include "stdafx.h"
#include <atlpath.h>
#include "../../include/DumpCommon.h"
#include "../../include/MiniDumper.h"
#include "../../include/NTService.h"
#include "../../include/ProudNetServer.h"
#include "conio.h"

using namespace std;
using namespace Proud;

const int _MAX_PATH2 = 8192;
#define _COUNTOF(array) (sizeof(array)/sizeof(array[0]))

void GetDumpFilePath(LPWSTR output)
{
    WCHAR path[_MAX_PATH2];
    WCHAR drive[_MAX_PATH2];
    WCHAR dir[_MAX_PATH2];
    WCHAR fname[_MAX_PATH2];
    WCHAR ext[_MAX_PATH2];
    WCHAR module_file_name[_MAX_PATH2];

    GetModuleFileNameW(NULL, module_file_name, _COUNTOF(module_file_name));
    _tsplitpath_s(module_file_name, drive, _MAX_PATH2, dir, _MAX_PATH2, fname, _MAX_PATH2, ext, _MAX_PATH2);
    _tmakepath_s(path, _MAX_PATH2, drive, dir, L"", L"");
    wsprintf(output, L"%s%s.DMP", path, fname);
};

int g_ServerPort = 33334;

void AccessViolation()
{
    int* a = 0;
    *a = 1;
}

class CMySvrEvent: public INTServiceEvent
{
public:
    virtual void Log(int type, LPCWSTR text)
    {
        _tprintf(L"%s\n", text);
    }

    virtual void Run()
    {
        // 윈도우 서비스 시작과 동시에 게임서버를 수행시킬 수 있는 코드를 작성합니다.
        // At the same time when Windows service gets started, design a code that can execute a game server.
        // 编写Windows服务启动的同时运行游戏引擎的代码。
        // ウィンドウサービススタートと同時にゲームサーバーを随行できるコードを作成します
        RefCount<CNetServer> srv(CNetServer::Create());
        CStartServerParameter p1;
        p1.m_tcpPorts.Add(g_ServerPort);
        
        ErrorInfoPtr err;
        
        bool result = srv->Start(p1, err);
        if (result == false)
        {
            printf("Server Start Error: %s\n",     StringW2A(ErrorInfo::TypeToString_Kor(err->m_errorType)));
            return;
        }
    
        puts("Game Server started.\n");
    
        while (1)
        {
            Sleep(100);
            if (_kbhit())
            {
                int ch = _getch();
                switch (ch)
                {
                case 27:
                    return;
                }
            }
    
            MSG msg;
    
            // 최대 일정 짧은 시간동안, 콘솔 입력, 윈도 메시지 수신, 메인 스레드 종료 중 하나를 기다린다.
            // Wait for one of console input, Windows message receiving or main thread shutting down in a short period of time.
            // 等待最短时间, 输入Console, 接收Windows消息, 终了主线程中一个。
            // 最大日程短い時間の間、コンソール入力、ウィンドウメッセージ受信、メインスレッド終了中一つを待ちます。
            MsgWaitForMultipleObjects(0, 0, TRUE, 100, QS_ALLEVENTS);
            if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
            {
                if (!GetMessage(&msg, NULL, NULL, NULL))
                    break;
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }

    virtual void Pause()
    {
        AccessViolation();
        printf("pause");
    }

    virtual void Stop()
    {
        printf("stop");
    }
    
    virtual void Continue()
    {
        printf("continue");
    }
};

CMySvrEvent g_svrEvent;

int wmain(int argc, WCHAR* argv[], WCHAR* envp[])
{
    int nRetCode = 0;
    
    WCHAR dumpFileName[_MAX_PATH2] = { 0, };
    GetDumpFilePath(dumpFileName);
    
    CMiniDumpParameter parameter;
    parameter.m_dumpFileName = dumpFileName;
    parameter.m_miniDumpType = SmallMiniDumpType;
    
    switch (CMiniDumper::Instance().Startup(parameter))
    {
    case MiniDumpAction_AlarmCrash:
        // 오류 발생으로 새로운 프로세스에서 덤프 파일을 생성한 후, 이 값이 return이 됩니다.
        // 생성된 덤프 파일을 메일로 보내거나 에러 창을 보이는 등 유저가 덤프 파일 생성 후, 처리해야할 작업을 처리해주시면 됩니다.

        // A dump file is created at a new process due to error occurrence and then this value will be returned.
        // After a user create a dump file, do works that need to be done such as sending a created dump file by email or showing an error window.

        // 因出现错误,在新的process中生成转储文件后该值将被返还。
        // 将生成的转储文件以邮件的形式发送,或可以看到 Error对话框的用户生成转存文件后,处理应处理的事即可

        // エラー発生により新しいプロセスからダンプファイルを生成した後、この値がreturnされます。
        // 生成されたダンプファイルをメールで送ったり、エラーメッセージが提示されるなどユーザーがダンプファイル生成後、処理すべきの作業をしてください。
        ...
        return nRetCode;

    case MiniDumpAction_DoNothing:
        // 유저 호출로 새로운 프로세스에서 덤프 파일을 생성한 후, 이 값이 반환됩니다.
        // 이 경우에는 아무것도 하지 말아야합니다.

        // After creating a dump file at a new process by calling a user, this value will be returned.
        // In this case, you should not do anything.

        // 因用户呼叫,在新的process中生成转储文件后,该值将被返还。
        // 在这种情况,不要做任何事情。.

        // ユーザー呼び出しにより新しいプロセスからダンプファイルを生成した後、この値が返還されます。
        // この場合何もしないでください。
        ...
        return nRetCode;

    default:
        // MiniDumpAction_None
        // 일반적으로 앱 실행 시, 이 값이 반환됩니다.
        // 여기서는 일반적으로 처리해야할 일을 처리해주시면 됩니다.

        // When executing apps, this value will be returned.
        // In this case, do works that generally need to be done.

        // 一般运行App时,该值将被返还。
        //在这里处理一般应处理的事情即可。

        // 一般的にアプリ実行後、この値が返還されます。
        // ここでは一般的に処理すべきの事を処理してください。
        ...
        break;
    }

    CNTServiceStartParameter param;
    param.m_serviceName = L"ProudNet NT Service Sample";
    param.m_serviceEvent = &g_svrEvent;

    CNTService::WinMain(argc, argv, envp, param);

    return nRetCode;
}