站在巨人的肩膀上

Dr. Mingw

概述

  • 剖析 Dr. Mingw 的源代码,抽取最核心的代码,封装成一个动态库,名为 MiniDrMingw.dll。
  • MiniDrMingw.dll 是面向过程的,容易看懂,没有什么跳转。
  • 可以加入各种个性化需求(如在崩溃时,记录崩溃时的 CPU 占用率、内存占用情况等)。
  • 大概使用方式就是:
    1、应用程序加上编译参数,生成 .map 文件,里面记录着程序的函数地址等。
    # gcc 编译选项,生成 .map 文件
    QMAKE_LFLAGS += -Wl,-Map=$$PWD'/bin/'$$TARGET'.map'2、应用程序在 main 函数里面,使用下面语句进行初始化:
    ExcHndlInit((a.applicationDirPath() +"/" + a.applicationName() + ".RPT").toLocal8Bit().data());
    
  • 在崩溃时,如何定位出崩溃的函数,后面在讲 Demo 的时候再说清楚。

MiniDrMingw.dll 的主要函数:SetUnhandledExceptionFilter-设置异常捕获函数。

  • 当异常没有处理的时候,系统就会调用 SetUnhandledExceptionFilter 所设置异常处理函数.
  • 当发生异常时,比如内存访问违例时,CPU 硬件会发现此问题,并产生一个异常(你可以把它理解为中断),然后 CPU 会把代码流程切换到异常处理服务例程。操作系统异常处理服务例程会查看当前进程是否处于调试状态。如果是,则通知调试器发生了异常,如果不是则操作系统会查看当前线程是否安装了的异常帧链(FS[0]),如果安装了 SEH(try… catch…),则调用 SEH,并根据返回结果决定是否全局展开或局部展开。如果异常链中所有的 SEH 都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常(二次异常)。如果还没人处理,则调用操作系统的默认异常处理代码 UnhandledExceptionHandler,不过操作系统允许你 Hook 这个函数,就是通过 SetUnhandledExceptionFilter 函数来设置。大部分异常通过此种方法都能捕获,不过栈溢出、覆盖的有可能捕获不到。
  • 该函数说明如下:
    LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( _In_LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
    - 参数:lpTopLevelExceptionFilter,函数指针。当异常发生时,且程序不处于调试模式(在 vs 或者别的调试器里运行)则首先调用该函数。
    - 返回值:返回以前设置的回调函数。
    

MiniDrMingw.dll 的函数调用关系

void ExcHndlInit(const char *pCrashFileName)↓
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter)↓
MyUnhandledExceptionFilter↓
GenerateExceptionReport↓
dumpException(hProcess, pExceptionRecord)、dumpStack(hProcess, GetCurrentThread(), pContext)、dumpModules(hProcess);
  • 详细说明可以看下面源代码的注释。

MiniDrMingw.dll 的源代码

  • 虽然用 QtCreator 开发,但是源代码基本没有用到 Qt 的东西。
  • 以动态库的形式。
  • 分为以前几个文件:
    • main.cpp:导出函数、主要函数。
    • dllexport.h:导出接口。
    • log.h:写文件。
    • formatoutput.h:将崩溃信息格式为可读信息。
    • util.h:一些辅助类的函数。
  • 工程文件 DrMingwDemo.pro 源代码如下:
    // MiniDrMingw.pro
    QT += coreTARGET = MiniDrMingw
    TEMPLATE = lib
    DESTDIR = binCONFIG += skip_target_version_extDEFINES += __SHARE_EXPORTwin32{
    QMAKE_LFLAGS += -Wl,--add-stdcall-alias
    }LIBS += -lpsapi -lversion -ldbghelpHEADERS += \dllexport.h \log.h \formatoutput.h \util.hunix {target.path = /usr/libINSTALLS += target
    }SOURCES += \main.cpp
    
  • main.cpp 源代码如下:
    // main.cpp
    #include "dllexport.h"
    #include <windows.h>
    #include <string>
    #include <imagehlp.h>
    #include <shlobj.h>
    #include <assert.h>
    #include <psapi.h>
    #include <tlhelp32.h>
    #include "log.h"
    #include "formatoutput.h"static std::string g_sCrashFileName;
    static BOOL g_bHandlerSet = FALSE;
    static LPTOP_LEVEL_EXCEPTION_FILTER g_prevUnhandledExceptionFilter = nullptr;DWORD SetSymOptions(BOOL fDebug)
    {DWORD dwSymOptions = SymGetOptions();// We have more control calling UnDecorateSymbolName directly Also, we// don't want DbgHelp trying to undemangle MinGW symbols (e.g., from DLL// exports) behind our back (as it will just strip the leading underscore.)if (0) {dwSymOptions |= SYMOPT_UNDNAME;} else {dwSymOptions &= ~SYMOPT_UNDNAME;}dwSymOptions |= SYMOPT_LOAD_LINES | SYMOPT_OMAP_FIND_NEAREST;if (TRUE) {dwSymOptions |= SYMOPT_DEFERRED_LOADS;}if (fDebug) {dwSymOptions |= SYMOPT_DEBUG;}#ifdef _WIN64dwSymOptions |= SYMOPT_INCLUDE_32BIT_MODULES;
    #endifreturn SymSetOptions(dwSymOptions);
    }BOOL InitializeSym(HANDLE hProcess, BOOL fInvadeProcess)
    {// Provide default symbol search path// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680689.aspx// http://msdn.microsoft.com/en-gb/library/windows/hardware/ff558829.aspxstd::string sSymSearchPathBuf;const char *szSymSearchPath = nullptr;if (getenv("_NT_SYMBOL_PATH") == nullptr && getenv("_NT_ALT_SYMBOL_PATH") == nullptr) {char szLocalAppData[MAX_PATH];HRESULT hr = SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, szLocalAppData);assert(SUCCEEDED(hr));if (SUCCEEDED(hr)) {// Cache symbols in %APPDATA%\drmingwsSymSearchPathBuf += "srv*";sSymSearchPathBuf += szLocalAppData;sSymSearchPathBuf += "\\drmingw*http://msdl.microsoft.com/download/symbols";szSymSearchPath = sSymSearchPathBuf.c_str();} else {// No cacheszSymSearchPath = "srv*http://msdl.microsoft.com/download/symbols";}}return SymInitialize(hProcess, szSymSearchPath, fInvadeProcess);
    }void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo)
    {// pExceptionInfo 的结构体信息扩展开来如下:// typedef struct _EXCEPTION_POINTERS {//  DWORD ExceptionCode;//  DWORD ExceptionFlags;//  struct _EXCEPTION_RECORD *ExceptionRecord;//  PVOID ExceptionAddress;//  DWORD NumberParameters;//  ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];////  DWORD ContextFlags;//  DWORD Dr0;//  DWORD Dr1;//  DWORD Dr2;//  DWORD Dr3;//  DWORD Dr6;//  DWORD Dr7;//  DWORD ControlWord;//  DWORD StatusWord;//  DWORD TagWord;//  DWORD ErrorOffset;//  DWORD ErrorSelector;//  DWORD DataOffset;//  DWORD DataSelector;//  BYTE RegisterArea[SIZE_OF_80387_REGISTERS];//  DWORD Cr0NpxState;//  DWORD SegGs;//  DWORD SegFs;//  DWORD SegEs;//  DWORD SegDs;//  DWORD Edi;//  DWORD Esi;//  DWORD Ebx;//  DWORD Edx;//  DWORD Ecx;//  DWORD Eax;//  DWORD Ebp;//  DWORD Eip;//  DWORD SegCs;//  DWORD EFlags;//  DWORD Esp;//  DWORD SegSs;//  BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];//} EXCEPTION_POINTERS,*PEXCEPTION_POINTERS;// Start out with a bannerwriteLog("-------------------\n\n");// 打印系统时间SYSTEMTIME SystemTime;GetLocalTime(&SystemTime);char szDateStr[128];LCID Locale = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);GetDateFormatA(Locale, 0, &SystemTime, "dddd',' MMMM d',' yyyy", szDateStr, _countof(szDateStr));char szTimeStr[128];GetTimeFormatA(Locale, 0, &SystemTime, "HH':'mm':'ss", szTimeStr, _countof(szTimeStr));writeLog("Error occurred on %s at %s.\n\n", szDateStr, szTimeStr);// 获取当前进程的一个句柄HANDLE hProcess = GetCurrentProcess();SetSymOptions(FALSE);PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;if(InitializeSym(hProcess, TRUE)){// 打印崩溃地址信息dumpException(hProcess, pExceptionRecord);PCONTEXT pContext = pExceptionInfo->ContextRecord;assert(pContext);// XXX: In 64-bits WINE we can get context record that don't match the exception record somehow
    #ifdef _WIN64PVOID ip = (PVOID)pContext->Rip;
    #elsePVOID ip = (PVOID)pContext->Eip;
    #endifif(pExceptionRecord->ExceptionAddress != ip){writeLog("warning: inconsistent exception context record\n");}// 函数调用栈信息dumpStack(hProcess, GetCurrentThread(), pContext);if (!SymCleanup(hProcess)) {assert(0);}}// 打印调用模块的信息dumpModules(hProcess);writeLog("\n");
    }// 当出现未被处理的异常时,会先调用该函数
    static LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
    {static LONG nLockNum = 0;// 实现数的原子性加减if(1 == InterlockedIncrement(&nLockNum)){// SetErrorMode() 函数控制 Windows 是否处理指定类型的严重错误或使调用应用程序来处理它们。// SEM_FAILCRITICALERRORS: 系统不显示关键错误处理消息框。相反,系统发送错误给调用进程。// SEM_NOGPFAULTERRORBOX: 系统不显示Windows错误报告对话框。// SEM_NOALIGNMENTFAULTEXCEPT: 系统会自动修复故障。此功能只支持部分处理器架构。// SEM_NOOPENFILEERRORBOX:当无法找到文件时不弹出错误对话框。相反,错误返回给调用进程。UINT oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);// 创建崩溃时写到磁盘的 .RPT 文件if(nullptr == g_handleCrashFile){g_handleCrashFile = CreateFileA(g_sCrashFileName.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);}if(nullptr != g_handleCrashFile){// 在一个文件中设置新的读取位置SetFilePointer(g_handleCrashFile, 0, 0, FILE_END);// 主要通过该函数来生成异常报告信息,其中 pExceptionInfo 是异常信息结构体指针。GenerateExceptionReport(pExceptionInfo);// 刷新输出缓存区(也就是为了实时写磁盘)FlushFileBuffers(g_handleCrashFile);}// 把错误模式设置回去SetErrorMode(oldErrorMode);}// 实现数的原子性加减InterlockedDecrement(&nLockNum);// 以前也设置过异常捕获函数,那么处理完我们的异常信息之后,我们再调用它。if(g_prevUnhandledExceptionFilter)return g_prevUnhandledExceptionFilter(pExceptionInfo);elsereturn EXCEPTION_CONTINUE_SEARCH;
    }void ExcHndlInit(const char *pCrashFileName)
    {if(nullptr != pCrashFileName)g_sCrashFileName = pCrashFileName;elseg_sCrashFileName = "./crash.RPT";if(FALSE == g_bHandlerSet){// 设置自己的异常捕获函数,返回以前设置的异常捕获函数g_prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);g_bHandlerSet = TRUE;}
    }
    
  • formatoutput.h 源代码如下:
    // formatoutput.h
    #ifndef FORMATOUTPUT_H
    #define FORMATOUTPUT_H#include "log.h"
    #include "util.h"BOOL dumpSourceCode(LPCSTR lpFileName, DWORD dwLineNumber)
    {FILE *fp;unsigned i;DWORD dwContext = 2;if ((fp = fopen(lpFileName, "r")) == NULL)return FALSE;i = 0;while (!feof(fp) && ++i <= dwLineNumber + dwContext) {int c;if ((int)i >= (int)dwLineNumber - (int)dwContext) {writeLog(i == dwLineNumber ? ">%5i: " : "%6i: ", i);while (!feof(fp) && (c = fgetc(fp)) != '\n')if (isprint(c))writeLog("%c", c);writeLog("\n");} else {while (!feof(fp) && fgetc(fp) != '\n');}}fclose(fp);return TRUE;
    }void dumpContext(
    #ifdef _WIN64const WOW64_CONTEXT *pContext
    #elseconst CONTEXT *pContext
    #endif
    )
    {// Show the registerswriteLog("Registers:\n");if (pContext->ContextFlags & CONTEXT_INTEGER) {writeLog("eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx esi=%08lx edi=%08lx\n", pContext->Eax,pContext->Ebx, pContext->Ecx, pContext->Edx, pContext->Esi, pContext->Edi);}if (pContext->ContextFlags & CONTEXT_CONTROL) {writeLog("eip=%08lx esp=%08lx ebp=%08lx iopl=%1lx %s %s %s %s %s %s %s %s %s %s\n",pContext->Eip, pContext->Esp, pContext->Ebp,(pContext->EFlags >> 12) & 3,                  //  IOPL level valuepContext->EFlags & 0x00100000 ? "vip" : "   ", //  VIP (virtual interrupt pending)pContext->EFlags & 0x00080000 ? "vif" : "   ", //  VIF (virtual interrupt flag)pContext->EFlags & 0x00000800 ? "ov" : "nv",   //  VIF (virtual interrupt flag)pContext->EFlags & 0x00000400 ? "dn" : "up",   //  OF (overflow flag)pContext->EFlags & 0x00000200 ? "ei" : "di",   //  IF (interrupt enable flag)pContext->EFlags & 0x00000080 ? "ng" : "pl",   //  SF (sign flag)pContext->EFlags & 0x00000040 ? "zr" : "nz",   //  ZF (zero flag)pContext->EFlags & 0x00000010 ? "ac" : "na",   //  AF (aux carry flag)pContext->EFlags & 0x00000004 ? "po" : "pe",   //  PF (parity flag)pContext->EFlags & 0x00000001 ? "cy" : "nc"    //  CF (carry flag));}if (pContext->ContextFlags & CONTEXT_SEGMENTS) {writeLog("cs=%04lx  ss=%04lx  ds=%04lx  es=%04lx  fs=%04lx  gs=%04lx", pContext->SegCs,pContext->SegSs, pContext->SegDs, pContext->SegEs, pContext->SegFs,pContext->SegGs);if (pContext->ContextFlags & CONTEXT_CONTROL) {writeLog("             efl=%08lx", pContext->EFlags);}} else {if (pContext->ContextFlags & CONTEXT_CONTROL) {writeLog("                                                                       ""efl=%08lx",pContext->EFlags);}}writeLog("\n\n");
    }void dumpStack(HANDLE hProcess, HANDLE hThread, const CONTEXT *pContext)
    {DWORD MachineType;assert(pContext);STACKFRAME64 StackFrame;ZeroMemory(&StackFrame, sizeof StackFrame);BOOL bWow64 = FALSE;if (0) {       // if (HAVE_WIN64)IsWow64Process(hProcess, &bWow64);}if (bWow64) {const WOW64_CONTEXT *pWow64Context = reinterpret_cast<const WOW64_CONTEXT *>(pContext);// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)assert((pWow64Context->ContextFlags & WOW64_CONTEXT_FULL) == WOW64_CONTEXT_FULL);
    #ifdef _WIN64dumpContext(pWow64Context);
    #endifMachineType = IMAGE_FILE_MACHINE_I386;StackFrame.AddrPC.Offset = pWow64Context->Eip;StackFrame.AddrStack.Offset = pWow64Context->Esp;StackFrame.AddrFrame.Offset = pWow64Context->Ebp;} else {// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)assert((pContext->ContextFlags & CONTEXT_FULL) == CONTEXT_FULL);
    #ifndef _WIN64MachineType = IMAGE_FILE_MACHINE_I386;dumpContext(pContext);                      // 打印当时寄存器的信息StackFrame.AddrPC.Offset = pContext->Eip;StackFrame.AddrStack.Offset = pContext->Esp;StackFrame.AddrFrame.Offset = pContext->Ebp;
    #elseMachineType = IMAGE_FILE_MACHINE_AMD64;StackFrame.AddrPC.Offset = pContext->Rip;StackFrame.AddrStack.Offset = pContext->Rsp;StackFrame.AddrFrame.Offset = pContext->Rbp;
    #endif}StackFrame.AddrPC.Mode = AddrModeFlat;StackFrame.AddrStack.Mode = AddrModeFlat;StackFrame.AddrFrame.Mode = AddrModeFlat;/** StackWalk64 modifies Context, so pass a copy.*/CONTEXT Context = *pContext;if (MachineType == IMAGE_FILE_MACHINE_I386) {writeLog("AddrPC   Params\n");} else {writeLog("AddrPC           Params\n");}BOOL bInsideWine = []() -> BOOL{HMODULE hNtDll = GetModuleHandleA("ntdll");if (!hNtDll) {return FALSE;}return GetProcAddress(hNtDll, "wine_get_version") != NULL;}();DWORD64 PrevFrameStackOffset = StackFrame.AddrStack.Offset - 1;int nudge = 0;while (TRUE) {constexpr int MAX_SYM_NAME_SIZE = 512;char szSymName[MAX_SYM_NAME_SIZE] = "";char szFileName[MAX_PATH] = "";DWORD dwLineNumber = 0;if (!StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context,NULL, // ReadMemoryRoutineSymFunctionTableAccess64, SymGetModuleBase64,NULL // TranslateAddress))break;if (MachineType == IMAGE_FILE_MACHINE_I386) {writeLog("%08lX %08lX %08lX %08lX", (DWORD)StackFrame.AddrPC.Offset,(DWORD)StackFrame.Params[0], (DWORD)StackFrame.Params[1],(DWORD)StackFrame.Params[2]);} else {writeLog("%016I64X %016I64X %016I64X %016I64X", StackFrame.AddrPC.Offset,StackFrame.Params[0], StackFrame.Params[1], StackFrame.Params[2]);}BOOL bSymbol = TRUE;BOOL bLine = FALSE;DWORD dwOffsetFromSymbol = 0;DWORD64 AddrPC = StackFrame.AddrPC.Offset;HMODULE hModule = (HMODULE)(INT_PTR)SymGetModuleBase64(hProcess, AddrPC);char szModule[MAX_PATH];if (hModule && GetModuleFileNameExA(hProcess, hModule, szModule, MAX_PATH)) {writeLog("  %s", getBaseName(szModule));bSymbol = GetSymFromAddr(hProcess, AddrPC + nudge, szSymName, MAX_SYM_NAME_SIZE,&dwOffsetFromSymbol);if (bSymbol) {writeLog("!%s+0x%lx", szSymName, dwOffsetFromSymbol - nudge);bLine =GetLineFromAddr(hProcess, AddrPC + nudge, szFileName, MAX_PATH, &dwLineNumber);if (bLine) {writeLog("  [%s @ %ld]", szFileName, dwLineNumber);}} else {writeLog("!0x%I64x", AddrPC - (DWORD64)(INT_PTR)hModule);}}writeLog("\n");if (bLine) {dumpSourceCode(szFileName, dwLineNumber);}// Basic sanity check to make sure  the frame is OK.  Bail if not.if (StackFrame.AddrStack.Offset <= PrevFrameStackOffset ||StackFrame.AddrPC.Offset == 0xBAADF00D) {break;}PrevFrameStackOffset = StackFrame.AddrStack.Offset;// Wine's StackWalk64 implementation on certain yield never ending// stack backtraces unless one bails out when AddrFrame is zero.if (bInsideWine && StackFrame.AddrFrame.Offset == 0) {break;}/** When we walk into the callers, StackFrame.AddrPC.Offset will not* contain the calling function's address, but rather the return* address.  This could be the next statement, or sometimes (for* no-return functions) a completely different function, so nudge the* address by one byte to ensure we get the information about the* calling statement itself.*/nudge = -1;}writeLog("\n");
    }void dumpException(HANDLE hProcess, PEXCEPTION_RECORD pExceptionRecord)
    {NTSTATUS ExceptionCode = pExceptionRecord->ExceptionCode;char szModule[MAX_PATH];LPCSTR lpcszProcess;HMODULE hModule;if (GetModuleFileNameExA(hProcess, NULL, szModule, MAX_PATH)){lpcszProcess = getBaseName(szModule);}else{lpcszProcess = "Application";}// First print information about the type of faultwriteLog("%s caused", lpcszProcess);LPCSTR lpcszException = getExceptionString(ExceptionCode);if (lpcszException) {LPCSTR lpszArticle;switch (lpcszException[0]) {case 'A':case 'E':case 'I':case 'O':case 'U':lpszArticle = "an";break;default:lpszArticle = "a";break;}writeLog(" %s %s", lpszArticle, lpcszException);} else {writeLog(" an Unknown [0x%lX] Exception", ExceptionCode);}// Now print information about where the fault occurredwriteLog(" at location %p", pExceptionRecord->ExceptionAddress);if((hModule = (HMODULE)(INT_PTR)SymGetModuleBase64(hProcess, (DWORD64)(INT_PTR)pExceptionRecord->ExceptionAddress)) &&GetModuleFileNameExA(hProcess, hModule, szModule, sizeof szModule)){writeLog(" in module %s", getBaseName(szModule));}// If the exception was an access violation, print out some additional information, to the error// log and the debugger.// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363082%28v=vs.85%29.aspxif ((ExceptionCode == EXCEPTION_ACCESS_VIOLATION || ExceptionCode == EXCEPTION_IN_PAGE_ERROR) &&pExceptionRecord->NumberParameters >= 2){LPCSTR lpszVerb;switch (pExceptionRecord->ExceptionInformation[0]) {case 0:lpszVerb = "Reading from";break;case 1:lpszVerb = "Writing to";break;case 8:lpszVerb = "DEP violation at";break;default:lpszVerb = "Accessing";break;}writeLog(" %s location %p", lpszVerb, (PVOID)pExceptionRecord->ExceptionInformation[1]);}writeLog(".\n\n");
    }void dumpModules(HANDLE hProcess)
    {HANDLE hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetProcessId(hProcess));if (hModuleSnap == INVALID_HANDLE_VALUE) {return;}DWORD MachineType;
    #ifdef _WIN64BOOL bWow64 = FALSE;IsWow64Process(hProcess, &bWow64);if (bWow64) {MachineType = IMAGE_FILE_MACHINE_I386;} else {
    #else{
    #endif
    #ifndef _WIN64MachineType = IMAGE_FILE_MACHINE_I386;
    #elseMachineType = IMAGE_FILE_MACHINE_AMD64;
    #endif}MODULEENTRY32 me32;me32.dwSize = sizeof me32;if (Module32First(hModuleSnap, &me32)) {do {if (MachineType == IMAGE_FILE_MACHINE_I386) {writeLog("%08lX-%08lX ",(DWORD)(DWORD64)me32.modBaseAddr,(DWORD)(DWORD64)me32.modBaseAddr + me32.modBaseSize);} else {writeLog("%016I64X-%016I64X ",(DWORD64)me32.modBaseAddr,(DWORD64)me32.modBaseAddr + me32.modBaseSize);}char *ptr = nullptr;long len = WideCharToMultiByte(CP_ACP, 0, me32.szExePath, -1, NULL, 0, NULL, NULL);WideCharToMultiByte(CP_ACP, 0, me32.szExePath, -1, ptr, len + 1, NULL, NULL);const char *szBaseName = getBaseName(ptr);DWORD dwVInfo[4];if (getModuleVersionInfo(ptr, dwVInfo)) {writeLog("%-12s\t%lu.%lu.%lu.%lu\n", szBaseName, dwVInfo[0], dwVInfo[1], dwVInfo[2],dwVInfo[3]);} else {writeLog("%s\n", szBaseName);}} while (Module32Next(hModuleSnap, &me32));writeLog("\n");}CloseHandle(hModuleSnap);
    }#endif // FORMATOUTPUT_H
    
  • util.h 源代码如下:
    // util.h
    #ifndef UTIL_H
    #define UTIL_H#include <windows.h>
    #include <imagehlp.h>const char *getSeparator(const char *szFilename)
    {const char *p, *q;p = NULL;q = szFilename;char c;do {c = *q++;if (c == '\\' || c == '/' || c == ':'){p = q;}} while (c);return p;
    }const char *getBaseName(const char *szFilename)
    {const char *pSeparator = getSeparator(szFilename);if (!pSeparator){return szFilename;}return pSeparator;
    }BOOL GetSymFromAddr(HANDLE hProcess,DWORD64 dwAddress,LPSTR lpSymName,DWORD nSize,LPDWORD lpdwDisplacement)
    {PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)malloc(sizeof(SYMBOL_INFO) + nSize * sizeof(char));DWORD64 dwDisplacement =0; // Displacement of the input address, relative to the start of the symbolBOOL bRet;pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);pSymbol->MaxNameLen = nSize;DWORD dwOptions = SymGetOptions();bRet = SymFromAddr(hProcess, dwAddress, &dwDisplacement, pSymbol);if (bRet) {// Demangle if not done alreadyif ((dwOptions & SYMOPT_UNDNAME) ||UnDecorateSymbolName(pSymbol->Name, lpSymName, nSize, UNDNAME_NAME_ONLY) == 0) {strncpy(lpSymName, pSymbol->Name, nSize);}if (lpdwDisplacement) {*lpdwDisplacement = dwDisplacement;}}free(pSymbol);return bRet;
    }BOOL GetLineFromAddr(HANDLE hProcess,DWORD64 dwAddress,LPSTR lpFileName,DWORD nSize,LPDWORD lpLineNumber)
    {IMAGEHLP_LINE64 Line;DWORD dwDisplacement =0; // Displacement of the input address, relative to the start of the symbol// Do the source and line lookup.memset(&Line, 0, sizeof Line);Line.SizeOfStruct = sizeof Line;if (!SymGetLineFromAddr64(hProcess, dwAddress, &dwDisplacement, &Line))return FALSE;assert(lpFileName && lpLineNumber);strncpy(lpFileName, Line.FileName, nSize);*lpLineNumber = Line.LineNumber;return TRUE;
    }BOOL getModuleVersionInfo(LPCSTR szModule, DWORD *dwVInfo)
    {DWORD dummy, size;BOOL success = FALSE;size = GetFileVersionInfoSizeA(szModule, &dummy);if (size > 0) {LPVOID pVer = malloc(size);ZeroMemory(pVer, size);if (GetFileVersionInfoA(szModule, 0, size, pVer)) {VS_FIXEDFILEINFO *ffi;if (VerQueryValueA(pVer, "\\", (LPVOID *)&ffi, (UINT *)&dummy)) {dwVInfo[0] = ffi->dwFileVersionMS >> 16;dwVInfo[1] = ffi->dwFileVersionMS & 0xFFFF;dwVInfo[2] = ffi->dwFileVersionLS >> 16;dwVInfo[3] = ffi->dwFileVersionLS & 0xFFFF;success = TRUE;}}free(pVer);}return success;
    }#ifndef STATUS_FATAL_USER_CALLBACK_EXCEPTION
    #define STATUS_FATAL_USER_CALLBACK_EXCEPTION ((NTSTATUS)0xC000041DL)
    #endif
    #ifndef STATUS_CPP_EH_EXCEPTION
    #define STATUS_CPP_EH_EXCEPTION ((NTSTATUS)0xE06D7363L)
    #endif
    #ifndef STATUS_CLR_EXCEPTION
    #define STATUS_CLR_EXCEPTION ((NTSTATUS)0xE0434f4DL)
    #endif
    #ifndef STATUS_WX86_BREAKPOINT
    #define STATUS_WX86_BREAKPOINT ((NTSTATUS)0x4000001FL)
    #endif
    #ifndef STATUS_POSSIBLE_DEADLOCK
    #define STATUS_POSSIBLE_DEADLOCK ((DWORD)0xC0000194L)
    #endifLPCSTR getExceptionString(NTSTATUS ExceptionCode)
    {switch (ExceptionCode) {case EXCEPTION_ACCESS_VIOLATION: // 0xC0000005return "Access Violation";case EXCEPTION_IN_PAGE_ERROR: // 0xC0000006return "In Page Error";case EXCEPTION_INVALID_HANDLE: // 0xC0000008return "Invalid Handle";case EXCEPTION_ILLEGAL_INSTRUCTION: // 0xC000001Dreturn "Illegal Instruction";case EXCEPTION_NONCONTINUABLE_EXCEPTION: // 0xC0000025return "Cannot Continue";case EXCEPTION_INVALID_DISPOSITION: // 0xC0000026return "Invalid Disposition";case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: // 0xC000008Creturn "Array bounds exceeded";case EXCEPTION_FLT_DENORMAL_OPERAND: // 0xC000008Dreturn "Floating-point denormal operand";case EXCEPTION_FLT_DIVIDE_BY_ZERO: // 0xC000008Ereturn "Floating-point division by zero";case EXCEPTION_FLT_INEXACT_RESULT: // 0xC000008Freturn "Floating-point inexact result";case EXCEPTION_FLT_INVALID_OPERATION: // 0xC0000090return "Floating-point invalid operation";case EXCEPTION_FLT_OVERFLOW: // 0xC0000091return "Floating-point overflow";case EXCEPTION_FLT_STACK_CHECK: // 0xC0000092return "Floating-point stack check";case EXCEPTION_FLT_UNDERFLOW: // 0xC0000093return "Floating-point underflow";case EXCEPTION_INT_DIVIDE_BY_ZERO: // 0xC0000094return "Integer division by zero";case EXCEPTION_INT_OVERFLOW: // 0xC0000095return "Integer overflow";case EXCEPTION_PRIV_INSTRUCTION: // 0xC0000096return "Privileged instruction";case EXCEPTION_STACK_OVERFLOW: // 0xC00000FDreturn "Stack Overflow";case EXCEPTION_POSSIBLE_DEADLOCK: // 0xC0000194return "Possible deadlock condition";case STATUS_FATAL_USER_CALLBACK_EXCEPTION: // 0xC000041Dreturn "Fatal User Callback Exception";case STATUS_ASSERTION_FAILURE: // 0xC0000420return "Assertion failure";case STATUS_CLR_EXCEPTION: // 0xE0434f4Dreturn "CLR exception";case STATUS_CPP_EH_EXCEPTION: // 0xE06D7363return "C++ exception handling exception";case EXCEPTION_GUARD_PAGE: // 0x80000001return "Guard Page Exception";case EXCEPTION_DATATYPE_MISALIGNMENT: // 0x80000002return "Alignment Fault";case EXCEPTION_BREAKPOINT: // 0x80000003return "Breakpoint";case EXCEPTION_SINGLE_STEP: // 0x80000004return "Single Step";case STATUS_WX86_BREAKPOINT: // 0x4000001Freturn "Breakpoint";case DBG_TERMINATE_THREAD: // 0x40010003return "Terminate Thread";case DBG_TERMINATE_PROCESS: // 0x40010004return "Terminate Process";case DBG_CONTROL_C: // 0x40010005return "Control+C";case DBG_CONTROL_BREAK: // 0x40010008return "Control+Break";case 0x406D1388:return "Thread Name Exception";case RPC_S_UNKNOWN_IF:return "Unknown Interface";case RPC_S_SERVER_UNAVAILABLE:return "Server Unavailable";default:return NULL;}
    }#endif // UTIL_H
    
  • log.h 源代码如下:
    // log.h
    #ifndef LOG_H
    #define LOG_H#include <windows.h>static HANDLE g_handleCrashFile = nullptr;int writeLog(const char *format, ...)
    {char szBuffer[1024];int retValue;va_list ap;va_start(ap, format);retValue = _vsnprintf(szBuffer, sizeof szBuffer, format, ap);va_end(ap);DWORD cbWritten;const char *szText = szBuffer;while (*szText != '\0') {const char *p = szText;while (*p != '\0' && *p != '\n') {++p;}WriteFile(g_handleCrashFile, szText, p - szText, &cbWritten, 0);if (*p == '\n') {WriteFile(g_handleCrashFile, "\r\n", 2, &cbWritten, 0);++p;}szText = p;};return retValue;
    }#endif // LOG_H
    
  • dllexport.h 源代码如下:
    // dllexport.h
    #ifndef HEADERDEF_H
    #define HEADERDEF_H#ifdef __cplusplus
    #define D_EXTERN_C extern "C"
    #else
    #define D_EXTERN_C
    #endif#ifdef __SHARE_EXPORT
    #define D_SHARE_EXPORT D_DECL_EXPORT
    #else
    #define D_SHARE_EXPORT D_DECL_IMPORT
    #endif#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__)
    #define D_CALLTYPE __stdcall
    #define D_DECL_EXPORT __declspec(dllexport)
    #define D_DECL_IMPORT
    #else
    #define D_CALLTYPE
    #define D_DECL_EXPORT __attribute__((visibility("default")))
    #define D_DECL_IMPORT __attribute__((visibility("default")))
    #endifD_EXTERN_C D_SHARE_EXPORT void D_CALLTYPE ExcHndlInit(const char *pCrashFileName);#endif // HEADERDEF_H
    

测试工具 DrMingwDemo

  • 工程文件 DrMingwDemo.pro 源代码如下:

    QT += core
    QT -= guiCONFIG += c++11TARGET = DrMingwDemo
    CONFIG += console
    CONFIG -= app_bundleTEMPLATE = appDESTDIR = $$PWD/binLIBS += -L$$PWD/lib -lMiniDrMingwINCLUDEPATH += $$PWD/lib/include# gcc 编译选项,生成 .map 文件
    QMAKE_LFLAGS += -Wl,-Map=$$PWD'/bin/'$$TARGET'.map'SOURCES += main.cppQMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
    QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFOHEADERS +=
    
  • main.cpp 源代码如下:
    #include <QCoreApplication>
    #include <QThread>extern "C" void ExcHndlInit(const char *pCrashFileName);class TestClass : public QThread
    {
    public:void run() override{int* a = (int*)(NULL);*a = 1;}
    };int main(int argc, char *argv[])
    {QCoreApplication a(argc, argv);ExcHndlInit((a.applicationDirPath() +"/" + a.applicationName() + ".RPT").toLocal8Bit().data());// 制造崩溃TestClass instance;instance.start();return a.exec();
    }
    

崩溃分析

  • DrMingwDemo.exe 编译后,会在工程的 bin 目录下生成 DrMingwDemo.map。
  • 双击运行 DrMingwDemo.exe,然后闪退,此时在程序的运行目录,产生了 DrMingwDemo.RPT。
  • 打开 DrMingwDemo.RPT 文件,内容大概如下:
    -------------------Error occurred on Friday, September 9, 2022 at 20:54:00.DrMingwDemo.exe caused an Access Violation at location 004028A0 in module DrMingwDemo.exe Writing to location 00000000.Registers:
    eax=004042f4 ebx=0065fe78 ecx=0065fe78 edx=00000001 esi=0108b4a0 edi=0108b468
    eip=004028a0 esp=02c1fefc ebp=02c1ff28 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206AddrPC   Params
    004028A0 7606689C 76066CFF 0065FE78  DrMingwDemo.exe!0x28a0
    76066CFF 02C1FF80 769DFA29 0108C610  msvcrt.dll!_beginthreadex+0xcf
    76066DC1 0108C610 769DFA10 02C1FFDC  msvcrt.dll!_endthreadex+0x91
    769DFA29 0108C610 4F76EDC1 00000000  KERNEL32.DLL!BaseThreadInitThunk+0x19
    771A7A9E FFFFFFFF 771C8BAC 00000000  ntdll.dll!RtlGetAppContainerNamedObjectPath+0x11e
    771A7A6E 76066D60 0108C610 00000000  ntdll.dll!RtlGetAppContainerNamedObjectPath+0xee00400000-00451000
    
  • 由上面可以可以看出是 004028A0 这个地址出现了崩溃。此时,我们打开 DrMingwDemo.map 文件,找到离该地址最近的函数(一般函数地址会小于等于崩溃地址)。
  • DrMingwDemo.map 的部分内容如下:
    ...*(SORT(.text$*))*fill*         0x00402818        0x8 .text$_ZN10QByteArrayD1Ev0x00402820       0x40 release/main.o0x00402820                QByteArray::~QByteArray().text$_ZN7QStringD1Ev0x00402860       0x40 release/main.o0x00402860                QString::~QString().text$_ZN9TestClass3runEv0x004028a0       0x10 release/main.o0x004028a0                TestClass::run().text$_ZN9TestClassD0Ev0x004028b0       0x20 release/main.o0x004028b0                TestClass::~TestClass().text$_ZN9TestClassD1Ev0x004028d0       0x10 release/main.o0x004028d0                TestClass::~TestClass().text$_ZplRK7QStringPKc0x004028e0       0xc0 release/main.o0x004028e0                operator+(QString const&, char const*)
    ...
    
  • 因此,可以看出是 TestClass::run() 函数发生了崩溃。
  • 注意,该方法只能定位出哪个函数出现了崩溃,却难以定位出是哪行导致的崩溃,就算在项目工程中加入了 Debug 信息也如此。

广告时间:我的 GitHub (希望给个star)

MiniDrMingw

【C++】定位程序崩溃(Mingw)相关推荐

  1. 使用MAP文件快速定位程序崩溃代码行

    作为程序员,平时最担心见到的事情就是程序发生了崩溃,无论是指针越界还是非法操作,都将给我们的应用系统 造成巨大的损失.但在一个大型系统的测试过程中,初期出现程序崩溃似乎成了不可避免的事.其实测试中出现 ...

  2. 使用MAP文件快速定位程序崩溃代码行(转)

    作为程序员,平时最担心见到的事情就是程序发生了崩溃,无论是指针越界还是非法操作,都将给我们的应用系统造成巨大的损失.但在一个大型系统的测试过程中,初期出现程序崩溃似乎成了不可避免的事.其实测试中出现程 ...

  3. windbg结合IDA定位程序崩溃

    如何分析dump文件 打开windbg,把dump文件拖进windbg中,输入!analyze –v windbg找出出错模块svnets的基地址 通过IDA改变svnet模块基地址 IDA中的地址都 ...

  4. 软件崩溃时 将堆栈信息写入dump文件, 并使用VS2010定位程序崩溃位置

    Windows下有三种生成dump文件的方式: 1.通过任务管理器和注册表:2.WinDbg抓取:3.程序中加入存储Dump的代码 具体生成方法参看:Windows下dump文件生成与分析 本文详细介 ...

  5. 如何定位Release程序崩溃原因

    [转]如何定位Release程序崩溃原因 Posted on 2011-08-19 10:44 单鱼游弋 阅读(2162) 评论(1) 编辑 收藏 1       案例描述 作为Windows程序员, ...

  6. 【iOS】iOS 调试快速定位程序在哪崩溃

    iOS 开发过程中经常遇到程序崩溃.快速定位程序在哪崩溃的步骤如下: 1. 2. 3. 这样设置后,程序崩溃时会定位到崩溃的语句,如下: 原文链接:iOS开发何如在调试的时候轻松找到程序在哪里崩溃 转 ...

  7. 应用程序崩溃定位查找 (二)

    教程的第一部分介绍了 SIGABRT 和 EXC_BAD_ACCESS 的错误,并说明解决他们使用 Xcode 调试器和异常断点的一些策略. 但我们的应用程序仍然有一些问题!它不能完全按照它应该并且有 ...

  8. Go程序崩溃现场应该如何保留?

    Go 程序突然莫名崩溃后,当日志记录没有覆盖到错误场景时,还有别的方法排查吗? 没有消灭一切的银弹,也没有可以保证永不出错的程序.我们应当如何捕捉 Go 程序错误?我想同学们的第一反应是:打日志. 但 ...

  9. C++程序崩溃生成dump

    程序在运行时,难免会有一些异常情况发生,特别是在条件不容许去挂调试器的时候,如何快速的定位错误的方法就显得很重要. 日志一直都是一种很重要的定位错误的方法,出得好的日志可以方便程序员快速的定位问题所在 ...

最新文章

  1. 频谱仪使用方法图解_钳形电流表使用方法图解
  2. python与网页设计的区别_Python与设计模式(三):行为型模式(上)
  3. 100家店干翻17000家药店!刘强东最恐惧的对手来了!
  4. mysql如何大矩阵_如何打印矩阵
  5. python相关性分析特征过滤_Python相关性分析
  6. 【脏数据】什么是脏数据:脏数据的种类、类型
  7. 最小二乘法曲线拟合以及Matlab实现
  8. c语言中十进制转化二进制八进制十六进制,十进制转化为二进制八进制十六进制...
  9. 初识XUL用户界面UI开发
  10. 解决cannot find grldr in all devices问题
  11. 股价上涨,资金流出以及内外盘的关系
  12. Sharding-JDBC 源码之 SQL 改写
  13. 代码主题darcula_Intellij idea 中的Darcula主题怎么把颜色改回来?
  14. git 同时连接云效平台和github
  15. Unity Shader-真实下雨路面
  16. Jeesite 4.0 学习笔记
  17. 【理论课之配饰设计】色彩与视觉的原理
  18. linux下查看服务器的cpu、内存大小、硬盘大小
  19. OATS PK Pairwise Testing
  20. MySQL(管理)01 -- 用户User和权限Privileges<B.用户管理权限设置>

热门文章

  1. WIN7+64位+ndk配置
  2. 男人最喜欢听女人说的5句话
  3. 教你编程最实用的入门:python环境管理
  4. PTrade量化软件有哪些功能?支持Python吗?
  5. google adwods关键字工具
  6. 用 Java 写了一个类QQ界面聊天小项目,可在线聊天!
  7. 网络助手的NABCD分析
  8. 证券交易的量化接口的用途?
  9. PS滤镜安装方法、PhotoShop滤镜的安装方法
  10. CSS文字前面有不同颜色的圆