前言
编写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
进行分析
分析结果如下