怎么知道任何应用程序都挂起了,然后用户杀死了它,或者用户通常杀死了它或发生了任何崩溃



我想区分应用程序的崩溃、挂起和正常终止?就像我们必须为WER注册以创建崩溃转储一样,如果发生任何事情,操作系统会发送一些信号来处理,那么如何处理这一切,并创建一个库来帮助根据崩溃、挂起或简单的kill对其进行备份?有吗

我想区分应用程序的崩溃、挂起和正常终止?

您缺少以下选项:

  • 应用程序工作正常
  • 应用程序即将崩溃,但可能不会

这两个使得区分状态变得非常困难。为了理解这一点,你需要知道两件事:

  1. 异常调度
  2. 崩溃转储是如何生成的

异常调度

崩溃是由异常引起的。但并不是所有的异常都会导致崩溃,因为异常是可以处理的。异常的处理通常在catch{}块中完成。

因此,假设您的应用程序中出现异常。以下过程开始:

  1. 如果附加了调试器,请询问调试器是否要对此作出反应。这是调试器执行某些操作的第一次机会
  2. 如果调试器不想做出反应,请检查可能想对异常做出反应的catch{}
  3. 如果没有catch{}块,请检查所谓的"未处理的异常处理程序",它可能希望对异常做出反应
  4. 如果仍然没有人想处理异常,请再次询问调试器。这是调试器执行某些操作的第二次机会
  5. 如果调试器什么都不做,操作系统就需要处理这种情况。如果启用了一些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{}块,也没有未处理的异常处理程序。如果没有调试器,此应用程序将崩溃并终止。

崩溃转储是如何创建的

崩溃转储的创建非常类似于调试器创建崩溃转储。

  1. 将调试器附加到进程
  2. 启动新线程
  3. 在该线程中,强制执行已知异常
  4. 当调试器被告知第一次出现异常时,创建崩溃转储文件

强制的异常通常是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%的情况下有效。

最新更新