服務器——異常處理

服務器框架 

一、前言

服務器在運行過程如果出現數組越界訪問、訪問空指針、迭代器訪問越界等等,可能會導致出現拋出異常,導致系統崩潰。怎樣捕獲異常並快速定位問題呢?

 

二、異常捕獲和問題定位

在window下可以用minidump將崩潰轉存,通過生成的dmp可以快速定位到導致異常的地方。

2.1 異常捕獲

使用__try __except,捕獲異常,通過函數MiniDumpWriteDump生成dmp文件

 

2.2 通過dmp文件定位異常

打開dmp文件,設置符號路徑選擇pdb、dll文件所在目錄,如果是在同一個目錄可以不用設置,點擊調試之後就可以看到拋異常的地方了。

 

三、實例演示

3.1 異常捕獲

以下是一個簡單的實例,用於捕獲異常生成dmp文件.

StackWalkTest.h

#pragma once
#include <Windows.h>

#define MYTRY	__try{
#define MYTRYCATCH }__except(CStackWalk::Instance()->filterFunc(GetExceptionCode(), GetExceptionInformation())){
#define MYTRYCATCHEND	}

class CStackWalk
{
public:
	static CStackWalk* Instance()
	{
		static CStackWalk cd;
		return &cd;
	}
    
    // 創建dmp
	static long WINAPI createMiniDump(PEXCEPTION_POINTERS pExceptionPointers);
    
    // __try __except 捕獲的異常處理函數
    static int WINAPI filterFunc(int code, LPEXCEPTION_POINTERS pExceptionPtr);

    // 獲取dump文件名
    // 這個只是本地測試,這裏按照時間組合的文件名,實際項目可以自行設置文件名
    HANDLE  getDumpFile();
    
    // 創建dmp
	long onCreateMiniDump(PEXCEPTION_POINTERS pExceptionPointers);
   
    // 設置dmp類型
	void setDumpType(long dumpType);

private:
	CStackWalk();

private:
	long m_dumpType;    
};

StackWalkTest.cpp

#include "StackWalk.h"
#include <imagehlp.h>
#include <Shlwapi.h>
#include <time.h>
#include <iostream>
#include <tchar.h>
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "Shlwapi.lib")

// 實際項目中日誌,這裏標準輸出到窗口
#define TraceErrorLn(s) { printf(s);printf("\n"); }

CStackWalk::CStackWalk():m_dumpType(MiniDumpNormal)
{
	SetUnhandledExceptionFilter(createMiniDump);
}

// 設置dmp類型
void CStackWalk::setDumpType(long dumpType)
{
	m_dumpType = dumpType;
}

// 獲取dump文件名
// 這個只是本地測試,這裏按照時間組合的文件名,實際項目可以自行設置文件名
HANDLE CStackWalk::getDumpFile()
{
	wchar_t szFileName[MAX_PATH];
	HANDLE  hDumpFile = NULL;
	memset(szFileName, 0x00, MAX_PATH);
	GetModuleFileName(NULL, szFileName, MAX_PATH);
	PathRemoveFileSpec(szFileName);
	tm nowTM;
	time_t now;
	time(&now);
	localtime_s(&nowTM, &now);

	wchar_t timeStr[64];
	_stprintf_s(timeStr, TEXT("\\%04d%02d%02d%02d%02d%02d.dmp"), nowTM.tm_year + 1900, nowTM.tm_mon + 1, nowTM.tm_mday, nowTM.tm_hour, nowTM.tm_min, nowTM.tm_sec);
	_tcscat_s(szFileName, timeStr);
	hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	return hDumpFile;
}

// 創建dump
long WINAPI CStackWalk::createMiniDump(PEXCEPTION_POINTERS pExceptionPointers)
{
	Instance()->onCreateMiniDump(pExceptionPointers);
	return(0);
}

// 創建dump
long CStackWalk::onCreateMiniDump(PEXCEPTION_POINTERS pExceptionPointers)
{
	TraceErrorLn("存儲保護異常,CreateMiniDump");
	if (pExceptionPointers == NULL)
	{
		// Generate exception to get proper context in dump
		__try
		{
			// 拋出一個調用線程時發生的異常
			RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
		}
		__except (createMiniDump(GetExceptionInformation()),
			EXCEPTION_CONTINUE_EXECUTION)
		{
		}
	}
	else
	{
		MINIDUMP_EXCEPTION_INFORMATION  mdei;
		mdei.ThreadId = GetCurrentThreadId();
		mdei.ExceptionPointers = pExceptionPointers;
		mdei.ClientPointers = FALSE;

		HANDLE hDumpFile = getDumpFile();
		if (hDumpFile == INVALID_HANDLE_VALUE)
		{
			TraceErrorLn(("CreateFile Failed: %d\n"), GetLastError());
			return -1;
		}

		bool bRetCode = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)m_dumpType, &mdei, NULL, NULL);

		CloseHandle(hDumpFile);

		if (bRetCode != TRUE)
			return(GetLastError());
	}

	return(0);
}

// __try __except 捕獲的異常處理函數
int CStackWalk::filterFunc(int code, LPEXCEPTION_POINTERS pExceptionPtr)
{
    CStackWalk::Instance()->createMiniDump(pExceptionPtr);
    TraceErrorLn(("異常,錯誤代碼:%x\n"), code);
    switch (code)
    {
    case EXCEPTION_ACCESS_VIOLATION:
        if (nullptr != pExceptionPtr && nullptr != pExceptionPtr->ExceptionRecord)
        {
            TraceErrorLn(("存儲保護異常,錯誤代碼:%x, 錯誤地址:%x"), code, pExceptionPtr->ExceptionRecord->ExceptionAddress);
        }
        else
        {
            TraceErrorLn(("存儲保護異常,錯誤代碼:%x"), code);
        }
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        TraceErrorLn(("數據類型未對齊異常,錯誤代碼:%x\n"), code);
        break;
    case EXCEPTION_BREAKPOINT:
        TraceErrorLn(("中斷異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_SINGLE_STEP:
        TraceErrorLn(("單步中斷異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        TraceErrorLn(("數組越界異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
    case EXCEPTION_FLT_INEXACT_RESULT:
    case EXCEPTION_FLT_INVALID_OPERATION:
    case EXCEPTION_FLT_OVERFLOW:
    case EXCEPTION_FLT_STACK_CHECK:
    case EXCEPTION_FLT_UNDERFLOW:
        TraceErrorLn(("浮點數計算異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        TraceErrorLn(("被0除異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_INT_OVERFLOW:
        TraceErrorLn(("數據溢出異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_IN_PAGE_ERROR:
        TraceErrorLn(("頁錯誤異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        TraceErrorLn(("非法指令異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_STACK_OVERFLOW:
        TraceErrorLn(("堆棧溢出異常,錯誤代碼:%x"), code);
        break;
    case EXCEPTION_INVALID_HANDLE:
        TraceErrorLn(("無效句病異常,錯誤代碼:%x"), code);
        break;
    default:
        if (code & (1 << 29))
        {
            TraceErrorLn(("用戶自定義的軟件異常,錯誤代碼:%x"), code);
        }
        else
        {
            TraceErrorLn(("其它異常,錯誤代碼:%x"), code);
        }
        break;
    }

    return 1;
}

 

異常捕獲測試

#pragma once
#include "StackWalk.h"
#include <iostream>
#include <vector>
using namespace std;

#define NAMESPACE_STACKWALKTEST namespace NAME_STACKWALKTEST {
#define NAMESPACE_STACKWALKTESTEND }
NAMESPACE_STACKWALKTEST

//////////////////////////////////////////////////////////////////////
// 測試 用例 START
class ITest
{
public:
    virtual void testFun() = 0;
};

//typedef  void (*pFun)(); //函數指針的定義
void test(const char* testName, ITest* p)
{
    cout << "-------start: " << testName << " ------- " << endl;
    MYTRY
        p->testFun();
    MYTRYCATCH
        cout << "拋出異常" << endl;
    MYTRYCATCHEND

        cout << endl << endl;
}

// 測試用例
// 訪問空指針對象
class Test1 : public ITest
{
public:
    void operator()() { test("Test1", static_cast<ITest*>(this)); }
    void testFun()
    {
        int* p = nullptr;
        cout << *p << endl;
    }
};

// 數組索引訪問越界
class Test2 : public ITest
{
public:
    void operator()() { test("Test2", static_cast<ITest*>(this)); }
    void testFun()
    {
        int arr[10] = { 0 };
        int sum = 0;
        for (int i = 0; i < 10000000; ++i)
        {
            sum += arr[i];
        }

        cout << sum << endl;
    }
};

// 迭代器失效
class Test3 : public ITest
{
public:
    void operator()() { test("Test3", static_cast<ITest*>(this)); }
    void testFun()
    {
        for (int j = 0; j < 10; ++j)
        {
            vector<int> data = { 1,2,3,4,5 };
            for (auto it = data.begin(); it != data.end(); ++it)
            {
                if (*it < 5)
                {
                    data.push_back(10);
                }
            }
        }
    }
};

NAMESPACE_STACKWALKTESTEND
// 測試 用例 END
//////////////////////////////////////////////////////////////////////

#define TESTTRYTEST(fun) {NAME_STACKWALKTEST::fun p; p();}
void StackWalk_Test()
{
    CStackWalk::Instance();	
    cout << "---------------------------StackWalk_Test start ---------------------------" << endl;
	
    TESTTRYTEST(Test1);
    //TESTTRYTEST(Test2);
    //TESTTRYTEST(Test2);

    cout << "---------------------------StackWalk_Test endl ---------------------------" << endl;
}

上面代碼演示的是測試用例一,訪問空指針對象,執行結果如下:

 

3.2 dmp文件生成

生成的dmp文件:

 

3.3 dmp文件調試

打開dmp文件之後可以看到調用了空指針。

 

3.4 體面的崩潰

CStackWalk::CStackWalk():m_dumpType(MiniDumpNormal)
{
	SetUnhandledExceptionFilter(createMiniDump);
}

CStackWalk 構造函數中調用了SetUnhandledExceptionFilter函數,SetUnhandledExceptionFilter在程序崩潰前可以獲得程序崩潰的信息,並生成dmp文件。

 

四、總結

  1. 在系統中在可能出現異常的地方加上異常捕獲處理,框架裏可以在內存申請、線程創建加上異常捕獲,在應用層只需要在網絡消息處理、定時前回調加上異常捕獲。
  2. 考慮到在沒有加異常捕獲的地方可能也會拋出異常,可以調用SetUnhandledExceptionFilter(createMiniDump),在崩潰前生成dmp文件。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章