服務器框架
一、前言
服務器在運行過程如果出現數組越界訪問、訪問空指針、迭代器訪問越界等等,可能會導致出現拋出異常,導致系統崩潰。怎樣捕獲異常並快速定位問題呢?
二、異常捕獲和問題定位
在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文件。
四、總結
- 在系統中在可能出現異常的地方加上異常捕獲處理,框架裏可以在內存申請、線程創建加上異常捕獲,在應用層只需要在網絡消息處理、定時前回調加上異常捕獲。
- 考慮到在沒有加異常捕獲的地方可能也會拋出異常,可以調用SetUnhandledExceptionFilter(createMiniDump),在崩潰前生成dmp文件。