windows下使用dbghelp解决未知崩溃问题

前言

编写QT程序后在自己电脑测试无问题,但是换一个电脑就会出现未知的崩溃问题,这种问题分析起来可不是紧靠加日志就可以解决的,所以需要借助windows下dbghelp来捕获崩溃时堆栈,生成dmp文件
在使用windbg进行分析

代码

以下代码已对多字节字符集和 Unicode 字符集进行了兼容,并且支持32和64位,可以直接将文件放入自己项目中,也可编译成动态库,但是需要注意动态库字符集及位数和工程使用的字符集和位数要一致。记得在链接器加入shlwapi.lib

// windump.h
#ifndef __WIN_DUMP_H__
#define __WIN_DUMP_H__

#include <string>
#include <atomic>

#if defined(_UNICODE) || defined(UNICODE)
using String = std::wstring;
#else
using String = std::string;
#endif //  defined(_UNICODE) || defined(UNICODE)

class WinDump
{
public:
	WinDump() {}
	~WinDump() {}

	static void CatchUnhandledException(const String &dmpFilePath);
};

#endif // __WIN_DUMP_H__

// windump.cpp
#pragma warning(disable: 4996)
#include "windump.h"
#include <mutex>
#include <time.h>
#include <Windows.h>
#include <DbgHelp.h>
#include <TlHelp32.h>
#include <Shlwapi.h>

//#if defined(_UNICODE) || defined(UNICODE)
//#error "not support UNICODE"
//#endif //  defined(_UNICODE) || defined(UNICODE)

#if defined(_UNICODE) || defined(UNICODE)
#define SPRINTF wsprintf
#define PRINTF  wprintf
#define _STR	"%ws"
#else
#define SPRINTF sprintf
#define PRINTF	printf
#define _STR	"%s"
#endif //  defined(_UNICODE) || defined(UNICODE)

#ifndef _DBGHELP_

typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
	DWORD	ThreadId;
	PEXCEPTION_POINTERS	ExceptionPointers;
	BOOL	ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, * PMINIDUMP_EXCEPTION_INFORMATION;

typedef enum _MINIDUMP_TYPE {
	MiniDumpNormal = 0x00000000,
	MiniDumpWithDataSegs = 0x00000001,
} MINIDUMP_TYPE;

typedef	BOOL(WINAPI* MINIDUMP_WRITE_DUMP)(
	IN HANDLE			hProcess,
	IN DWORD			ProcessId,
	IN HANDLE			hFile,
	IN MINIDUMP_TYPE	DumpType,
	IN CONST PMINIDUMP_EXCEPTION_INFORMATION	ExceptionParam, OPTIONAL
	IN PVOID									UserStreamParam, OPTIONAL
	IN PVOID									CallbackParam OPTIONAL
	);

#else

typedef	BOOL(WINAPI* MINIDUMP_WRITE_DUMP)(
	IN HANDLE			hProcess,
	IN DWORD			ProcessId,
	IN HANDLE			hFile,
	IN MINIDUMP_TYPE	DumpType,
	IN CONST PMINIDUMP_EXCEPTION_INFORMATION	ExceptionParam, OPTIONAL
	IN PMINIDUMP_USER_STREAM_INFORMATION		UserStreamParam, OPTIONAL
	IN PMINIDUMP_CALLBACK_INFORMATION			CallbackParam OPTIONAL
	);
#endif

typedef	HANDLE(WINAPI* CREATE_TOOL_HELP32_SNAPSHOT)(DWORD flag, DWORD pid);
typedef	BOOL(WINAPI* MODULE32_FIRST)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
typedef	BOOL(WINAPI* MODULE32_NEXT)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);

#define DBGHELP_DLL		TEXT("dbghelp.dll")
#define	DUMP_FUNC_NAME	("MiniDumpWriteDump")

#define KERNEL32_DLL	TEXT("kernel32.dll")
#define SNAPSHOT_NAME	TEXT("CreateToolhelp32Snapshot")
#define MODULE_FIRST	TEXT("Module32First")
#define MODULE_NEXT		TEXT("Module32Next")

static String gDumpFile;
static std::atomic<bool> gInit = false;

HMODULE gDbgHelpDll = nullptr;
MINIDUMP_WRITE_DUMP gDumpFunc = nullptr;

CREATE_TOOL_HELP32_SNAPSHOT gSnapShotFunc = nullptr;
MODULE32_FIRST gModuleFirst = nullptr;
MODULE32_NEXT  gModuleNext = nullptr;


LONG WINAPI CrashReport(PEXCEPTION_POINTERS pExceptionInfo)
{
	if (gDumpFile.find('\\') != String::npos &&
		gDumpFile.find('/') != String::npos) {
		PRINTF(TEXT("invalid path: " _STR ", only one of '\\' or '/' can be included\n"), gDumpFile.c_str());
		exit(0);
	}

	size_t pos = gDumpFile.find_last_of('\\');
	if (pos == String::npos) {
		pos = gDumpFile.find_last_of('/');
		if (pos == String::npos) {
			PRINTF(TEXT("invalid path: " _STR "\n"), gDumpFile.c_str());
			return 0;
		}
	}

	String path = String(gDumpFile.c_str(), pos);
	String fileName = gDumpFile.c_str() + pos + 1;
	if (PathFileExists(path.c_str()) == false) {
		CreateDirectory(path.c_str(), nullptr);
	}

	time_t seconds = time(NULL);
	struct tm* tim = localtime(&seconds);

#if defined(_UNICODE) || defined(UNICODE)
	wchar_t timeBuf[128] = { 0 };
#else
	char timeBuf[128] = { 0 };
#endif //  defined(_UNICODE) || defined(UNICODE)
	SPRINTF(timeBuf, TEXT("%04d-%02d-%02d %02d_%02d_%02d_"),
		tim->tm_year + 1900, tim->tm_mon + 1, tim->tm_mday, tim->tm_hour, tim->tm_min, tim->tm_sec);
	fileName = String(timeBuf) + fileName;

#ifdef _DEBUG
	PRINTF(TEXT("file name: " _STR "\n"), fileName.c_str());
#endif // _DEBUG

	if (gDumpFunc) {
		MINIDUMP_EXCEPTION_INFORMATION	info;
		info.ThreadId = GetCurrentThreadId();
		info.ExceptionPointers = pExceptionInfo;
		info.ClientPointers = 0;

		HANDLE dmpFile = CreateFile((path + fileName).c_str(), GENERIC_WRITE, 0, nullptr,
			CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
		gDumpFunc(GetCurrentProcess(), GetCurrentProcessId(), dmpFile, MiniDumpNormal,
			pExceptionInfo ? &info : nullptr, nullptr, nullptr);

		CloseHandle(dmpFile);
	}

	return 0;
}

void onDestroy()
{
	if (gDbgHelpDll != nullptr) {
		FreeLibrary(gDbgHelpDll);
	}
}

void WinDump::CatchUnhandledException(const String& dmpFilePath)
{
	if (gInit) {
		return;
	}

	gDumpFile = dmpFilePath;
	SetUnhandledExceptionFilter(CrashReport);

	gDbgHelpDll = LoadLibrary(DBGHELP_DLL);
	if (gDbgHelpDll != nullptr) {
		gDumpFunc = (MINIDUMP_WRITE_DUMP)GetProcAddress(gDbgHelpDll, DUMP_FUNC_NAME);
	}

	::atexit(onDestroy);
	gInit = true;
}
// 测试文件 main.cpp
#include "windump.h"
#include <Windows.h>

int main()
{
    WinDump::CatchUnhandledException(TEXT("./windump_test.dmp"));

    char* ptr = nullptr;
    ptr[1] = '\0';

    return 0;
}
测试

使用cmd运行程序,不要再vs中执行,会被vs捕获
执行后在当前目录生成一个日期+windump_test.dmp格式的dmp文件,使用windbg加载它
File -> open Crash Dump,然后设置pdb文件路径,一般就是和程序在一块,除非在链接器->调试修改过如下是未修改的
在这里插入图片描述
File -> Symbol File Path设置pdb路径,无需加上pdb文件名
在打开的文件窗口最下面一栏输入!analyze -v进行分析
分析结果如下
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值