您的位置:首页 > 其它

分析两种Dump(崩溃日志)文件生成的方法及比较

2015-08-25 17:39 991 查看
分析两种Dump(崩溃日志)文件生成的方法及比较 - 方亮的专栏 - 博客频道 - CSDN.NET

/article/1826046.html

做windows产品开发的,永远绕不开一个问题——程序崩溃。如果希望不断提升产品质量,就得不停的收集和分析崩溃日志。但是我们会发现一个问题,我们经常采用的方案无法拦截崩溃。(转载请指明出于breaksoftware的csdn博客)比如会出现如下提示:





这是一个非常不好的体验,至少说这个是对提升软件质量无益的体验。虽然以上框可以通过如下代码禁用掉,但是仍然只是个掩耳盗铃的做法。

[cpp] view
plaincopy

SetErrorMode(SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);

我们先看一种标准的Dump生成方案:

[cpp] view
plaincopy





#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;

}

可以见得,我们生成dump文件必须一个结构体——_EXCEPTION_POINTERS。

这个结构体自然不是我们自己构造的,而是系统给我们的。我们该从哪个接口接收系统给我们的该信息呢?

一般情况下,我们使用SetUnhandledExceptionFilter来设置一个回调函数。当软件即将崩溃时,我们设置的回调函数理论上会被调用。然而,实际并非如此。我们看一个报错的例子。



如果你也见过这个错误,我想你的截取dump方案应该是被绕过了。我专门查了一下该错误,MSDN上有相关例子

[cpp] view
plaincopy





#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。

我们构造一个SetUnhandledExceptionFilter可以截获dump的例子

[cpp] view
plaincopy





LONG WINAPI DumpCallback(_EXCEPTION_POINTERS* excp) {

CreateDump(excp);

return EXCEPTION_EXECUTE_HANDLER;

}

……

SetUnhandledExceptionFilter(DumpCallback);

int *p = NULL;

*p = 1;

我们查看调用堆栈



可以见得,在调用我们回调函数之前,调用了系统的UnhandledExceptionFilter函数,这个函数的入参也是_EXCEPTION_POINTERS指针。

[cpp] view
plaincopy





LONG WINAPI UnhandledExceptionFilter(

_In_ struct _EXCEPTION_POINTERS *ExceptionInfo

);

那么,我们可以猜测,如果我们可以接管该函数,可能可以让我们捕获R6025这样的异常。我使用detours库Hook了这个函数

[cpp] view
plaincopy





#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);

}

}

结果,这种方式,便可以截获R6025这样的CRT错误。

现在,我们开始分析,为什么SetUnhandledExceptionFilter无法截获这些CRT错误。从上面可以分析出,当出现异常时,流程会进入UnhandledExceptionFilter,但是我们设置的回调函数没被调用。那么可以猜测,应该是系统的UnhandledExceptionFilter函数内部走了其他的流程。我查看下UnhandledExceptionFilter函数的逆向结果,此时我不会将其列出来,因为我们要知道其内部是在哪儿调用了我们通过SetUnhandledExceptionFilter设置的回调函数。我们先看下SetUnhandledExceptionFilter的实现,用IDA查看的逆向结果比较杂乱,我就以ReactOS的代码作为例子来讲解,其核心思想是一致的

[cpp] view
plaincopy





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);

}

从上述代码中,我们可以见到,系统通过原子操作保存了我们设置的回调函数。然后在UnhandledExceptionFilter函数内部,是这样调用我们设置的回调函数的(依然以ReactOs为例)

[cpp] view
plaincopy





……

RealFilter = RtlDecodePointer(GlobalTopLevelExceptionFilter);

if (RealFilter)

{

LONG ret = RealFilter(ExceptionInfo);

if (ret != EXCEPTION_CONTINUE_SEARCH)

return ret;

}

……

找到这个锚点,我们便可以动态调试,找出回调函数没有被调用的原因。

[cpp] view
plaincopy





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

调试时,需要注意:当运行到75BF771D时,我们要将执行路径指向75BF7723。因为我们是debug状态,要跳过这个检测。然后我们继续执行,会发现75BF7735处执行的结果是0,即我们获取的回调函数执行为空。这样便分析出,为什么SetUnhandledExceptionFilter方法设置的回调没有被执行。但是一个新的问题又被抛了出来——何时这个回调被设置成空了?可以这样设计下:Hook函数NtQueryInformationProcess,使其返回调试端口号一直未0,。然后针对GlobalTopLevelExceptionFilter下硬件断点。或许,这样便可以找到元凶。

最后附上工程。

百度云下载地址:http://pan.baidu.com/s/1qWG14BE 。密码:w5o5
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: