客户端程序发布之后,程序会出现突然崩溃的情况,客户机器上并不一样安装有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的截图。
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。