QT上位机开发(闪退问题的处理)

发布时间:2024年01月16日

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

? ? ? ? 在实际项目中,有一类软件问题其实是比较难以处理的,那就是闪退。很多的工业上位机软件,都是需要长期运行的,但是软件运行的过程当中,如果发生了闪退,其实就很麻烦了。因为这个时候,即使我们记录了log文件,但是log文件距离实际发生错误的地方,其实已经隔开很远了,未必能找到真正出问题的地方。此外,就算我们拿到了log,想在公司实验室复现一下现场的问题,是不是能够准确复现,或者说复现了之后是不是还和现场一样,这都是值得商榷的。

? ? ? ? 今天呢,为了能够解决这个问题,我们引入了coredump的机制,通过现场发生错误,生成coredump的形式,来准确地找到发生问题的原因。

1、什么情况下会发生闪退

? ? ? ? 大多数闪退都是因为发生内存越界引起的,比如int* p = 0; *p = 0;这样的代码就会发生闪退。

2、处理闪退需要哪几个步骤

? ? ? ? 一般处理闪退需要有3个文件,即exe文件、pdb文件和闪退文件

3、怎么生成闪退文件

? ? ? ? 目前github上面已经有很多别人写好的类,直接引用相关的类,就可以很轻松地生成闪退文件,比如下面我自己的这个开源库,

https://github.com/feixiaoxing/coredump/tree/master

4、如何把上面的开源代码引入到自己的项目

? ? ? ? 上面的开源项目本身也只是一个小的demo。如果需要把相关的代码引入到自己的项目中,只需要这三步就可以了,首先引入头文件CMiniDump.h,


#ifndef MINIDUMPER_H
#define MINIDUMPER_H

#include <windows.h>

class CMiniDumper
{
public:

	CMiniDumper(bool bPromptUserForMiniDump);
	~CMiniDumper(void);

private:

	static LONG WINAPI unhandledExceptionHandler(struct _EXCEPTION_POINTERS *pExceptionInfo);
	void setMiniDumpFileName(void);
	bool getImpersonationToken(HANDLE* phToken);
	BOOL enablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);
	BOOL restorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);
	LONG writeMiniDump(_EXCEPTION_POINTERS *pExceptionInfo);

	_EXCEPTION_POINTERS *m_pExceptionInfo;
	TCHAR m_szMiniDumpPath[MAX_PATH];
	TCHAR m_szAppPath[MAX_PATH];
	TCHAR m_szAppBaseName[MAX_PATH];
	bool m_bPromptUserForMiniDump;

	static CMiniDumper* s_pMiniDumper;
	static LPCRITICAL_SECTION s_pCriticalSection;
};

#endif 

? ? ? ? 接着,引入源文件CMiniDump.cpp,即,

#include "CMiniDump.h"
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <strsafe.h>
//#include <tchar.h>
#include <dbghelp.h>

#ifdef UNICODE
#define _tcssprintf wsprintf
#define tcsplitpath _wsplitpath
#else
#define _tcssprintf sprintf
#define tcsplitpath _splitpath
#endif

const int USER_DATA_BUFFER_SIZE = 4096;

//-----------------------------------------------------------------------------
// GLOBALS
//-----------------------------------------------------------------------------
CMiniDumper* CMiniDumper::s_pMiniDumper = NULL;
LPCRITICAL_SECTION CMiniDumper::s_pCriticalSection = NULL;

// Based on dbghelp.h
typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess,
	DWORD dwPid,
	HANDLE hFile,
	MINIDUMP_TYPE DumpType,
	CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
	CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
	CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

//-----------------------------------------------------------------------------
// Name: CMiniDumper()
// Desc: Constructor
//-----------------------------------------------------------------------------
CMiniDumper::CMiniDumper(bool bPromptUserForMiniDump)
{
	// Our CMiniDumper should act alone as a singleton.
	assert(!s_pMiniDumper);

	s_pMiniDumper = this;
	m_bPromptUserForMiniDump = bPromptUserForMiniDump;

	// The SetUnhandledExceptionFilter function enables an application to 
	// supersede the top-level exception handler of each thread and process.
	// After calling this function, if an exception occurs in a process 
	// that is not being debugged, and the exception makes it to the 
	// unhandled exception filter, that filter will call the exception 
	// filter function specified by the lpTopLevelExceptionFilter parameter.
	::SetUnhandledExceptionFilter(unhandledExceptionHandler);

	// Since DBGHELP.dll is not inherently thread-safe, making calls into it 
	// from more than one thread simultaneously may yield undefined behavior. 
	// This means that if your application has multiple threads, or is 
	// called by multiple threads in a non-synchronized manner, you need to  
	// make sure that all calls into DBGHELP.dll are isolated via a global
	// critical section.
	s_pCriticalSection = new CRITICAL_SECTION;

	if (s_pCriticalSection)
		InitializeCriticalSection(s_pCriticalSection);
}

//-----------------------------------------------------------------------------
// Name: ~CMiniDumper()
// Desc: Destructor
//-----------------------------------------------------------------------------
CMiniDumper::~CMiniDumper(void)
{
	if (s_pCriticalSection)
	{
		DeleteCriticalSection(s_pCriticalSection);
		delete s_pCriticalSection;
	}
}

//-----------------------------------------------------------------------------
// Name: unhandledExceptionHandler()
// Desc: Call-back filter function for unhandled exceptions
//-----------------------------------------------------------------------------
LONG CMiniDumper::unhandledExceptionHandler(_EXCEPTION_POINTERS *pExceptionInfo)
{
	if (!s_pMiniDumper)
		return EXCEPTION_CONTINUE_SEARCH;

	return s_pMiniDumper->writeMiniDump(pExceptionInfo);
}

//-----------------------------------------------------------------------------
// Name: setMiniDumpFileName()
// Desc: 
//-----------------------------------------------------------------------------
void CMiniDumper::setMiniDumpFileName(void)
{
	time_t currentTime;
	time(&currentTime);

	wsprintf(m_szMiniDumpPath,
		L"%s%s.%ld.dmp",
		m_szAppPath,
		m_szAppBaseName,
		currentTime);
}

//-----------------------------------------------------------------------------
// Name: getImpersonationToken()
// Desc: The method acts as a potential workaround for the fact that the 
//       current thread may not have a token assigned to it, and if not, the 
//       process token is received.
//-----------------------------------------------------------------------------
bool CMiniDumper::getImpersonationToken(HANDLE* phToken)
{
	*phToken = NULL;

	if (!OpenThreadToken(GetCurrentThread(),
		TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
		TRUE,
		phToken))
	{
		if (GetLastError() == ERROR_NO_TOKEN)
		{
			// No impersonation token for the current thread is available. 
			// Let's go for the process token instead.
			if (!OpenProcessToken(GetCurrentProcess(),
				TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
				phToken))
				return false;
		}
		else
			return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Name: enablePrivilege()
// Desc: Since a MiniDump contains a lot of meta-data about the OS and 
//       application state at the time of the dump, it is a rather privileged 
//       operation. This means we need to set the SeDebugPrivilege to be able 
//       to call MiniDumpWriteDump.
//-----------------------------------------------------------------------------
BOOL CMiniDumper::enablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
	BOOL bOk = FALSE;

	TOKEN_PRIVILEGES tp;
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	bOk = LookupPrivilegeValue(0, pszPriv, &tp.Privileges[0].Luid);

	if (bOk)
	{
		DWORD cbOld = sizeof(*ptpOld);
		bOk = AdjustTokenPrivileges(hToken, FALSE, &tp, cbOld, ptpOld, &cbOld);
	}

	return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}

//-----------------------------------------------------------------------------
// Name: restorePrivilege()
// Desc: 
//-----------------------------------------------------------------------------
BOOL CMiniDumper::restorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
	BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, NULL, NULL);
	return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}

#include <tchar.h>
//-----------------------------------------------------------------------------
// Name: writeMiniDump()
// Desc: 
//-----------------------------------------------------------------------------
LONG CMiniDumper::writeMiniDump(_EXCEPTION_POINTERS *pExceptionInfo)
{
	LONG retval = EXCEPTION_CONTINUE_SEARCH;
	m_pExceptionInfo = pExceptionInfo;

	HANDLE hImpersonationToken = NULL;
	if (!getImpersonationToken(&hImpersonationToken))
		return FALSE;

	// You have to find the right dbghelp.dll. 
	// Look next to the EXE first since the one in System32 might be old (Win2k)

	HMODULE hDll = NULL;
	TCHAR szDbgHelpPath[MAX_PATH];

	if (GetModuleFileName(NULL, m_szAppPath, _MAX_PATH))
	{
		TCHAR *pSlash = wcsrchr(m_szAppPath, '\\');

		if (pSlash)
		{
			_tcscpy_s(m_szAppBaseName, pSlash + 1);
			*(pSlash + 1) = 0;
		}

		wcscpy_s(szDbgHelpPath, m_szAppPath);
		wcscat_s(szDbgHelpPath, L"DBGHELP.DLL");
		hDll = ::LoadLibrary(szDbgHelpPath);
	}

	if (hDll == NULL)
	{
		// If we haven't found it yet - try one more time.
		hDll = ::LoadLibrary(L"DBGHELP.DLL");
	}

	LPCTSTR szResult = NULL;

	if (hDll)
	{
		// Get the address of the MiniDumpWriteDump function, which writes 
		// user-mode mini-dump information to a specified file.
		MINIDUMPWRITEDUMP MiniDumpWriteDump =
			(MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump");

		if (MiniDumpWriteDump != NULL)
		{
			TCHAR szScratch[USER_DATA_BUFFER_SIZE];

			setMiniDumpFileName();

			// Ask the user if he or she wants to save a mini-dump file...
			wsprintf(szScratch,
				L"There was an unexpected error:\n\nWould you "
				L"like to create a mini-dump file?\n\n%s ",
				m_szMiniDumpPath);

			// Create the mini-dump file...
			HANDLE hFile = ::CreateFile(m_szMiniDumpPath,
				GENERIC_WRITE,
				FILE_SHARE_WRITE,
				NULL,
				CREATE_ALWAYS,
				FILE_ATTRIBUTE_NORMAL,
				NULL);

			if (hFile != INVALID_HANDLE_VALUE)
			{
				_MINIDUMP_EXCEPTION_INFORMATION ExInfo;
				ExInfo.ThreadId = ::GetCurrentThreadId();
				ExInfo.ExceptionPointers = pExceptionInfo;
				ExInfo.ClientPointers = NULL;

				// We need the SeDebugPrivilege to be able to run MiniDumpWriteDump
				TOKEN_PRIVILEGES tp;
				BOOL bPrivilegeEnabled = enablePrivilege(SE_DEBUG_NAME, hImpersonationToken, &tp);

				BOOL bOk;

				// DBGHELP.dll is not thread-safe, so we need to restrict access...
				EnterCriticalSection(s_pCriticalSection);
				{
					// Write out the mini-dump data to the file...
					bOk = MiniDumpWriteDump(GetCurrentProcess(),
						GetCurrentProcessId(),
						hFile,
						MiniDumpNormal,
						&ExInfo,
						NULL,
						NULL);
				}
				LeaveCriticalSection(s_pCriticalSection);

				// Restore the privileges when done
				if (bPrivilegeEnabled)
					restorePrivilege(hImpersonationToken, &tp);

				if (bOk)
				{
					szResult = NULL;
					retval = EXCEPTION_EXECUTE_HANDLER;
				}
				else
				{
					wsprintf(szScratch,
						L"Failed to save the mini-dump file to '%s' (error %d)",
						m_szMiniDumpPath,
						GetLastError());

					szResult = szScratch;
				}

				::CloseHandle(hFile);
			}
			else
			{
				wsprintf(szScratch,
					L"Failed to create the mini-dump file '%s' (error %d)",
					m_szMiniDumpPath,
					GetLastError());

				szResult = szScratch;
			}
		}
		else
		{
			szResult = L"Call to GetProcAddress failed to find MiniDumpWriteDump. "
				L"The DBGHELP.DLL is possibly outdated.";
		}
	}
	else
	{
		szResult = L"Call to LoadLibrary failed to find DBGHELP.DLL.";
	}

	if (szResult && m_bPromptUserForMiniDump)
		::MessageBox(NULL, szResult, NULL, MB_OK);

	TerminateProcess(GetCurrentProcess(), 0);

	return retval;
}

? ? ? ? 最后就是测试和验证,

#include <iostream>
using namespace std;
#include "CMiniDump.h"

int main()
{

	CMiniDumper g_miniDumper(true);
	std::cout << "Hello World!\n";

	int*p = 0;
	*p = 1;
    return 0;
}

5、编译测试和运行

? ? ? ? 所有的代码都准备好之后,就可以开始测试和运行了。首先,先用windeployqt.exe生成一个基础的部署环境。接着,直接运行exe文件。最后,运行之后,如果没有什么问题的话,这个时候就会生成一个coredump文件。用ms2017打开这个文件,选择“使用 仅限本机调试”,就可以找到上位机最终出错的原因了。

文章来源:https://blog.csdn.net/feixiaoxing/article/details/135615076
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。