内容预览
1.在windows环境、MSVC编译器下,定位QT程序异常结束位置。
2.在windows环境、MINGW编译器下,定位QT程序异常结束位置。
3.适配MSVC、MINGW编译器的异常定位源码分析
QT程序异常结束位置定位
在windows下对QT程序异常结束位置定位的方法有很多如,Google的breakpad,或者是windows自带的api。
一、MSVC编译器
DbgHelp方法实现
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#ifdef Q_CC_MSVC
#include <windows.h>
#include <Dbghelp.h>
#include <iostream>
#include <vector>
#include <tchar.h>
using namespace std;
#pragma comment(lib, "Dbghelp.lib")
namespace NSDumpFile
{
void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
{
// 创建Dump文件
//
HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// Dump信息
//
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
// 写入Dump文件内容
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
CloseHandle(hDumpFile);
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
return NULL;
}
BOOL PreventSetUnhandledExceptionFilter()
{
HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
if (hKernel32 == NULL)
return FALSE;
void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
if (pOrgEntry == NULL)
return FALSE;
unsigned char newJump[100];
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
newJump[0] = 0xE9; // JMP absolute
memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
SIZE_T bytesWritten;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
return bRet;
}
LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)
{
TCHAR szMbsFile[MAX_PATH] = { 0 };
::GetModuleFileName(NULL, szMbsFile, MAX_PATH);
TCHAR* pFind = _tcsrchr(szMbsFile, '\\');
if (pFind)
{
*(pFind + 1) = 0;
_tcscat(szMbsFile, _T("CrashDumpFile.dmp"));
CreateDumpFile(szMbsFile, pException);
}
// TODO: MiniDumpWriteDump
FatalAppExit(-1, _T("Fatal Error"));
return EXCEPTION_CONTINUE_SEARCH;
}
void RunCrashHandler()
{
SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
PreventSetUnhandledExceptionFilter();
}
};
#define DeclareDumpFile() NSDumpFile::RunCrashHandler();
#endif
void crash()
{
QString *p = NULL;
p->clear();
}
int main(int argc, char *argv[])
{
DeclareDumpFile()
QApplication a(argc, argv);
crash();
return a.exec();
}
使用方法
1.1 运行程序后因跑到下图的异常代码中导致程序将异常崩溃
1.2 找到可执行文件目录,并双击CrashDumpFile.dmp
注意:
1.使用DMP文件和pdb文件调试时DMP、exe和pdb三个文件要保持版本一致。也就是说,如果你运行exe文件,程序崩溃,生成DMP文件后,此时你再重新编译程序,重新生成了exe文件和pdb文件。即使源码没有发生任何改动,你双击DMP文件也是没有办法再定位到源代码中的错误了。所以你得及时把exe文件和pdb文件做好备份。
2.有些时候即使程序崩溃了也是没有办法生成DMP文件的,比如有时数组越界导致程序崩溃的时候。所以这个方法也不能保证一定能找到程序崩溃的原因,所以大家得掌握更多的调试手段。
1.3 点击“使用 仅限本机 进行调试” 按钮
1.4 查看异常退出位置
二、MINGW编译器
参考文章: qt mingw 创建dump 并查找crash 出错行
tlhelp32方法实现
ccrashstack.h
#ifndef CCRASHSTACK_H
#define CCRASHSTACK_H
#include <QApplication>
#ifdef Q_CC_MINGW
#include <windows.h>
#include <QString>
#include <QDir>
class CCrashStack
{
private:
PEXCEPTION_POINTERS m_pException;
private:
QString GetModuleByRetAddr(PBYTE Ret_Addr, PBYTE & Module_Addr);
QString GetCallStack(PEXCEPTION_POINTERS pException);
QString GetVersionStr();
bool GetHardwareInaformation(QString &graphics_card, QString &sound_deivce);
public:
CCrashStack(PEXCEPTION_POINTERS pException);
QString GetExceptionInfo();
};
#endif
#endif //CCRASHSTACK_H
ccrashstack.cpp
#include <QApplication>
#ifdef Q_CC_MINGW
#include "ccrashstack.h"
#include <tlhelp32.h>
#include <stdio.h>
#define _WIN32_DCOM
#include <comdef.h>
#include <wbemidl.h>
#include <QFile>
#include <QDir>
//#include<base/constants.h>
#include "qdebug.h"
CCrashStack::CCrashStack(PEXCEPTION_POINTERS pException)
{
m_pException = pException;
}
QString CCrashStack::GetModuleByRetAddr(PBYTE Ret_Addr, PBYTE & Module_Addr)
{
MODULEENTRY32 M = {sizeof(M)};
HANDLE hSnapshot;
wchar_t Module_Name[MAX_PATH] = {0};
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
if ((hSnapshot != INVALID_HANDLE_VALUE) &&
Module32First(hSnapshot, &M))
{
do
{
if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)
{
lstrcpyn(Module_Name, M.szExePath, MAX_PATH);
Module_Addr = M.modBaseAddr;
break;
}
} while (Module32Next(hSnapshot, &M));
}
CloseHandle(hSnapshot);
QString sRet = QString::fromWCharArray(Module_Name);
return sRet;
}
QString CCrashStack::GetCallStack(PEXCEPTION_POINTERS pException)
{
PBYTE Module_Addr_1;
char bufer[256]={0};
QString sRet;
typedef struct STACK
{
STACK * Ebp;
PBYTE Ret_Addr;
DWORD Param[0];
} STACK, * PSTACK;
STACK Stack = {0, 0};
PSTACK Ebp;
if (pException) //fake frame for exception address
{
Stack.Ebp = (PSTACK)pException->ContextRecord->Ebp;
Stack.Ret_Addr = (PBYTE)pException->ExceptionRecord->ExceptionAddress;
Ebp = &Stack;
}
else
{
Ebp = (PSTACK)&pException - 1; //frame addr of Get_Call_Stack()
// Skip frame of Get_Call_Stack().
if (!IsBadReadPtr(Ebp, sizeof(PSTACK)))
Ebp = Ebp->Ebp; //caller ebp
}
// Break trace on wrong stack frame.
for (; !IsBadReadPtr(Ebp, sizeof(PSTACK)) && !IsBadCodePtr(FARPROC(Ebp->Ret_Addr));
Ebp = Ebp->Ebp)
{
// If module with Ebp->Ret_Addr found.
memset(bufer,0, sizeof(0));
sprintf(bufer, "\n%08X ", (unsigned int)Ebp->Ret_Addr);
sRet.append(bufer);
QString moduleName = this->GetModuleByRetAddr(Ebp->Ret_Addr, Module_Addr_1) ;
if (moduleName.length() > 0)
{
sRet.append(moduleName);
}
}
return sRet;
} //Get_Call_Stack
QString CCrashStack::GetVersionStr()
{
OSVERSIONINFOEX V = {sizeof(OSVERSIONINFOEX)}; //EX for NT 5.0 and later
if (!GetVersionEx((POSVERSIONINFO)&V))
{
ZeroMemory(&V, sizeof(V));
V.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx((POSVERSIONINFO)&V);
}
if (V.dwPlatformId != VER_PLATFORM_WIN32_NT)
V.dwBuildNumber = LOWORD(V.dwBuildNumber); //for 9x HIWORD(dwBuildNumber) = 0x04xx
QString sRet;
sRet.append(QString("Windows: %1.%2.%3, SP %4.%5, Product Type %6\n")
.arg(V.dwMajorVersion).arg(V.dwMinorVersion).arg(V.dwBuildNumber)
.arg(V.wServicePackMajor).arg(V.wServicePackMinor).arg(V.wProductType));
// QString graphics_module = "GraphicsCard: ";
// QString sound_module = "SoundDevice: ";
// GetHardwareInaformation(graphics_module, sound_module);
// sRet.append(graphics_module);
// sRet.append(sound_module);
return sRet;
}
QString CCrashStack::GetExceptionInfo()
{
WCHAR Module_Name[MAX_PATH];
PBYTE Module_Addr;
QString sRet;
char buffer[512]={0};
QString sTmp = GetVersionStr();
sRet.append(sTmp);
sRet.append("Process: ");
GetModuleFileName(NULL, Module_Name, MAX_PATH);
sRet.append(QString::fromWCharArray(Module_Name));
sRet.append("\n");
// If exception occurred.
if (m_pException)
{
EXCEPTION_RECORD & E = *m_pException->ExceptionRecord;
CONTEXT & C = *m_pException->ContextRecord;
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "Exception Addr: %08X ", (int)E.ExceptionAddress);
sRet.append(buffer);
// If module with E.ExceptionAddress found - save its path and date.
QString module = GetModuleByRetAddr((PBYTE)E.ExceptionAddress, Module_Addr);
if (module.length() > 0)
{
sRet.append(" Module: ");
sRet.append(module);
}
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "\nException Code: %08X\n", (int)E.ExceptionCode);
sRet.append(buffer);
if (E.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// Access violation type - Write/Read.
memset(buffer, 0, sizeof(buffer));
sprintf(buffer,"%s Address: %08X\n",
(E.ExceptionInformation[0]) ? "Write" : "Read", (int)E.ExceptionInformation[1]);
sRet.append(buffer);
}
sRet.append("Instruction: ");
for (int i = 0; i < 16; i++)
{
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, " %02X", PBYTE(E.ExceptionAddress)[i]);
sRet.append(buffer);
}
sRet.append("\nRegisters: ");
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "\nEAX: %08X EBX: %08X ECX: %08X EDX: %08X", (unsigned int)C.Eax,(unsigned int) C.Ebx, (unsigned int)C.Ecx, (unsigned int)C.Edx);
sRet.append(buffer);
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "\nESI: %08X EDI: %08X ESP: %08X EBP: %08X", (unsigned int)C.Esi, (unsigned int)C.Edi, (unsigned int)C.Esp, (unsigned int)C.Ebp);
sRet.append(buffer);
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "\nEIP: %08X EFlags: %08X", (unsigned int)C.Eip,(unsigned int) C.EFlags);
sRet.append(buffer);
} //if (pException)
sRet.append("\nCall Stack:");
QString sCallstack = this->GetCallStack(m_pException);
sRet.append(sCallstack);
return sRet;
}
#endif
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include "ccrashstack.h"
void crash()
{
QString *p = NULL;
p->clear();
}
int main(int argc, char *argv[])
{
SetUnhandledExceptionFilter(callback);
QApplication a(argc, argv);
crash();
return a.exec();
}
使用方法
2.1 运行程序后因跑到下图的异常代码中导致程序将异常崩溃*
2.2 找到可执行文件目录,并进入crashes文件夹并打开后缀为.log的日志文件,并记录下下图红框框起来的地方
2.3用addr2line 来查找 崩溃点
以我的QT5.9.7版本为例,addr2line.exe文件在QT安装路径下的\Tools\mingw530_32\bin 文件夹中,如下图所示。
2.3.1 打开MinGW的命令输入窗口即如下图所示窗口
2.3.2 CD到addr2line 的文件夹下
2.3.3 执行addr2line指令
addr2line.exe -f -e C:\Users\jbyyy\Desktop\debug\DumpTest.exe 00409F41 00409927 004019BD 00401A01 00403BC5
addr2line.exe -f -e '你的可执行文件路径' '输入 所有2.2所示图例红框所标的地址'
下图红框为定位到的程序异常结束位置
三、异常定位源码分析
程序适配MSVC、MINGW编译器。
将提供源码的crashManager文件夹移植到你项目的根目录下,并在你程序项目的pro文件夹中加入 include(crashManager/DumpManager.pri)即可。
使用方法
在mian.cpp中添加头文件crashManager.h,并执行setCrashManager()函数即可
关键代码展示
#ifdef Q_OS_UNIX
#include <iostream>
#include "linuxCrash/handler/QBreakpadHandler.h"
#endif
void setCrashManager()
{
&& _MSC_VER > 1500
#if defined(Q_OS_UNIX)
QCoreApplication::setApplicationName("AppName");
QCoreApplication::setApplicationVersion("1.0");
QCoreApplication::setOrganizationName("OrgName");
QCoreApplication::setOrganizationDomain("name.org");
QBreakpadInstance.setDumpPath("crashes");
#elif defined(Q_CC_MSVC)
DeclareDumpFile()
#elif defined(Q_CC_MINGW)
SetUnhandledExceptionFilter(callback);
#endif
}
源码下载
支持windows环境,MSVC、MINGW编译器源码
需要参考文献:【QT 定位程序异常结束位置】windows环境实现程序异常结束定位
支持ubuntu、arm环境源码
需要参考文献:【QT 定位程序异常结束位置】arm环境使用 breakpad 实现程序异常崩溃定位