分析两种Dump(崩溃日志)文件生成的方法及比较 - 方亮的专栏 - 博客频道 - youkuaiyun.com
http://blog.youkuaiyun.com/breaksoftware/article/details/23134445
做windows产品开发的,永远绕不开一个问题——程序崩溃。如果希望不断提升产品质量,就得不停的收集和分析崩溃日志。但是我们会发现一个问题,我们经常采用的方案无法拦截崩溃。(转载请指明出于breaksoftware的csdn博客)比如会出现如下提示:
这是一个非常不好的体验,至少说这个是对提升软件质量无益的体验。虽然以上框可以通过如下代码禁用掉,但是仍然只是个掩耳盗铃的做法。
- SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
我们先看一种标准的Dump生成方案:
- #include "CreateDump.h"
- #include <atlbase.h>
- #include <atlstr.h>
- #include <strsafe.h>
- #include <DbgHelp.h>
- #pragma comment(lib,"DbgHelp.lib")
- #define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS (0x00000004)
- #define MiniDumpWithThreadInfo 0x1000
- typedef BOOL (WINAPI *PGetModuleHandleEx)( DWORD dwFlags, LPCTSTR lpModuleName, HMODULE *phModule );
- VOID CreateDump(struct _EXCEPTION_POINTERS *pExceptionPointers)
- {
- //收集信息
- CStringW strBuild;
- strBuild.Format(L"Build: %s %s", __DATE__, __TIME__);
- CString strError;
- HMODULE hModule;
- WCHAR szModuleName[MAX_PATH] = {0};
- PGetModuleHandleEx pFun = (PGetModuleHandleEx)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetModuleHandleExW");
- if ( !pFun ) {
- return;
- }
- pFun(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)pExceptionPointers->ExceptionRecord->ExceptionAddress, &hModule);
- GetModuleFileName(hModule, szModuleName, ARRAYSIZE(szModuleName));
- strError.Format(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"DumpFile";
- 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 = (MINIDUMP_TYPE)(MiniDumpNormal
- | MiniDumpWithHandleData
- | MiniDumpWithUnloadedModules
- | MiniDumpWithIndirectlyReferencedMemory
- | MiniDumpScanMemory
- | MiniDumpWithProcessThreadData
- | MiniDumpWithThreadInfo);
- bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
- hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);
- return;
- }
这个结构体自然不是我们自己构造的,而是系统给我们的。我们该从哪个接口接收系统给我们的该信息呢?
一般情况下,我们使用SetUnhandledExceptionFilter来设置一个回调函数。当软件即将崩溃时,我们设置的回调函数理论上会被调用。然而,实际并非如此。我们看一个报错的例子。
如果你也见过这个错误,我想你的截取dump方案应该是被绕过了。我专门查了一下该错误,MSDN上有相关例子
- #pragma once
- class A;
- void fcn( A* );
- class A
- {
- public:
- virtual void f() = 0;
- A() { fcn( this ); }
- };
- class B : A
- {
- void f() { }
- };
- void fcn( A* p )
- {
- p->f();
- }
- // The declaration below invokes class B's constructor, which
- // first calls class A's constructor, which calls fcn. Then
- // fcn calls A::f, which is a pure virtual function, and
- // this causes the run-time error. B has not been constructed
- // at this point, so the B::f cannot be called. You would not
- // want it to be called because it could depend on something
- // in B that has not been initialized yet.
- int PureVirtualFunc()
- {
- B b;
- return 0;
- }
我们构造一个SetUnhandledExceptionFilter可以截获dump的例子
- LONG WINAPI DumpCallback(_EXCEPTION_POINTERS* excp) {
- CreateDump(excp);
- return EXCEPTION_EXECUTE_HANDLER;
- }
- ……
- SetUnhandledExceptionFilter(DumpCallback);
- int *p = NULL;
- *p = 1;
可以见得,在调用我们回调函数之前,调用了系统的UnhandledExceptionFilter函数,这个函数的入参也是_EXCEPTION_POINTERS指针。
- LONG WINAPI UnhandledExceptionFilter(
- _In_ struct _EXCEPTION_POINTERS *ExceptionInfo
- );
- #include "AutoDump.h"
- #include <windows.h>
- #include "../detours/detours.h"
- #include "CreateDump.h"
- LONG WINAPI NewUnhandledExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ){
- OutputDebugString(L"NewUnhandledExceptionFilter\n");
- CreateDump(ExceptionInfo);
- return EXCEPTION_EXECUTE_HANDLER;
- }
- CAutoDump::CAutoDump(void)
- {
- m_lpUnhandledExceptionFilter = NULL;
- do {
- SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
- m_lpUnhandledExceptionFilter = DetourFindFunction( "KERNEL32.DLL", "UnhandledExceptionFilter" );
- if ( NULL == m_lpUnhandledExceptionFilter ) {
- break;
- }
- LONG lRes = NO_ERROR;
- lRes = DetourTransactionBegin();
- if ( NO_ERROR != lRes ) {
- break;
- }
- lRes = DetourAttach( &m_lpUnhandledExceptionFilter, NewUnhandledExceptionFilter );
- if ( NO_ERROR != lRes ) {
- break;
- }
- lRes = DetourTransactionCommit();
- if ( NO_ERROR != lRes ) {
- break;
- }
- } while (0);
- }
- CAutoDump::~CAutoDump(void)
- {
- if ( m_lpUnhandledExceptionFilter ) {
- do {
- LONG lRes = NO_ERROR;
- lRes = DetourTransactionBegin();
- if ( NO_ERROR != lRes ) {
- break;
- }
- lRes = DetourDetach( &m_lpUnhandledExceptionFilter, NewUnhandledExceptionFilter );
- if ( NO_ERROR != lRes ) {
- break;
- }
- lRes = DetourTransactionCommit();
- if ( NO_ERROR != lRes ) {
- break;
- }
- } while (0);
- }
- }
现在,我们开始分析,为什么SetUnhandledExceptionFilter无法截获这些CRT错误。从上面可以分析出,当出现异常时,流程会进入UnhandledExceptionFilter,但是我们设置的回调函数没被调用。那么可以猜测,应该是系统的UnhandledExceptionFilter函数内部走了其他的流程。我查看下UnhandledExceptionFilter函数的逆向结果,此时我不会将其列出来,因为我们要知道其内部是在哪儿调用了我们通过SetUnhandledExceptionFilter设置的回调函数。我们先看下SetUnhandledExceptionFilter的实现,用IDA查看的逆向结果比较杂乱,我就以ReactOS的代码作为例子来讲解,其核心思想是一致的
- LPTOP_LEVEL_EXCEPTION_FILTER
- WINAPI
- SetUnhandledExceptionFilter(IN LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
- {
- PVOID EncodedPointer, EncodedOldPointer;
- EncodedPointer = RtlEncodePointer(lpTopLevelExceptionFilter);
- EncodedOldPointer = InterlockedExchangePointer((PVOID*)&GlobalTopLevelExceptionFilter,
- EncodedPointer);
- return RtlDecodePointer(EncodedOldPointer);
- }
- ……
- RealFilter = RtlDecodePointer(GlobalTopLevelExceptionFilter);
- if (RealFilter)
- {
- LONG ret = RealFilter(ExceptionInfo);
- if (ret != EXCEPTION_CONTINUE_SEARCH)
- return ret;
- }
- ……
- 75BF76D3 mov dword ptr [ebp-20h],6
- 75BF76DA xor esi,esi
- 75BF76DC mov dword ptr [ebp-1Ch],esi
- 75BF76DF mov dword ptr [ebp-24h],esi
- 75BF76E2 mov dword ptr [ebp-28h],esi
- 75BF76E5 mov ebx,dword ptr [ebp+8]
- 75BF76E8 mov eax,dword ptr [ebx]
- 75BF76EA test byte ptr [eax+4],10h
- 75BF76EE jne _UnhandledExceptionFilter@4+29h (75BF7934h)
- 75BF76F4 mov dword ptr [ebp-2Ch],1
- 75BF76FB cmp dword ptr [eax],0C0000409h
- 75BF7701 je _UnhandledExceptionFilter@4+3Fh (75BF8146h)
- 75BF7707 push ebx
- 75BF7708 call _CheckForReadOnlyResourceFilter@4 (75BF78B9h)
- 75BF770D cmp eax,0FFFFFFFFh
- 75BF7710 je _UnhandledExceptionFilter@4+91h (75BF793Bh)
- 75BF7716 call _BasepIsDebugPortPresent@0 (75BF7831h)
- 75BF771B test eax,eax
- 75BF771D jne _UnhandledExceptionFilter@4+29h (75BF7934h)
- 75BF7723 mov esi,75CA030Ch
- 75BF7728 push esi
- 75BF7729 call dword ptr [__imp__RtlAcquireSRWLockExclusive@4 (75BD034Ch)]
- 75BF772F push dword ptr ds:[75CA0074h]
- 75BF7735 call dword ptr [__imp__RtlDecodePointer@4 (75BD0670h)]
- 75BF773B mov edi,eax
- 75BF773D test edi,edi
百度云下载地址:http://pan.baidu.com/s/1qWG14BE 。密码:w5o5