程序异常崩溃捕捉-dmp文件及Windbg分析

时间:2022-04-13 06:42:40

客户端程序发布之后,程序会出现突然崩溃的情况,客户机器上并不一样安装有Windbg工具, 所以最好是自己程序里面能够捕捉exception/crash,并且生成crash dump,然后通过网络传回到自己服务器。

 

捕捉exception 可以用API 函数 SetUnhandledExceptionFilter 。生成crash dump 可以用DbgHelp.dll 里面的MiniDumpWriteDump函数。

 

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( __in LPTOP_LEVEL_EXCEPTION_FILTERlpTopLevelExceptionFilter );

 

BOOL WINAPI MiniDumpWriteDump( __in HANDLE hProcess, __in DWORDProcessId, __in HANDLEhFile, __in MINIDUMP_TYPEDumpType, __in PMINIDUMP_EXCEPTION_INFORMATIONExceptionParam, __in PMINIDUMP_USER_STREAM_INFORMATIONUserStreamParam, __in PMINIDUMP_CALLBACK_INFORMATIONCallbackParam );

 代码示例:

#include <dbghelp.h>  
    #include <shellapi.h>  
    #include <shlobj.h>  
      
      
    // 自定义的exectpion filter  
    LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pExceptionPointers)  
    {  
      
        SetErrorMode( SEM_NOGPFAULTERRORBOX );  
      
        //收集信息  
         CStringW strBuild;  
        strBuild.Format(L"Build: %s %s", __DATE__, __TIME__);  
        CStringW strError;  
        HMODULE hModule;  
        WCHAR szModuleName[MAX_PATH] = L"";  
        GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)pExceptionPointers->ExceptionRecord->ExceptionAddress, &hModule);  
        GetModuleFileName(hModule, szModuleName, ARRAYSIZE(szModuleName));  
        strError.AppenedFormat(L"%s %d , %d ,%d.", szModuleName,pExceptionPointers->ExceptionRecord->ExceptionCode,       pExceptionPointers->ExceptionRecord->ExceptionFlags, pExceptionPointers->ExceptionRecord->ExceptionAddress);  
      
        //生成 mini crash dump  
        BOOL bMiniDumpSuccessful;  
        WCHAR szPath[MAX_PATH];   
        WCHAR szFileName[MAX_PATH];   
        WCHAR* szAppName = L"AppName";  
        WCHAR* szVersion = L"v1.0";  
        DWORD dwBufferSize = MAX_PATH;  
        HANDLE hDumpFile;  
        SYSTEMTIME stLocalTime;  
        MINIDUMP_EXCEPTION_INFORMATION ExpParam;  
        GetLocalTime( &stLocalTime );  
        GetTempPath( dwBufferSize, szPath );  
        StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );  
        CreateDirectory( szFileName, NULL );  
        StringCchPrintf( szFileName, MAX_PATH, L"%s%s//%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",   
                   szPath, szAppName, szVersion,   
                   stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,   
                   stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,   
                   GetCurrentProcessId(), GetCurrentThreadId());  
        hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,   
                    FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);  
      
        MINIDUMP_USER_STREAM UserStream[2];  
        MINIDUMP_USER_STREAM_INFORMATION UserInfo;  
        UserInfo.UserStreamCount = 1;  
        UserInfo.UserStreamArray = UserStream;  
        UserStream[0].Type = CommentStreamW;  
        UserStream[0].BufferSize = strBuild.GetLength()*sizeof(WCHAR);  
        UserStream[0].Buffer = strBuild.GetBuffer();  
        UserStream[1].Type = CommentStreamW;  
        UserStream[1].BufferSize = strError.GetLength()*sizeof(WCHAR);  
        UserStream[1].Buffer = strError.GetBuffer();  
      
        ExpParam.ThreadId = GetCurrentThreadId();  
        ExpParam.ExceptionPointers = pExceptionPointers;  
        ExpParam.ClientPointers = TRUE;  
          
        MINIDUMP_TYPE MiniDumpWithDataSegs = MiniDumpNormal   
                | MiniDumpWithHandleData   
                | MiniDumpWithUnloadedModules   
                | MiniDumpWithIndirectlyReferencedMemory   
                | MiniDumpScanMemory   
                | MiniDumpWithProcessThreadData   
                | MiniDumpWithThreadInfo;  
        bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),   
                        hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);  
      // 上传mini dump 到自己服务器(略)  
      ...  
      
      return EXCEPTION_CONTINUE_SEARCH; //或者 EXCEPTION_EXECUTE_HANDLER 关闭程序  
    }  
       
    int _tmain()  
    {  
      // 设置 execption filter  
      SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);  
      ....  
      return 0;  
    }
 

在程序异常生成dmp文件后,我们需要使用该文件对应的pdb文件来定位程序崩溃的源文件:

建立PDB文件基本上是这几个选项,a)在project setting的C++属性中,选择生成program database,或者直接手动加入/Zi选项,如果有/Z7,把它替换成/Zi。b)在link选项中选择Generate debug info,或者直接加入/debug选项,另外注意/pdb应该是类似/PDB:".\Release/yourproj.pdb"这样的,如果不是手动修改。

有的人会担心包含debug信息以后文件变大,修改link中这两个选项/OPT:REF和/OPT:ICF会减小最终生成的文件大小。在这里借用一下John Robbins的截图。

程序异常崩溃捕捉-dmp文件及Windbg分析



windbg 调试崩溃
1、程序崩溃发生过程
这是一个对文件进行处理的模块,而处理模块在处理之前,需查询被处理的文件是否值得处理。这个任务执行过程中发生了崩溃,问题就发生在查询模块。
2、提取dump文件
3、分析dump:
1)启动windbg,file--open crash dump 配置符号库,reload完成 。
2)使用命令 :.ecxr获得进程崩溃时寄存器的内容
0:021> .ecxr (意指恢复崩溃时所有寄存器的内容,包括堆栈等)
eax=0532d414 ebx=00000fec ecx=000003fb edx=00000000 esi=0532c428 edi=00000000
eip=750f53ea esp=0490f22c ebp=0490f234 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
msvcr80!memmove+0x5a:
750f53ea f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:002b:00000000=???????? ds:002b:0532c428=00000000

3)使用命令:k 显示堆栈
0:021> k (.ecxr不能直接显示堆栈,需使用k显示堆栈)
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0490f234 03ba4806 msvcr80!memmove+0x5a(kxewfsys调用memmove,memmove是个字符串操作函数,能把字符串的一部分复制到另一部分,这里出问题,可能是复制时传递的指针有问题,或者字符串的大小有问题)
0490f25c 03ba2547 kxewfsys!__ovfl_get+0xa6 (调用memmove之前内部的一些处理)
0490f27c 03ba4a74 kxewfsys!__bt_cmp+0x77
0490f2a8 03ba2679 kxewfsys!__bt_search+0x74
0490f2c4 03ba1409 kxewfsys!__bt_get+0x49
0490f2d8 03ba7448 kxewfsys!IKBDBImpl::Get+0x19
0490f2f8 03ba750d kxewfsys!CFdbFileInfo::Search+0x28 [e:eingsoft_eubauilduild_srckicekice_kxewhitesrckxewfssysfdbfileinfo.cpp @ 483]
0490f338 03ba9c63 kxewfsys!CFdbFileInfo::QueryFileInfo+0x4d [e:eingsoft_eubauilduild_srckicekice_kxewhitesrckxewfssysfdbfileinfo.cpp @ 546]
0490f380 028210d1 kxewfsys!CFdbManager::QueryFileInfo+0xa3 [e:eingsoft_eubauilduild_srckicekice_kxewhitesrckxewfssysfdbmanager.cpp @ 370] (kxewfsys 是处理查询模块)
*** ERROR: Symbol file could not be found. Defaulted to export symbols for kspfeng.dll -
0490f390 03b1323c kxewhite!kxe_white_query_file_info+0x21 [e:eingsoft_eubauilduild_srckicekice_kxewhitesrckxewfssdkkxewfs.cpp @ 103] (将文件提交进行查询.)
0490f45c 03b13442 kspfeng!KSEGetAddonEntries+0x21fbc (kspfeng.dll是文件处理模块用到的公共功能的封装文件)
*** ERROR: Symbol file could not be found. Defaulted to export symbols for ksecore.dll -
0490f498 03abcaa4 kspfeng!KSEGetAddonEntries+0x221c2
0490f4c8 03b07fd5 ksecore+0x1caa4
0490f52c 03b1c52b kspfeng!KSEGetAddonEntries+0x16d55
0490f860 5019dd7c kspfeng!KSEGetAddonEntries+0x2b2ab
0490f864 01c95695 0x5019dd7c
0490f868 1a77217c 0x1c95695
0490f86c 01c95693 0x1a77217c
0490f870 1a7982dc 0x1c95693
0490f874 01c95693 0x1a7982dc
4、总结崩溃原因
查询模块,对文件路径的处理存在bug。