MiniDump (Error Dump System)

MiniDump (Error Dump System)是ProudNet提供的一項功能,當遊戲用戶端或遊戲伺服器在網路遊戲服務過程中崩潰時,開發者可以立即收集錯誤訊息,以便追蹤原因並及時採取行動。

錯誤信息作爲*.DMP文件進行收集,開發者可以從開發工具(如VisualStudio等)中打開收集到的轉儲文件,檢查源文件在哪個行發生了衝突。

構建錯誤轉儲系統教程

錯誤轉儲系統構建教程包括遊戲服務器和客戶端。

(1) 安裝 DbgHelp 庫

dbghelp.dll 複製到安裝 OS 的文件夾 (C:\Windows\system32) 或應用程序當前文件夾中 。 dbghelp.dll(ProudNet 安裝路徑)\ProudNet\Sample\Bin 中 。

(2) 配置 Visual Studio 編譯選項

爲實現MiniDump (Error Dump System),請如下進行C++編譯設置。

配置用於使用錯誤轉儲系統的編譯

(3) 轉儲服務器製作示例

負責收集轉儲客戶端發送的錯誤信息轉儲文件。

#include "../../../include/ProudNetServer.h"
#include "../../../include/DumpServer.h"
#include "MyDumpServer.h"
class CMyDumpServer : public IDumpServerDelegate
{
    ...
    virtual void OnStartServer(CStartServerParameter &refParam) override;
    String GetDumpFilePath(HostID clientEnid, const Proud::AddrPort& clientAddr, CPnTime dumpTime) override;
    void Run();
}
void main(int argc, char* argv[])
{
    CMyDumpServer srv;
    srv.Run();
}

(4) 轉儲客戶端程序

轉儲客戶端負責將包含錯誤信息的轉儲文件傳輸到轉儲服務器。

進程中有衝突時, 自動臨時 *.創建DMP轉儲文件後重新執行該過程,命令參數(Command Argument)將附加錯誤等級信息

這樣作爲命令因子進入的錯誤等級信息要根據嚴重程度進行分支處理,MiniDumpAction_AlarmCrash屬於嚴重錯誤,因此必須填寫將轉儲文件傳送給轉儲服務器的代碼。

(5) 爲遊戲服務器創建轉儲客戶端

遊戲服務器一般不提供用戶UI,因此不顯示錯誤報告對話框需要編寫代碼以便將*.DMP轉儲文件直接發送到轉儲服務器。

(6) 爲遊戲客戶端創建轉儲客戶端

遊戲客戶端一般提供用戶UI,因此顯示錯誤報告生成的需要編寫代碼向遊戲用戶確認*.DMP轉儲文件是發送到轉儲服務器還是省略後進行。

#include "stdafx.h"
#include <atlpath.h>
#include "../../include/MiniDumper.h"
#include "../../include/DumpCommon.h"
 
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);
};
 
void AccessViolation()
{
    int* a = 0;
    *a = 1;
}
 
// void wmain(int argc, wchar_t* argv[])
int main(int argc, char* argv[])
{
    int nRetCode = 0;
    int *data = 0;
    int menu = 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;
    }
 
    while (1)
    {
        puts("MENU: 1. Access Violation('a')");
        printf("> ");
        
        menu = getchar();
 
        switch (menu)
        {
        case 'a':
            AccessViolation();
            break;
        default:
            break;
        }
    }
 
    return 0;
}

利用錯誤轉儲系統

- 在轉儲系統中攔截 C 運行錯誤

錯誤轉儲系統基本上沒有檢測到STL中的out-of-the-range錯誤或pure virtual function在運行時間調用錯誤。

錯誤轉儲系統僅處理Structured Exception,因爲上述錯誤是在C runtime library中消化的。 因此,這些錯誤必須在C runtime library消化之前繞過StructuredException才能留下錯誤轉儲。

下面是留下錯誤轉儲的方法。

// 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; // 測試錯誤處理器是否被繞過
 
}

- 無中斷的錯誤轉儲系統(Exception Logger)

ProudNet提供在程序發生錯誤時不關閉程序,繼續保留錯誤位置的功能。 這被稱爲無中斷的錯誤轉儲系統(Exception Logger)

一般遊戲服務器在發生衝突時,會立即留下情況並重新啓動程序,但不得已時,在不重新啓動程序的情況下,維持出現錯誤的情況,有時需要強行維持遊戲服務器的運行。

但是,如果內存狀態已經崩潰的服務器程序繼續運行,會很危險,所以要注意。

無中斷錯誤轉儲系統製作前檢查事項

  • 必須安裝DbgHelp庫(dbghelp.dll)。

  • 您需要爲 Visual Studio 設置 C++ Exception 編譯選項。

  • Include : DumpCommon.h, MiniDumper.h

  • Link : ProudNetCommon.lib

製作示例

在程序的起點(Main Entry Point)上, 呼叫 CExceptionLogger::Instance().Init() 函數來初始化 CExceptionLogger 實例。 繼承 IExceptionLoggerDelegate 抽象類別並重寫 GetDumpDirectory() 成員來定義轉儲檔案路徑。 如果返回空格 (""),則轉儲檔案將保存在目前資料夾中。

此示例源文件位於 <安裝文件夾>\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;
        }
    }
}

- 將進程的當前狀態保留爲轉儲文件

即使沒有出現錯誤,此功能也可以將進程的當前狀態保留爲轉儲文件,如果保存的轉儲文件與創建該轉儲程序時一起生成的調試信息文件(.pdb)一起打開,則可以在源級別上查看進程運行中的情況。

通過此功能,即使在調試困難的環境下,您也可以將進程的當前狀態保留爲自卸。

此功能即使在調試困難的環境下也能使過程保持自卸狀態。 調用 Proud.CMiniDumper.WriteDumpFromHere 將調用時點的進程中的所有線程的調用棧保存爲轉儲文件。

Last updated