由于堆损坏,任何提示,C ++ / msvc6应用程序崩溃?

时间:2010-04-07 15:27:20

标签: windows visual-c++ crash windbg heap-corruption

关于申请

  • 它在Windows XP Professional SP2上运行。
  • 它是使用带有Service Pack 6的Microsoft Visual C ++ 6.0构建的。
  • 这是基于MFC的。
  • 它使用几个外部dll(例如Xerces,ZLib或ACE)。
  • 具有高性能要求
  • 它做了很多网络和硬盘I / O,但它也是cpu密集型。
  • 它有一个异常处理机制,当发生未处理的异常时会生成一个小型转储。
  • 更新:它是一个高度多线程的应用程序,我们使用互斥锁来保护并发访问(当然,我们可能在某个地方失败了......)

有关崩溃的事实

  • 它只发生在多处理器/多核机器上以及繁重的工作中。
  • 经过几个小时的运行,它随机发生(我们和我们的客户都没有找到模式)。
  • 我们无法在我们的测试实验室重现崩溃。它只发生在某些生产系统上(但总是在多核机器上)
  • 它总是在同一点崩溃,尽管完整的堆栈并不总是相同。让我添加崩溃线程的堆栈(使用WinDbg获取,抱歉我们没有符号)
Exception code: c0000005 ACCESS_VIOLATION
Address        : 006a85b9
Access Type    : write
Access Address : 2e020fff
Fault address:  006a85b9 01:002a75b9 C:\MyDir\MyApplication.exe

ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
030af6c8 7c9206eb 77bfc3c9 01a80000 00224bc3 MyApplication+0x2a85b9
030af960 7c91e9c0 7c92901b 00000ab4 00000000 ntdll!RtlAllocateHeap+0xeac (FPO: [Non-Fpo])
030af98c 7c9205c8 00000001 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
030af9c0 7c920551 01a80898 7c92056d 313adfb0 ntdll!RtlpFreeToHeapLookaside+0x22 (FPO: [2,0,4])
030afa8c 4ba3ae96 000307da 00130005 00040012 ntdll!RtlFreeHeap+0x1e9 (FPO: [Non-Fpo])
030afacc 77bfc2e3 0214e384 3087c8d8 02151030 0x4ba3ae96
030afb00 7c91e306 7c80bfc1 00000948 00000001 msvcrt!free+0xc8 (FPO: [Non-Fpo])
030afb20 0042965b 030afcc0 0214d780 02151218 ntdll!ZwReleaseSemaphore+0xc (FPO: [3,0,0])
030afb7c 7c9206eb 02e6c471 02ea0000 00000008 MyApplication+0x2965b
030afe60 7c9205c8 02151248 030aff38 7c920551 ntdll!RtlAllocateHeap+0xeac (FPO: [Non-Fpo])
030afe74 7c92056d 0210bfb8 02151250 02151250 ntdll!RtlpFreeToHeapLookaside+0x22 (FPO: [2,0,4])
030aff38 77bfc2de 01a80000 00000000 77bfc2e3 ntdll!RtlFreeHeap+0x647 (FPO: [Non-Fpo])
7c92056d c5ffffff ce7c94be ff7c94be 00ffffff msvcrt!free+0xc3 (FPO: [Non-Fpo])
7c920575 ff7c94be 00ffffff 12000000 907c94be 0xc5ffffff
7c920579 00ffffff 12000000 907c94be 90909090 0xff7c94be
*** WARNING: Unable to verify checksum for xerces-c_2_7.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for xerces-c_2_7.dll - 
7c92057d 12000000 907c94be 90909090 8b55ff8b MyApplication+0xbfffff
7c920581 907c94be 90909090 8b55ff8b 08458bec xerces_c_2_7
7c920585 90909090 8b55ff8b 08458bec 04408b66 0x907c94be
7c920589 8b55ff8b 08458bec 04408b66 0004c25d 0x90909090
7c92058d 08458bec 04408b66 0004c25d 90909090 0x8b55ff8b
  • 地址 MyApplication + 0x2a85b9 对应于对std :: list的erase()的调用。

到目前为止我尝试了什么

  • 查看与崩溃结束发生点相关的所有代码。
  • 尝试在我们的测试实验室中启用pageheap,但现在没有找到任何有用的内容。
  • 我们已经将std :: list替换为C数组,然后它在代码的其他部分崩溃(尽管它是相关代码,但它不在旧列表所在的代码中)。巧合的是,现在它在另一个擦除中崩溃了,虽然这次是std :: multiset。让我复制转储中包含的堆栈:
ntdll.dll!_RtlpCoalesceFreeBlocks@16()  + 0x124e bytes  
ntdll.dll!_RtlFreeHeap@12()  + 0x91f bytes  
msvcrt.dll!_free()  + 0xc3 bytes    
MyApplication.exe!006a4fda()
[Frames below may be incorrect and/or missing, no symbols loaded for MyApplication.exe] 
MyApplication.exe!0069f305()
ntdll.dll!_NtFreeVirtualMemory@16()  + 0xc bytes    
ntdll.dll!_RtlpSecMemFreeVirtualMemory@16()  + 0x1b bytes   
ntdll.dll!_ZwWaitForSingleObject@12()  + 0xc bytes  
ntdll.dll!_RtlpFreeToHeapLookaside@8()  + 0x26 bytes    
ntdll.dll!_RtlFreeHeap@12()  + 0x114 bytes  
msvcrt.dll!_free()  + 0xc3 bytes    
c5ffffff()  
  • (2010年4月12日)我尝试启用堆免费检查(使用gflags),但它大大减慢了应用程序的速度......

可能无法应用的解决方案(我知道)

  • “将应用程序迁移到更新的编译器”:我们正在努力解决此问题,但目前还不是解决方案。
  • “启用pageheap(正常或完整)”:我们无法在生产计算机上启用pageheap,因为这会严重影响性能。

我认为这就是我现在所记得的,如果我忘记了什么,我会尽快添加它。如果您能给我一些提示或提出一些可能的解决方案,请不要犹豫回答!

7 个答案:

答案 0 :(得分:1)

您可以尝试通过调用调试堆检查例程来查看代码,看看是否可以找到更接近源的损坏(您正在使用调试CRT来追踪此问题,对吧?):

答案 1 :(得分:1)

从Windows的调试工具中使用Application Verifier。有时它会有所帮助。

尝试设置VS以下载OS调试符号,并确保应用程序中的OMIT FRAME POINTERS处于关闭状态。也许堆栈跟踪将提供信息。

  

高度多线程

很久以前我发现WinXP中每个进程的线程数有限制。我的测试片段只能创建少量的线程。问题由线程池解决。

编辑:

就我的目的而言,只需检查gflags.exe中的“Application Verifier”复选框即可。不幸的是,我没有其他选择的经验。 至于线程限制,测试片段很简单:

unsigned __stdcall ThreadProc(LPVOID)
{
  _tprintf(_T("Thread started\n"));
  return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
  while (TRUE)
  {
    unsigned threadId = 0;
    _tprintf(_T("Start thread\n"));
    _beginthreadex( NULL, 0, &ThreadProc, NULL, 0, &threadId);
  }
  return 0;
}

这次我没等多久,但任务管理器中的句柄数量增长非常快。我的真实世界应用程序仅在12小时内获得此效果。但必须说问题不是崩溃,新线程没有创建。

答案 2 :(得分:1)

这里的关键是这只发生在多处理器机器上(核心与处理器相同) 线程程序在单个处理器上运行时会发生两个线程永远不会同时执行的情况。操作系统必须对每个处理器进行时间分片以模拟线程。 在多处理器系统中,多个线程可以同时运行。 您现在可能正在同时访问来自不同线程的共享资源。 这些资源可以是外部系统的连接,甚至是全局变量和数据结构,甚至是Singleton类。 不幸的是,你现在有一个最困难的问题。 如果您发现内存已损坏,则需要在另一个线程上查找还有谁在使用它,然后同步内存(Semaphore或CriticalSection)。 不幸的是,没有简单的方法可以找到问题。

您可以暂时将处理器关联设置为仅在一个处理器上运行,直到找到问题为止。见链接 http://msdn.microsoft.com/en-us/library/ms684251(VS.85).aspx 这是一种设置亲和力的方法 对于Windows XP / Vista / 7,通过打开Windows任务管理器(CTL + ALT + DEL,或右键单击任务栏)访问Affinity,选择“进程”选项卡,右键单击要隔离的应用程序进程,然后选择“设置亲和力”。在Processor Affinity对话框中,取消选中您不需要使用的CPU /核心。这有效地将该应用程序隔离到所选的CPU /核心,从而防止进行跨越和减少进程切换,并简化了监控多个程序的CPU /核心分配的能力。

答案 3 :(得分:1)

你能发布你得到的例外吗?

如果这是一些内存损坏错误,那么崩溃会在内存损坏后的某个时间发生,因此追踪根本原因将是一项挑战。你应该:

  1. 旅行(或远程登录)到生产系统,安装Visual Studio,准备好.pdb和.map文件(以及Windows的符号),将调试器附加到发布版本并等待崩溃。虽然如果你正确设置它,你可以在dev机器上使用minidump文件,你已经在那里设置了应用程序和窗口的符号。然后你可以看到哪个免费调用正在抛出,并试图弄清楚哪个对象被释放,以查看该对象是否以某种方式被破坏,以及内存中的附近对象。
  2. 以某种方式找到一种方法来重现办公室中的错误,您是否可以创建足够高的卷来复制客户正在做的事情?
  3. 您发布的调用堆栈看起来并不特别有启发性。

    由于您使用带有SP6的VS 6,因此其STL正常。

    您能否判断生产系统上的应用是否泄漏了任何资源?运行perfmon可以帮助解决这个问题。

    另一件事,你不是经常从不同的线程调用new / delete吗?我发现如果你做得足够快,你会很快崩溃你的应用程序(在XP上这样做)。我不得不用我的应用程序中的VirtualAlloc(Windows虚拟内存API)替换新的/删除调用,这对我来说很有用。当然,STL也可以从堆中分配。

答案 4 :(得分:1)

使用可以挂钩CPU事件的性能分析器,例如VTune。在采样模式下设置它并告诉它等待与缓存线共享相关的事件。这些由SNOOP阶段的HITM事件识别。

如果在具有实际工作负载的多处理器计算机上运行此操作,则会在代码中找到单个数据线程之间存在活动争用的位置。您将需要分析以这种方式找到的探查器热点,并尝试找到未用适当的互斥锁包装的东西。

我不是CPU架构或其他任何专家,但我的理解是,当CPU即将访问一段数据时,系统将检查是否有其他CPU正在访问同一条数据,这是完成的通过观察每个CPU发出的内存提取和写入,这个过程称为snooping。 Snooping确保如果两个或更多CPU在其每个缓存中具有相同的数据,则在修改其中一个数据时,将删除数据的复制副本。 HIT-Modified事件意味着系统检测到这种情况并且必须刷新其中一个CPU缓存行。

有关使用VTune的更多信息,请参阅此文档

http://software.intel.com/en-us/articles/using-intel-vtune-performance-analyzer-events-ratios-optimizing-applications/

我现在面前没有VTune的副本,所以也许这不起作用,但它似乎是获取一些数据的最低影响方式。采样模式下的VTune不应该导致很多性能问题。

答案 5 :(得分:1)

当您的第二个堆栈跟踪显示时,您的应用程序正在破坏堆。堆块的头部被写入,因此当合并空闲块时,或者当通过空闲列表(在第一个堆栈跟踪中)时,崩溃发生在堆管理器中。 您识别出的当前释放内存的代码可能是另一个溢出或下溢内存块的代码的受害者。

调试此类崩溃的最简单方法是使用来自windows的调试帮助,通过pageheap或appverifier,但是根据应用程序的不同,它可能会减慢太多,或者增加内存使用率太高而无法使用,似乎是这样的。您可以尝试使用轻型页面,这样会产生较小的影响。

您需要确定应用程序的哪个部分溢出。一种方法是查看溢出块中包含的信息。如果你在RtlpCoalesceFreeBlocks中崩溃,我想我记得其中一个寄存器(@esi)指向损坏的块的开始(在写这篇文章时我不在Windows系统上,无法检查)。或者如果你有转储,使用windbg命令!heap -a将转储所有内存并显示损坏的块(最好登录到文件,因为完整的堆列表可能很长)。一旦知道损坏的块,它们的内容可能有助于识别代码。

另一个帮助可以是启用堆栈回溯(使用gflags)。这可以在生产中完成,因为它比pageheap更轻。它会向堆块添加一些信息,并可能将崩溃移动到应用程序中的另一个位置,但堆栈跟踪将有助于识别分配溢出的块的代码。

答案 6 :(得分:0)

我会专注于在具有适当调试符号的构建上发生问题,至少对于您的主应用程序而言。你好像用“抱歉我们没有符号”来掩盖它,但是当应用符号时,堆栈跟踪可能会显示更多信息。

这究竟是什么意思:“我们无法生成符号,因为我们正在链接一个如果我们使用它们就不会链接的库。”?这看起来很奇怪。