弹出系统异常提示框的崩溃原因分析
2017-08-05 12:43
901 查看
前导分析
当程序执行崩溃的时候,如果程序并没有设置任何崩溃捕获钩子时,操作系统会弹出提示框,提醒用户是否调试或是关闭程序,如果这个时候我们使用任务管理器或者外部第三方程序创建了一个崩溃程序的dump(在win7以上系统的资源管理器的进程列表中,右键单击目标进程,在弹出的右键的菜单中创建转存文件,即导出了dump文件),那么我们该如何分析呢。调试分析
准备
尽可能的准备好dump,二进制文件,以及pdb或者是map文件。加载dump到windbg中,设置调试符号的路径。我们首先看看主线程的堆栈示意,因为我们的模块基本位于主线程中运行,而底层库会运行在工作线程中,我们先查看一下自己模块的工作情况。
0:000> kn # ChildEBP RetAddr 00 0012e5c8 77946a04 ntdll!KiFastSystemCallRet 01 0012e5cc 75d069dc ntdll!NtWaitForMultipleObjects+0xc 02 0012e668 7601bc8e KERNELBASE!WaitForMultipleObjectsEx+0x100 03 0012e6b0 7601bcfc kernel32!WaitForMultipleObjectsExImplementation+0xe0 04 0012e6cc 7602f165 kernel32!WaitForMultipleObjects+0x18 05 0012e738 7602f202 kernel32!WerpReportFaultInternal+0x186 06 0012e74c 7602ef30 kernel32!WerpReportFault+0x70 07 0012e75c 7602eeaa kernel32!BasepReportFault+0x20 08 0012e7e8 102143dd kernel32!UnhandledExceptionFilter+0x1af 09 0012e80c 00547077 MSVCRTD!_XcptFilter+0x3d 0a 0012ff88 76023c45 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345] 0b 0012ff94 779637f5 kernel32!BaseThreadInitThunk+0xe 0c 0012ffd4 779637c8 ntdll!__RtlUserThreadStart+0x70 0d 0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
这里隐去了重要模块的模块名,我们且看一下堆栈示意情况。
简要分析一下堆栈,从堆栈中我们暂时无法发现崩溃的位置,只有我们自己模块的代码启动函数,这并不能确认问题,代码运行主模块的时候发生了异常,异常经由系统派发,系统并没有找到可处理的SEH处理程序,进而派发到线程最后一个异常处理,这个处理就是未处理异常的过滤函数,UnhandledExceptionFilter,一般这个函数会判断用户是否设置了未处理异常过滤函数,自然我们的模块中并没有设置异常捕获,接着UnhandledExceptionFilter开始调用错误提示框显示错误给用户,并等待用户的操作。然而这里有个比较重要的信息,虽然我们无法立即定位到崩溃位置,但是我们可以知道崩溃的信息,我尝试下面的方法。
0:000> .ecxr Minidump doesn't have an exception context Unable to get exception context, HRESULT 0x80004002
于是乎,我使用.ecxr命令看看能不能显示出异常信息的详细情况,却事与愿违,由于我们使用的任务管理器创建的dump,这种情况并不会记录特别的异常指针或堆栈信息到异常上下文中,情况要比看起来略微的复杂。
异常函数参数定位
UnhandledExceptionFilter
既然我们知道了UnhandledExceptionFilter在当前堆栈上被调用了,通过参考reactos的代码,我们会知道,这个函数带有异常信息,我们能不能通过这个异常信息了解到相关崩溃的位置呢?可以试试。LONG WINAPI UnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo) { ...... } typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;
我们参考ReactOs的代码会发现,这个函数的参数是一个指针,指向一个EXCEPTION_POINTERS,其中一个重要的结构时PEXCEPTION_RECORD,内部数据中含有ExceptionAddress,我们需要找到的就是这个异常地址,这个地址是实际的崩溃位置。
我们回到dump分析中,我们要显示出函数的参数信息,回到之前的堆栈,我们会寻找UnhandledExceptionFilter,所对应的栈基址为0012e7e8。我们打印出参数信息
0:000> dds 0012e7e8 L5 0012e7e8 0012e80c //保存上一函数基地址 0012e7ec 102143dd MSVCRTD!_XcptFilter+0x3d //保存函数返回地址 0012e7f0 00000000 //第一个参数 0012e7f4 00000000 0012e7f8 0012e844
然而比较惨的是这个函数并没有传递参数信息。
_XcptFilter
我们可以顺这个思路去查找另外一个函数_XcptFilter,这个函数是UnhandledExceptionFilter的父函数。我们查看一下这个函数的代码,以确认参数的类型。int __cdecl _XcptFilter ( unsigned long xcptnum, PEXCEPTION_POINTERS pxcptinfoptrs ) { ...... }发现这个函数的第二个参数信息同样是PEXCEPTION_POINTERS,我们可以查看一下这个函数。换个获取参数的方法,使用kv。
0:000> kv # ChildEBP RetAddr Args to Child 00 0012e5c8 77946a04 75d069dc 00000002 0012e61c ntdll!KiFastSystemCallRet (FPO: [0,0,0]) 01 0012e5cc 75d069dc 00000002 0012e61c 00000001 ntdll!NtWaitForMultipleObjects+0xc (FPO: [5,0,0]) 02 0012e668 7601bc8e 0012e61c 0012e690 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100 (FPO: [Non-Fpo]) 03 0012e6b0 7601bcfc 00000002 7ffd7000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0 (FPO: [Non-Fpo]) 04 0012e6cc 7602f165 00000002 0012e700 00000000 kernel32!WaitForMultipleObjects+0x18 (FPO: [Non-Fpo]) 05 0012e738 7602f202 0012e838 00000001 00000001 kernel32!WerpReportFaultInternal+0x186 (FPO: [Non-Fpo]) 06 0012e74c 7602ef30 0012e838 00000001 0012e7e8 kernel32!WerpReportFault+0x70 (FPO: [Non-Fpo]) 07 0012e75c 7602eeaa 0012e838 00000001 e479dbd8 kernel32!BasepReportFault+0x20 (FPO: [Non-Fpo]) 08 0012e7e8 102143dd 00000000 00000000 0012e844 kernel32!UnhandledExceptionFilter+0x1af (FPO: [Non-Fpo]) 09 0012e80c 00547077 c0000005 0012e838 10211673 MSVCRTD!_XcptFilter+0x3d 0a 0012ff88 76023c45 7ffd7000 0012ffd4 779637f5 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345] 0b 0012ff94 779637f5 7ffd7000 7614288e 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo]) 0c 0012ffd4 779637c8 00546ea0 7ffd7000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 0d 0012ffec 00000000 00546ea0 7ffd7000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])锁定_XcptFilter所在行,会发现,第一个参数是c0000005,这应该是一个异常的类型,而第二个参数是0012e838,我们找到了异常指针的地址。
0:000> dd 0012e838 0012e838 0012e92c 0012e948 0012e864 779471b9 0012e848 0012e92c 0012ff78 0012e948 0012e900 0012e858 0012ed90 779471cd 0012ff78 0012e914 0012e868 7794718b 0012e92c 0012ff78 0012e948 0012e878 0012e900 0054709a 00000000 0012e92c 0012e888 0012ff78 7791f96f 0012e92c 0012ff78 0012e898 0012e948 0012e900 0054709a 0012eca4 0012e8a8 0012e92c 0012ec48 0012e8dc 75d7c4e7 0:000> dd 0012e92c 0012e92c c0000005 00000000 00000000 5f42d84a 0012e93c 00000002 00000000 dddddddd 0001003f 0012e94c 00000000 00000000 00000000 00000000 0012e95c 00000000 00000000 0000027f 00004000 0012e96c 0000ffff 00000000 00000000 00000000 0012e97c 00000000 00000000 00000000 00000000 0012e98c 00000000 00000000 00000000 00000000 0012e99c 00000000 00000000 00000000 00000000根据EXCEPTION_POINTERS的结构类型,我们先对0012e838取值,得到的0012e92c是EXCEPTION_RECORD的指针,而0012e948是PCONTEXT的指针,我们比较关心的是EXCEPTION_RECORD,于是乎,我们再次取值,便得到了异常结构数据。
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; //c0000005 DWORD ExceptionFlags; //00000000 struct _EXCEPTION_RECORD *ExceptionRecord; //00000000 PVOID ExceptionAddress; //5f42d84a DWORD NumberParameters; //00000002 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
这里我们最为关心的是ExceptionAddress的数据为5f42d84a,可是这个地址是什么位置的存在呢,代表着哪行代码呢。。。
真实堆栈还原
我们得到了异常地址,可是我们并不知道堆栈啊,并且这个地址代表什么意思呢?于是我们开始着手还原堆栈。以求了解代码的执行过程。我们从UnhandledExceptionFilter函数基址处向上还原堆栈。这里省略了部分无用的信息:
0:000> dds 0012e7e8 L200 0012e7e8 0012e80c 0012e7ec 102143dd MSVCRTD!_XcptFilter+0x3d 0012e7f0 00000000 ...... 0012e80c 0012ff88 0012e810 00547077 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345] 0012e814 c0000005 0012e818 0012e838 0012e81c 10211673 MSVCRTD!_except_handler3+0x4b 0012e820 0012e840 ...... 0012e840 0012e864 0012e844 779471b9 ntdll!ExecuteHandler2+0x26 0012e848 0012e92c 0012e84c 0012ff78 0012e850 0012e948 0012e854 0012e900 0012e858 0012ed90 0012e85c 779471cd ntdll!ExecuteHandler2+0x3a ...... 0012e8a0 0054709a XXXXXX!except_handler3 0012e8a4 0012eca4 ....... 0012e918 77947017 ntdll!KiUserExceptionDispatcher+0xf 0012e91c 0012e92c 0012e920 0012e948 0012e924 0012e92c 0012e928 0012e948 0012e92c c0000005 0012e930 00000000 0012e934 00000000 0012e938 5f42d84a MFC42D!CWnd::OnDestroy+0x23 //异常地址 0012e93c 00000002 0012e940 00000000 0012ec38 dddddddd 0012ec3c dddddddd 0012ec40 0012eca4 0012ec44 0041c79e XXXXXX!CFloatMenuDlg::OnDestroy+0x6e //这里我们便发现的自己模块调用位置。 0012ec48 0012ee1c 0012ec4c 00193f20 0012ec50 00000000
这里我们便复原出原始的堆栈,从XXXXXX!CFloatMenuDlg OnDestroy调用了MFC42D!CWnd OnDestroy,完成了崩溃位置的定位。
KiUserExceptionDispatcher
在上面的堆栈中,我们亦可以分析KiUserExceptionDispatcher,这个也是系统分发异常的流转函数之一,从上面的原始堆栈打印中可以看该函数的参数,正是和之前的分析一样,0012e92c指向异常结构。复现分析
后来测试人员发现了复现的方法,主动挂上了windbg,而当windbg调试程序的时候,发生异常首先会派发给windbg。我们发生异常的时候就已经位于异常的堆栈了,此时就不需要寻找异常上下文以及异常堆栈了。0:000:x86> r eax=03371b70 ebx=0557c070 ecx=0557c070 edx=724af314 esi=0557c070 edi=007c6c28 eip=00000002 esp=0018ee34 ebp=0018ee5c iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210202 00000002 ?? ???我们查看上下文会发现,这个EIP的指针00000002,显然是个非法的地址,这个地址不可访问,导致崩溃。
0:000:x86> kn # ChildEBP RetAddr 00 0018ee30 724bdb6b 0x2 01 0018ee44 0040bdde mfc42!CWnd::Default+0x1d 02 0018ee5c 724bcba9 XXXXXX!CFloatMenuDlg::OnDestroy+0x51 03 0018eee8 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0 04 0018ef08 724bd8a5 mfc42!CWnd::WindowProc+0x27 05 0018ef80 724bd808 mfc42!AfxCallWndProc+0xab 06 0018efa4 724bddbf mfc42!AfxWndProc+0x3d 07 0018efe4 753077d8 mfc42!AfxWndProcBase+0x59 08 0018f010 753078cb user32!InternalCallWinProc+0x23 09 0018f08c 75307b6f user32!UserCallWinProcCheckWow+0x100 0a 0018f0f0 75307c44 user32!DispatchClientMessage+0x15d 0b 0018f12c 77a12f02 user32!__fnDWORD+0x2b 0c 0018f160 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e 0d 0018f174 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x18 0e 0018f188 00410752 mfc42!CWnd::DestroyWindow+0x3c 0f 0018f204 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x32c 10 0018f290 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0 11 0018f2b0 724bd8a5 mfc42!CWnd::WindowProc+0x27 12 0018f328 724bd808 mfc42!AfxCallWndProc+0xab 13 0018f34c 724bddbf mfc42!AfxWndProc+0x3d 14 0018f388 753077d8 mfc42!AfxWndProcBase+0x59 15 0018f3b4 753078cb user32!InternalCallWinProc+0x23 16 0018f430 75307b6f user32!UserCallWinProcCheckWow+0x100 17 0018f494 75307c44 user32!DispatchClientMessage+0x15d 18 0018f4d0 77a12f02 user32!__fnDWORD+0x2b 19 0018f504 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e 1a 0018f518 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x18 1b 0018f52c 004a0c90 mfc42!CWnd::DestroyWindow+0x3c 1c 0018f5d4 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x49c 1d 0018f660 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0 1e 0018f680 724bd8a5 mfc42!CWnd::WindowProc+0x27 1f 0018f6f8 724bd808 mfc42!AfxCallWndProc+0xab 20 0018f71c 724bddbf mfc42!AfxWndProc+0x3d 21 0018f758 753077d8 mfc42!AfxWndProcBase+0x59 22 0018f784 753078cb user32!InternalCallWinProc+0x23 23 0018f800 75307b6f user32!UserCallWinProcCheckWow+0x100 24 0018f864 75307c44 user32!DispatchClientMessage+0x15d 25 0018f8a0 77a12f02 user32!__fnDWORD+0x2b 26 0018f8d4 7530a14b ntdll_779d0000!KiUserCallbackDispatcher+0x2e 27 0018f970 7530ee31 user32!RealDefWindowProcWorker+0xa4 28 0018f990 7530ee92 user32!RealDefWindowProcA+0x4d 29 0018f9d8 753077d8 user32!DefWindowProcA+0x75 2a 0018fa04 753078cb user32!InternalCallWinProc+0x23 2b 0018fa80 7530bd11 user32!UserCallWinProcCheckWow+0x100 2c 0018fab8 7531921c user32!CallWindowProcAorW+0xb1 2d 0018fad4 724bcb51 user32!CallWindowProcA+0x1c 2e 0018faf4 724bcdb5 mfc42!CWnd::DefWindowProcA+0x47 2f 0018fb10 724bd8a5 mfc42!CWnd::WindowProc+0x3e 30 0018fb88 724bd808 mfc42!AfxCallWndProc+0xab 31 0018fbac 724bddbf mfc42!AfxWndProc+0x3d 32 0018fbe8 753077d8 mfc42!AfxWndProcBase+0x59 33 0018fc14 753078cb user32!InternalCallWinProc+0x23 34 0018fc90 7530899d user32!UserCallWinProcCheckWow+0x100 35 0018fd04 7530ef74 user32!DispatchMessageWorker+0x3ef 36 0018fd10 724c5faf user32!DispatchMessageA+0xf 37 0018fd20 724dfaf2 mfc42!CWinThread::PumpMessage+0x40 38 0018fd40 724f198e mfc42!CWnd::RunModalLoop+0xd2 39 0018fd8c 00401f40 mfc42!CDialog::DoModal+0x122 3a 0018fec0 724c43dd XXXXXX!XXXXXX::InitInstance+0x40b 3b 0018fed4 00567b97 mfc42!AfxWinMain+0x47 3c 0018fee8 004cdf8c touchpcmt!WinMain+0x15 [appmodul.cpp @ 30] 3d 0018ff84 77498543 touchpcmt!WinMainCRTStartup+0x134 3e 0018ff90 77a2ac69 kernel32!BaseThreadInitThunk+0xe 3f 0018ffd4 77a2ac3c ntdll_779d0000!__RtlUserThreadStart+0x72 40 0018ffec 00000000 ntdll_779d0000!_RtlUserThreadStart+0x1b查看一下线程的堆栈情况,与之前我们的分析不谋而合,mfc42!CWnd Default函数发生了崩溃。
LRESULT CWnd::Default() { // call DefWindowProc with the last message _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); return DefWindowProc(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam); } //汇编代码如下 mfc42!CWnd::Default: 724bdb4e 8bff mov edi,edi 724bdb50 56 push esi 724bdb51 8bf1 mov esi,ecx 724bdb53 e86a86ffff call mfc42!CThreadLocal<_AFX_THREAD_STATE>::GetData (724b61c2) 724bdb58 ff7040 push dword ptr [eax+40h] 724bdb5b 8b16 mov edx,dword ptr [esi] 724bdb5d ff703c push dword ptr [eax+3Ch] 724bdb60 8bce mov ecx,esi 724bdb62 ff7038 push dword ptr [eax+38h] 724bdb65 ff92a8000000 call dword ptr [edx+0A8h] 724bdb6b 5e pop esi 724bdb6c c3 ret 724bdb6d 90 nop 724bdb6e 90 nop 724bdb6f 90 nop 724bdb70 90 nop 724bdb71 90 nop
我们找一下MFC的代码,分析一下这个代码,一切都明晰了,这里面是一个成员函数的调用,对比汇编,call指令是一个改变EIP指针的一个指令之一,此处传递过来的this指针有问题,导致索引偏移的地址数据无效,进而无法执行。联系相关人员对比代码发现确实这样,在销毁窗口前进行了类指针内存的释放,导致崩溃。修改代码避免了崩溃的发生。
总结
内存dump分析的过程也是推测验证的过程,我们尝试各种方法,通过点滴细节,推测全貌,然后反复的推测验证,再信息会总,从而分析出崩溃的真实原因,进而解决问题。相关文章推荐
- 在Activity的onCreate方法中显示PopupWindow导致异常的原因分析及解决方案
- 对mdi程序中一个弹出菜单警告原因的分析
- C# 屏蔽由于崩溃弹出的windows异常弹框
- 非并发原因引起的乐观锁异常故障分析
- 使用dump文件分析系统蓝屏原因
- 避免 showModalDialog 弹出新窗体的原因分析
- web程序莫名弹出错误提示框(dwr),处理方法。产生原因有待思考
- C# 外部组件发生异常原因分析 [分析]
- PHPWAMP自启异常,服务器重启后Apache等服务不会自动重启的原因分析
- 分析:Android系统刷机后,第一次开机启动很慢的原因
- 系统启动失败的原因分析及解决办法
- Melis系统崩溃问题分析以及解决思路
- 发生ConcurrentModificationException异常的原因分析
- 系统上线后WCF服务最近经常死掉的原因分析总结
- Linux内核学习之四--进程、进程调度、系统调用、proc文件系统和内核异常分析
- Andorid5.0 bind service 异常和fail原因分析及解决办法
- linux内核中断、异常、系统调用的分析以及实践
- 避免 showModalDialog 弹出新窗体的原因分析
- 基于事件关联的根本原因分析系统的设计与实现
- windows系统显示器屏幕有时会自动关闭的原因分析及解决