我想区分应用程序的崩溃、挂起和正常终止?就像我们必须为WER注册以创建崩溃转储一样,如果发生任何事情,操作系统会发送一些信号来处理,那么如何处理这一切,并创建一个库来帮助根据崩溃、挂起或简单的kill对其进行备份?有吗
我想区分应用程序的崩溃、挂起和正常终止?
您缺少以下选项:
- 应用程序工作正常
- 应用程序即将崩溃,但可能不会
这两个使得区分状态变得非常困难。为了理解这一点,你需要知道两件事:
- 异常调度
- 崩溃转储是如何生成的
异常调度
崩溃是由异常引起的。但并不是所有的异常都会导致崩溃,因为异常是可以处理的。异常的处理通常在catch{}
块中完成。
因此,假设您的应用程序中出现异常。以下过程开始:
- 如果附加了调试器,请询问调试器是否要对此作出反应。这是调试器执行某些操作的第一次机会
- 如果调试器不想做出反应,请检查可能想对异常做出反应的
catch{}
块 - 如果没有
catch{}
块,请检查所谓的"未处理的异常处理程序",它可能希望对异常做出反应 - 如果仍然没有人想处理异常,请再次询问调试器。这是调试器执行某些操作的第二次机会
- 如果调试器什么都不做,操作系统就需要处理这种情况。如果启用了一些WER设置,现在可能会保存故障转储。之后,它将终止进程并释放应用程序分配的资源
术语"首次机会例外"one_answers"第二次机会例外"很重要。
WinDbg告诉你关于这个:
0:006> g
(2db0.2908): CLR exception - code e0434352 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0098ebe0 ebx=00000005 ecx=00000005 edx=00000000 esi=0098eca4 edi=00000001
eip=76c44402 esp=0098ebe0 ebp=0098ec3c iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
KERNELBASE!RaiseException+0x62:
76c44402 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:0098ec34=5d02fd68
0:000>
正如您所看到的,这个异常是第一次机会异常。WinDbg表示
在任何异常处理之前都会报告第一次机会异常。
这意味着:调试器在运行任何catch{}
块之前就已经做出了反应。和:
此异常可能是预期的并已得到处理。
这意味着:代码可能有一个catch{}
块,它可以做一些有用的事情,这样应用程序就不会崩溃。
第二次机会异常如下:
0:000> g
(3e34.36c0): C++ EH exception - code e06d7363 (first chance)
(3e34.36c0): C++ EH exception - code e06d7363 (!!! second chance !!!)
eax=00daf940 ebx=00000000 ecx=00000003 edx=00000000 esi=00000001 edi=00000000
eip=76c44402 esp=00daf940 ebp=00daf998 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
KERNELBASE!RaiseException+0x62:
76c44402 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:00daf994=0754642c
正如您所看到的,以前有一个第一次出现的异常,但我指示调试器此时不要执行任何操作。应用程序既没有catch{}
块,也没有未处理的异常处理程序。如果没有调试器,此应用程序将崩溃并终止。
崩溃转储是如何创建的
崩溃转储的创建非常类似于调试器创建崩溃转储。
- 将调试器附加到进程
- 启动新线程
- 在该线程中,强制执行已知异常
- 当调试器被告知第一次出现异常时,创建崩溃转储文件
强制的异常通常是INT 3
指令,它是一个调试断点,异常代码为0x80000003。
识别碰撞
当出现异常并且该异常无法继续时,会发生崩溃。
在WinDbg中,您可以使用.exr -1
来获取有关最后一个异常的信息。
0:000> .exr -1
ExceptionAddress: 76c44402 (KERNELBASE!RaiseException+0x00000062)
ExceptionCode: e06d7363 (C++ EH exception)
ExceptionFlags: 00000001
ExceptionFlags
为1时,异常是不可继续的。
识别潜在的崩溃(但可能没有)
和以前一样,但异常标志为0。
识别杀人事件
这不容易实现。操作系统将终止进程。也不例外。通常情况下,您不会对这种情况进行崩溃转储。
但是,有些工具可以在进程终止时停止。但是没有太多可以分析的。您可以通过查看调用堆栈来识别这样的情况:
0:000> k L1
# Child-SP RetAddr Call Site
00 0000003a`d2d3f968 00007fff`3b16a938 ntdll!NtTerminateProcess+0x14
通常,只剩下一个线程:
0:000> ~
. 0 Id: 2078.34ec Suspend: 0 Teb: 0000003a`d2e03000 Unfrozen
应用程序工作正常
在这种情况下,异常代码将是0x80000003,因为为了生成崩溃转储而注入了断点。
0:004> .exr -1
ExceptionAddress: 77964120 (ntdll!DbgBreakPoint)
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 1
Parameter[0]: 00000000
从调用堆栈中,您通常会看到is是由调试器注入的:
0:004> k L2
# ChildEBP RetAddr
00 0666fd34 7799ace9 ntdll!DbgBreakPoint
01 0666fd64 754c6359 ntdll!DbgUiRemoteBreakin+0x39
主线程通常什么都不做,即等待用户输入
0:004> ~0k L1
# ChildEBP RetAddr
00 008fef50 6437a188 win32u!NtUserWaitMessage+0xc
应用程序挂起
挂起看起来很像正常运行的应用程序,因为生成崩溃转储的过程也是一样的:
0:004> .exr -1
ExceptionAddress: 77964120 (ntdll!DbgBreakPoint)
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 1
Parameter[0]: 00000000
0:004> k L2
# ChildEBP RetAddr
00 0666fd34 7799ace9 ntdll!DbgBreakPoint
01 0666fd64 754c6359 ntdll!DbgUiRemoteBreakin+0x39
有两种类型的挂起:高CPU挂起(应用程序正忙,可能处于无休止的循环中)或低CPU手数(应用程序已死锁)。
高CPU挂起可以通过其调用堆栈来识别。它在堆栈顶部可能没有WaitForSingleObject()
或WaitForMultipleObjects()
方法。
低CPU挂起可能看起来与工作中的应用程序完全相同,因为它也在等待。唯一的区别是:工作的应用程序正在等待用户输入(这可能很快就会发生),而挂起的应用程序则在等待其他东西(这可能永远不会得到,从而导致死锁)。
现实
实际情况可能要复杂得多,这取决于是否涉及.NET、是否有多个UI线程等。但是IMHO,在一个直接的应用程序中,这种方法应该在大约70%的情况下有效。