【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
? ? ? ? 在实际项目中,有一类软件问题其实是比较难以处理的,那就是闪退。很多的工业上位机软件,都是需要长期运行的,但是软件运行的过程当中,如果发生了闪退,其实就很麻烦了。因为这个时候,即使我们记录了log文件,但是log文件距离实际发生错误的地方,其实已经隔开很远了,未必能找到真正出问题的地方。此外,就算我们拿到了log,想在公司实验室复现一下现场的问题,是不是能够准确复现,或者说复现了之后是不是还和现场一样,这都是值得商榷的。
? ? ? ? 今天呢,为了能够解决这个问题,我们引入了coredump的机制,通过现场发生错误,生成coredump的形式,来准确地找到发生问题的原因。
? ? ? ? 大多数闪退都是因为发生内存越界引起的,比如int* p = 0; *p = 0;这样的代码就会发生闪退。
? ? ? ? 一般处理闪退需要有3个文件,即exe文件、pdb文件和闪退文件
? ? ? ? 目前github上面已经有很多别人写好的类,直接引用相关的类,就可以很轻松地生成闪退文件,比如下面我自己的这个开源库,
https://github.com/feixiaoxing/coredump/tree/master
? ? ? ? 上面的开源项目本身也只是一个小的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(¤tTime);
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;
}
? ? ? ? 所有的代码都准备好之后,就可以开始测试和运行了。首先,先用windeployqt.exe生成一个基础的部署环境。接着,直接运行exe文件。最后,运行之后,如果没有什么问题的话,这个时候就会生成一个coredump文件。用ms2017打开这个文件,选择“使用 仅限本机调试”,就可以找到上位机最终出错的原因了。