前言:
客户现场跑的程序有可能存在长时间运行挂掉的情况,而无法复现的话无法更好的查找问题原因。所以研究了一下window下程序程序调试crash的方法。该本是介绍qt+ mingw 的。
其中用到的类,以及整个工程,我放到了github可以供下载参考:源码
(如果打不开可留言联系我)
1. 在生成的目的文件中加入调试信息(可能导致exe增大)
QMAKE_CXXFLAGS_RELEASE += -g
QMAKE_CFLAGS_RELEASE += -g
QMAKE_LFLAGS_RELEASE = -mthreads -Wl, #其中最后的逗号注意添加,如果不添加可能编译不过
#禁止优化
QMAKE_CFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_RELEASE -= -O2
2. dump 生成
#ifdef Q_OS_WIN
long __stdcall callback(_EXCEPTION_POINTERS* excp)
{
CCrashStack crashStack(excp);
QString sCrashInfo = crashStack.GetExceptionInfo();
// TCHAR my_documents[MAX_PATH];
// SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, my_documents);
// QString file_path = QString::fromWCharArray(my_documents);
QDir dir;
QString file_path = dir.currentPath();
QDir *folder_path = new QDir;
bool exist = folder_path->exists(file_path.append("\\MyApp"));
if(!exist)
{
folder_path->mkdir(file_path);
}
delete folder_path;
folder_path = nullptr;
QString sFileName = file_path + "\\crash.log";
QFile file(sFileName);
if (file.open(QIODevice::WriteOnly|QIODevice::Truncate))
{
file.write(sCrashInfo.toUtf8());
file.close();
}
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
SetUnhandledExceptionFilter(callback);
#endif
//注冊异常捕获函数
QApplication a(argc, argv);
MainWindow w;
w.show();
// QString *p = NULL;
// p->clear();
return a.exec();
}
3. 其中 CCrashStack 类是自己写的window的api(网上大佬写的吧)
ccrashstack.cpp
#include "ccrashstack.h"
#include <tlhelp32.h>
#include <stdio.h>
#define _WIN32_DCOM
#include <comdef.h>
#include <Wbemidl.h>
//#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;
}
#if 0
bool CCrashStack::GetHardwareInaformation(QString &graphics_card, QString &sound_deivce)
{
HRESULT hres;
// Initialize COM.
hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
if (FAILED(hres))
{
qDebug() << "Failed to initialize COM library. "
<< "Error code = 0x"
<< hex << hres << endl;
return false;
}
// Initialize
hres = CoInitializeSecurity(NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
if ((hres != RPC_E_TOO_LATE) && FAILED(hres))
{
qDebug() << "Failed to initialize security. "
<< "Error code = 0x"
<< hex << hres << endl;
CoUninitialize();
return false;
}
// Obtain the initial locator to Windows Management
// on a particular host computer.
IWbemLocator *pLoc = 0;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hres))
{
qDebug() << "Failed to create IWbemLocator object. "
<< "Error code = 0x"
<< hex << hres << endl;
CoUninitialize();
return false;
}
IWbemServices *pSvc = 0;
// Connect to the root\cimv2 namespace with the
// current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
if (FAILED(hres))
{
qDebug() << "Could not connect. Error code = 0x"
<< hex << hres << endl;
pLoc->Release();
CoUninitialize();
return false;
}
// Set the IWbemServices proxy so that impersonation
// of the user (client) occurs.
hres = CoSetProxyBlanket(
pSvc, // the proxy to set
RPC_C_AUTHN_WINNT, // authentication service
RPC_C_AUTHZ_NONE, // authorization service
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // authentication level
RPC_C_IMP_LEVEL_IMPERSONATE, // impersonation level
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
qDebug() << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return false;
}
// Use the IWbemServices pointer to make requests of WMI.
// Make requests here:
IEnumWbemClassObject* pEnumerator_graphics = NULL;
IEnumWbemClassObject* pEnumerator_sound = NULL;
BSTR wql = SysAllocString(L"WQL");
BSTR sql_graphics = SysAllocString(L"SELECT * FROM Win32_VideoController");
BSTR sql_sound = SysAllocString(L"SELECT * FROM Win32_SoundDevice");
HRESULT hres_graphics;
HRESULT hres_sound;
hres_graphics = pSvc->ExecQuery(wql,
sql_graphics,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator_graphics);
hres_sound = pSvc->ExecQuery(wql,
sql_sound,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator_sound);
if (FAILED(hres_graphics) || FAILED(hres_sound))
{
qDebug() << "Query for processes failed. "
<< "Error code = 0x"
<< hex << hres_graphics << endl;
qDebug() << "Query for processes failed. "
<< "Error code = 0x"
<< hex << hres_sound << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return "";
}
else
{
IWbemClassObject *pclsObj_graphics;
ULONG uReturnGraphics = 0;
IWbemClassObject *pclsObj_sound;
ULONG uReturnSound = 0;
while (pEnumerator_graphics)
{
hres_graphics = pEnumerator_graphics->Next(WBEM_INFINITE, 1, &pclsObj_graphics, &uReturnGraphics);
if (0 == uReturnGraphics)
{
break;
}
VARIANT description_vtProp;
VARIANT driver_version_vtProp;
VARIANT status_vtProp;
// Get the value of the Name property
hres_graphics = pclsObj_graphics->Get(L"Description", 0, &description_vtProp, 0, 0);
QString description((QChar*) description_vtProp.bstrVal, wcslen(description_vtProp.bstrVal));
hres_graphics = pclsObj_graphics->Get(L"DriverVersion", 0, &driver_version_vtProp, 0, 0);
QString driver_version((QChar*) driver_version_vtProp.bstrVal, wcslen(driver_version_vtProp.bstrVal));
hres_graphics = pclsObj_graphics->Get(L"Status", 0, &status_vtProp, 0, 0);
QString status((QChar*) status_vtProp.bstrVal, wcslen(status_vtProp.bstrVal));
graphics_card.append(description).append(" ").append(driver_version).append(" ").append(status).append(" \n");
VariantClear(&description_vtProp);
VariantClear(&driver_version_vtProp);
VariantClear(&status_vtProp);
}
while (pEnumerator_sound)
{
hres_sound = pEnumerator_sound->Next(WBEM_INFINITE, 1, &pclsObj_sound, &uReturnSound);
if (0 == uReturnSound)
{
break;
}
VARIANT description_vtProp;
VARIANT status_vtProp;
// Get the value of the Name property
hres_sound = pclsObj_sound->Get(L"Description", 0, &description_vtProp, 0, 0);
QString description((QChar*) description_vtProp.bstrVal, wcslen(description_vtProp.bstrVal));
hres_sound = pclsObj_sound->Get(L"Status", 0, &status_vtProp, 0, 0);
QString status((QChar*) status_vtProp.bstrVal, wcslen(status_vtProp.bstrVal));
sound_deivce.append(description).append(" ").append(status).append(" \n");
VariantClear(&description_vtProp);
VariantClear(&status_vtProp);
}
}
// Cleanup
pSvc->Release();
pLoc->Release();
CoUninitialize();
return true;
}
#endif
ccrashstack.h
#ifndef CCRASHSTACK_H
#define CCRASHSTACK_H
#include <windows.h>
#include <QString>
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 // CCRASHSTACK_H
在设置的路径下找到crash.log 文件。
4. 生产汇编信息
使用qt的mingw命令行
C:\Users\oada\Downloads\DumpTest\build-DumpTest-Desktop_Qt_5_12_4_MinGW_32_bit-Debug\debug>objdump -S DumpTest.exe > aaa.asm
5. 查看步骤2中 crash.log
查看crash.log里的 “Exception Addr: C0000005” 在app.asm中查找相应地址
6. 用addr2line 来查找 崩溃点
假如你的 崩溃日志如下
"E:\\qt\\untitled5.exe"
"Exception Addr: 00904B21 :Module: C:\\Qt\\Qt5.9.0\\5.9\\mingw53_32\\bin\\Qt5Widgetsd.dll"
"Exception Code: C0000005 :Read Address: 00000000"
"Instruction: 8B 01 8B 40 34 C7 04 24 00 00 00 00 FF D0 83 EC"
"Registers: "
"EAX: 00904B1E EBX: 185304C8 ECX: 00000000 EDX: 18500048"
"ESI: 1851DD60 EDI: 755E16DD ESP: 0028FD20 EBP: 0028FD68"
"EIP: 00904B21 EFlags: 00010202"
"00904B21 :C:\\Qt\\Qt5.9.0\\5.9\\mingw53_32\\bin\\Qt5Widgetsd.dll"
"0040187C :E:\\qt\\untitled5.exe"
"004017AB :E:\\qt\\untitled5.exe"
"004016BC :E:\\qt\\untitled5.exe"
"00403B45 :E:\\qt\\untitled5.exe"
执行:
C:\Qt\Qt5.9.0\Tools\mingw530_32\bin>addr2line.exe -f -e e:\qt\untitled5.exe 0040187C 004017AB 004016BC 00403B45
_ZN4testC2Ev
E:\qt\untitled5/test.cpp:6
_ZN10MainWindowC2EP7QWidget
E:\qt\untitled5/mainwindow.cpp:12
_Z5qMainiPPc
E:\qt\untitled5/main.cpp:35
WinMain@16
c:\Users\qt\work\qt\qtbase\src\winmain/qtmain_win.cpp:104
7. 缺点
release 版本包含了调试信息和源代码信息,用户和其他人都可以查看到。