天天看點

Windows c++ 崩潰時生成 dump 檔案

1、背景

在做 Windows 用戶端應用開發時,難免遇到程式的崩潰,當程式在 debug 崩潰時,我們可以直接定位到崩潰點,但是當程式打包成 release 釋出時,難免也會遇到一些崩潰問題(當然在開發時要盡量保障程式的穩定性),一般遇到這樣的崩潰,我們就需要使用 dump 檔案加上符号表檔案來進行調試程式,是以一般的 CI 除了将 exe 上傳以外還需要将符号表資訊上傳。

2、在代碼中生成 dump 檔案

#include <Windows.h>
#include <DbgHelp.h>
#include <tchar.h>
#include <shlobj_core.h>
#include <io.h>
#include <direct.h>

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>

// 生成 dump 檔案的目錄名稱
const std::string& kDUMPDir = "\\MyDumpTest";
typedef BOOL(WINAPI * MiniDumpWriteDumpT)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE,
	PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION);

int CreateDump(PEXCEPTION_POINTERS pointers) {
	HMODULE dbg_help = LoadLibrary("DbgHelp.dll");
	if (NULL == dbg_help) {
		return EXCEPTION_CONTINUE_EXECUTION;
	}

	MiniDumpWriteDumpT dump_writer = (MiniDumpWriteDumpT)GetProcAddress(dbg_help, "MiniDumpWriteDump");
	if (NULL == dump_writer) {
		FreeLibrary(dbg_help);
		return EXCEPTION_CONTINUE_EXECUTION;
	}

	// 擷取到 %localappdata% 目錄
	char buffer[256] = "";
	SHGetSpecialFolderPathA(NULL, buffer, CSIDL_LOCAL_APPDATA, FALSE);

	std::string dump_dir = buffer;
	dump_dir += kDUMPDir;
	std::cout << "dump_dir:" << dump_dir << std::endl;
	// 判斷檔案夾是否存在
	if (_access(dump_dir.c_str(), 0) == -1) {
		// 如果不存在,那麼就建立
		_mkdir(dump_dir.c_str());
	}

	// dmp 檔案名
	SYSTEMTIME local_time;
	GetLocalTime(&local_time);

	std::stringstream ss;
	ss << dump_dir << "\\dump-" << local_time.wYear << "-" <<
		std::setw(2) << std::setfill('0') << local_time.wMonth << "-" <<
		std::setw(2) << std::setfill('0') << local_time.wDay << "-" <<
		std::setw(2) << std::setfill('0') << local_time.wHour <<
		std::setw(2) << std::setfill('0') << local_time.wMinute <<
		std::setw(2) << std::setfill('0') << local_time.wSecond << ".dmp";

	HANDLE dump_file = CreateFile(ss.str().c_str(), GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
	if (INVALID_HANDLE_VALUE == dump_file) {
		FreeLibrary(dbg_help);
		return EXCEPTION_CONTINUE_EXECUTION;
	}

	// 寫入 dmp 檔案
	MINIDUMP_EXCEPTION_INFORMATION param;
	param.ThreadId = GetCurrentThreadId();
	param.ExceptionPointers = pointers;
	param.ClientPointers = FALSE;

    // MiniDumpWithDataSegs : dump 檔案的類型,一般這個就夠了
    // MiniDumpNormal 比較小 MiniDumpWithFullMemory 比較大
	dump_writer(GetCurrentProcess(), GetCurrentProcessId(),
		dump_file, MiniDumpWithDataSegs, (pointers ? &param : NULL), NULL, NULL);

	// 釋放檔案
	CloseHandle(dump_file);
	FreeLibrary(dbg_help);
	return 0;
}

LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS pointers) {
	// 這裡做一些異常的過濾或提示
	if (IsDebuggerPresent()) {
		return EXCEPTION_CONTINUE_SEARCH;
	}
	return CreateDump(pointers);
}
int main(int argc, char *argv[]) {
	SetUnhandledExceptionFilter(ExceptionFilter);
	// 執行程式
	int* p = nullptr;
	p[0] = 1;
	std::cout << "end" << std::endl;
	getchar();
	return 0;
}           

3、運作程式