【问题标题】:Can unmanaged first chance exception cause a crash/restart?非托管的第一次机会异常会导致崩溃/重启吗?
【发布时间】:2018-09-27 12:18:24
【问题描述】:

后续问题:“在调查崩溃时,我是否应该只调查第二次机会异常?在哪些情况下我还需要调查第一次机会异常转储?”

我的问题有点宽泛,但我很好奇真正的答案是什么。我读过很多文章说第一次机会异常不太可能导致应用程序崩溃。这是导致它的第二次机会异常。一个简单的谷歌搜索并不能直接回答我的问题。

编辑:这里是示例文章,但还有更多:

What is a First Chance Exception?

"对于没有异常处理的代码,调试器会收到一个 第二次机会异常通知,将在未处理的情况下停止 例外。 "

Program crashes, but Debug Diag says it's a first chance exception, is that correct?

当然,根据定义,只有第二次机会异常会导致代码崩溃, 即没有被代码处理的?

我遇到了一个间歇性问题,我的应用重新启动或崩溃(事件查看器中没有错误),但在重新启动之前,Adplus 会产生一些第一次机会 AccessViolation 异常。没有第二次机会例外。

以下是 WinDbg.exe 上 FULLDUMP_FirstChance_av_AccessViolation 的 sn-p:

PROBLEM_CLASSES: 
HEAP_CORRUPTION
    Tid    [0x16e8]
    Frame  [0x02]: ntdll!RtlAllocateHeap
HEAP_CORRUPTION
    Tid    [0x16e8]
    Frame  [0x02]: ntdll!RtlAllocateHeap
INVALID_POINTER_READ
    Tid    [0x16e8]
    Frame  [0x00]: ntdll!ExpInterlockedPopEntrySListFault
NOSOS
    Tid    [0x16e8]
BUGCHECK_STR:  HEAP_CORRUPTION_HEAP_CORRUPTION_INVALID_POINTER_READ_NOSOS

下面的示例调用堆栈:

# ChildEBP RetAddr  Args to Child              
00 085aec28 7c91020e 00000007 00c407d8 00c40000 ntdll!ExpInterlockedPopEntrySListFault (FPO: [0,2,0])
01 085aec58 7c91019b 00c407d8 00000030 00000000 ntdll!RtlpAllocateFromHeapLookaside+0x1d (FPO: [Non-Fpo])
02 085aee84 78134d83 00c40000 00000000 00000030 ntdll!RtlAllocateHeap+0x1c2 (FPO: [Non-Fpo])
03 085aeea4 78160e30 00000030 0000002f 085aeecc msvcr80!malloc(unsigned int size = 0x30)+0x7a (FPO: [1,0,0]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\malloc.c @ 163]
04 085aeebc 7c4221b3 00000030 00000003 7c422f20 msvcr80!operator new(unsigned int size = 0x30)+0x1d (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\new.cpp @ 59]
05 085aeed4 7c423315 00000030 00000000 ae218f51 msvcp80!std::_Allocate<char>(unsigned int _Count = 0x30, char * __formal = 0x00000000 "")+0x15 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\xmemory @ 44]
06 085aef0c 7c4233c4 0000002a 00000000 085af028 msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Copy(unsigned int _Newsize = 0x2a, unsigned int _Oldlen = 0)+0x55 (FPO: [Non-Fpo]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 2020]
07 085aef20 7c423779 0000002a 00000000 085af200 msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Grow(unsigned int _Newsize = 0x2a, bool _Trim = false)+0x22 (FPO: [2,0,0]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 2050]
08 085aef3c 7c425e55 0000002a 00000000 0000002a msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::append(class std::basic_string<char,std::char_traits<char>,std::allocator<char> > * _Right = 0x0000002a, unsigned int _Roff = 0, unsigned int _Count = 0x2a)+0x58 (FPO: [Non-Fpo]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 969]
09 085aef4c 60baed1e 085af028 ae262fd2 085af1a4 msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::append(class std::basic_string<char,std::char_traits<char>,std::allocator<char> > * _Right = 0x085af028 " S1 S1 Card number:    ************8706  
")+0xd (FPO: [1,0,0]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 956]
0a 085af1a4 7c802662 00000100 00000000 00000000 aipoptrv19!DllUnregisterServer+0x1f15e
0b 085af234 7c42317a 00000000 00000000 0000000f kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
0c 085af274 60bc1fd8 60baa1cb 0865d680 0000001c msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> >(void)+0x11 (FPO: [0,0,4]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 576]
0d 085af278 60baa1cb 0865d680 0000001c 00000002 aipoptrv19!DllUnregisterServer+0x32418
0e 085af2e4 60bb227c 00000001 085af420 0865d648 aipoptrv19!DllUnregisterServer+0x1a60b
0f 085af34c 7c425e45 085af404 00000000 ffffffff aipoptrv19!DllUnregisterServer+0x226bc
10 085af35c 60b97724 72506f44 69746e69 0000676e msvcp80!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign(class std::basic_string<char,std::char_traits<char>,std::allocator<char> > * _Right = 0x72506f44)+0xd (FPO: [1,0,0]) (CONV: thiscall) [f:\dd\vctools\crt_bld\self_x86\crt\src\xstring @ 1044]
11 085af45c 78261414 00000002 403110f4 7824f516 aipoptrv19!DllUnregisterServer+0x7b64
12 085af468 7824f516 fffffffe 781f2c2e 0000001c mfc80!_AfxDispatchCall(<function> * __formal = 0x40b59c84, void * __formal = 0x085af6b8, unsigned int __formal = 0x85a0003)+0x10 (CONV: stdcall) [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\olecall.cpp @ 40]
13 085af470 781f2c2e 0000001c 7824f49b 00000008 mfc80!CCmdTarget::CallMemberFunc(struct AFX_DISPMAP_ENTRY * pEntry = 0x6d756e20, unsigned short wFlags = 0x6562, struct tagVARIANT * pvarResult = 0x20202020 Empty, struct tagDISPPARAMS * pDispParams = 0x2a2a2a2a, unsigned int * puArgErr = 0x2a2a2a2a)+0x1ad (CONV: thiscall) [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\oledisp1.cpp @ 1064]

该错误与我仍在研究的堆损坏和无效指针有关。我是堆和 malloc 的新手,我刚刚学会了使用 WinDbg 进行调试。我只是想知道当这不是我的优先事项并且不会真正解决我的问题时,我是否在浪费时间学习内存分配。 (当然了解堆是一件好事,但解决主要问题是重中之重)

我对我的 adplus 配置文件充满信心,我确信它会针对所有二次机会异常生成完整转储。我在一个示例应用程序上进行了尝试。

应用程序不会崩溃,它只是意外地间歇性地重新启动,而没有事件查看器错误。它可以在使用特定服务时间歇性地重新创建。

如果转储文件不是问题的真正原因,以下是我可能的想法:

  • 其他进程(未附加在我的 adplus 上)导致重新启动。
  • 没有生成第二次机会异常完整转储。
  • 其他(有什么想法吗?)

PS:对不起,如果我没有指定一些细节和代码示例等,因为它是机密的。我在不影响公司政策的情况下尽力解释了这个问题。

提前谢谢!

【问题讨论】:

  • 是的,这几乎是在浪费时间,您没有编写任何在这里失败的代码。不完全保证 aipoptrv19.dll 导致它,但它肯定是你应该怀疑的。 POS 问题很棘手,您必须与为您提供硬件的公司建立良好的工作关系。一般来说,你得到你所支付的。这里没有人可以帮助你。
  • 从表面上看,“第一次机会异常不太可能导致应用程序崩溃”纯属无稽之谈。哪些文章是这样说的?
  • 当第一次机会异常没有被应用程序处理,并且没有其他东西捕获它时,它最终会到达系统默认处理程序。 docs.microsoft.com/en-us/security-risk-detection/concepts/… 我认为这意味着第一次机会异常需要第二次崩溃的机会
  • 感谢大家的回复,非常感谢。 @HansPassant 我没有写任何代码,因为我不确定代码的哪一部分实际失败了。是的,我认为这与硬件有关,但我的高级同事说硬件问题不太可能导致 POS 应用程序实际崩溃/重新启动,而只会导致屏幕上出现“诊断错误”提示。
  • @ArndtJonasson 请查看我对链接的编辑。如果第一次机会异常没有被处理,那么它被称为第二次机会异常吧?

标签: vb.net windbg heap-corruption first-chance-exception adplus


【解决方案1】:

这个MSDN article about exception dispatching解释了这个过程:

当用户模式代码发生异常时,系统使用以下搜索顺序查找异常处理程序:

  1. 如果进程正在调试,系统会通知调试器。有关详细信息,请参阅调试器异常处理。
  2. 如果进程没有被调试,或者关联的调试器没有处理异常,系统会尝试通过搜索发生异常的线程的堆栈帧来定位基于帧的异常处理程序。系统先搜索当前堆栈帧,然后以相反的顺序搜索前面的堆栈帧。
  3. 如果找不到基于帧的处理程序,或者没有基于帧的处理程序处理异常,但正在调试进程,系统会再次通知调试器。
  4. 如果进程没有被调试,或者关联的调试器没有处理异常,系统会根据异常类型提供默认处理。对于大多数例外情况,默认操作是调用 ExitProcess 函数。

在第 1 步中,异常被称为第一次机会异常,因为这是任何人都可以捕获并处理异常的第一次机会。

在第 3 步中,同样的异常称为第二次机会异常,因为这是第二次,调试器有机会捕获并处理异常。

只有当流程继续到第 4 步时,程序才会崩溃或退出。因此,是的,只有第二次机会异常会导致进程崩溃。

非托管的第一次机会异常会导致崩溃/重启吗?

没有。见前。

在调查崩溃时,我应该只调查二次机会异常吗?

基本上是的。在分析崩溃时,每个人 (>90%) 都会这样做。

在哪些情况下我还需要调查第一次机会异常转储?

案例一:

第二次机会异常可能是先前第一次机会异常的结果。由于第一次机会异常,一个值可能未初始化并导致不同的第二次机会异常。

此类场景的示例代码:

SomeObject o = null;
try {
    throw new Exception("First chance"); // consider this in some method
    o = new SomeObject();
}
catch (Exception)
{
    // make sure that the exception does not become a second chance exception
}
o.DoSomething(); // causes NullReferenceException first chance and second chance if uncaught

应用程序由于 NullReferenceException 而崩溃,但真正的原因是之前的 Exception。但是,此类情况通常很容易识别,无需查看首次机会例外情况。

案例 2:

异常有很高的开销,即它们消耗 CPU 周期并因此消耗性能。如果您确实有很多第一次机会例外,您可能希望摆脱它们。

【讨论】:

  • 非常感谢。我相信案例 2 就是我们现在所经历的。有 6 个连续的第一次机会访问冲突转储文件,但没有第二次机会异常。我检查了 CPU 性能,它确实飙升,然后我们的应用程序突然重新启动,而事件查看器中没有任何错误。你认为这可能吗?您是否有一些推荐的流程或工具来调试它?先感谢您。崩溃/重启只能在生产中重新创建,我只有这 6 个转储、一些专用的应用程序日志、事件查看器和一个 PerfMon 日志。
  • @Cyber​​pau - 您是如何捕获第一次机会异常的?如果您使用 procdump,您还可以指示它在进程终止时创建转储文件。这可能会给你一些线索。另一种方法是使用 WPR 并对其进行分析,但 WPR 的开销(远)大于 procdump,因此请确保首先在非生产机器上进行测试。
  • @Cyber​​pau:对我来说,这听起来很像一个未处理的异常处理程序,它通过重新启动应用程序来“处理”第二次机会异常。
  • @Cyber​​pau 当然没有,因为未处理的异常处理程序在第 2 步中跳入:“系统尝试定位基于帧的异常处理程序”,它会找到未处理的异常处理程序。它永远不会进入第 3 步。
  • @Cyber​​pau:我建议你实现一个异常的minimal reproducible example 和一个未处理的异常处理程序,从头到尾调试它。在 C# 中只有大约 30 行代码,但它确实会帮助您理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-09
相关资源
最近更新 更多