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

首先让我说,我正在写几个月的试图找出在我们的应用程序发生崩溃的根源这个问题。 我会尽可能详细地说明我已经发现的情况。

关于应用程序

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

关于事故的事实

  • 它只发生在多处理器/多核机器上,并在繁重的工作中。
  • 它发生在随机(我们和我们的客户都没有find一个模式),几个小时运行后。
  • 我们不能在testing实验室重现崩溃。 它只发生在一些生产系统上(但总是在多核机器上)
  • 尽pipe整个堆栈并不总是相同的,但它总是以相同的点崩溃 。 让我添加崩溃的线程的堆栈(使用WinDbg获得,对不起,我们没有符号)
exception代码:c0000005 ACCESS_VIOLATION
地址:006a85b9
访问types:写入
访问地址:2e020fff
故障地址:006a85b9 01:002a75b9 C:\ MyDir \ MyApplication.exe

 ChildEBP RetAddr与孩子联系
警告:堆栈展开信息不可用。 以下框架可能是错误的。
 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 cffffffffffffffffffffff msvcrt!free + 0xc3(FPO:[Non-Fpo])
 7c920575 ff7c94be 00ffffff 12000000 907c94be 0xc5ffffff
 7c920579 00ffffff 12000000 907c94be 90909090 0xff7c94be
 ***警告:无法validationxerces-c_2_7.dll的校验和
 ***错误:无法find符号文件。 默认导出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()的调用。

我到目前为止所尝试过的

  • 检查所有与崩溃结束点相关的代码。
  • 试图在我们的testing实验室启用pageheap,尽pipe现在没有发现有用的东西。
  • 我们用std :: listreplace了一个C数组,然后在代码的其他部分崩溃(尽pipe它是相关的代码,它不在旧列表所在的代码中)。 巧合的是,现在它在另一个擦除崩溃,虽然这是一个std :: multiset的时间。 让我复制包含在转储中的堆栈:
 ntdll.dll!_RtlpCoalesceFreeBlocks@16()+ 0x124e字节  
 ntdll.dll!_RtlFreeHeap@12()+ 0x91f字节  
 msvcrt.dll!_free()+ 0xc3字节    
 MyApplication.exe!006a4fda()
 [下面的框架可能不正确和/或丢失,没有为MyApplication.exe加载符号] 
 MyApplication.exe!0069f305()
 ntdll.dll!_NtFreeVirtualMemory@16()+ 0xc字节    
 ntdll.dll!_RtlpSecMemFreeVirtualMemory@16()+ 0x1b字节   
 ntdll.dll!_ZwWaitForSingleObject@12()+ 0xc个字节  
 ntdll.dll!_RtlpFreeToHeapLookaside@8()+ 0x26字节    
 ntdll.dll!_RtlFreeHeap@12()+ 0x114字节  
 msvcrt.dll!_free()+ 0xc3字节    
 c5ffffff()  
  • (2010年4月12日)我试图启用堆检查(使用gflags),但它减慢了应用程序很多…

可能的解决scheme(我知道),不能应用

  • “将应用程序迁移到更新的编译器”: 我们正在研究这个,但目前这不是一个解决scheme。
  • “启用pageheap(正常或完整)”: 我们无法在生产机器上启用pageheap,因为这会严重影响性能。

我想现在我只记得,如果我忘记了一些东西,我会尽快join。 如果你能给我一些提示或者提出一些可能的解决办法,不要犹豫,回答!

提前感谢您的时间和build议。

您可以尝试用调试堆查看例程来查看代码是否可以找到更接近源代码的错误(您正在使用调试CRT来跟踪这个问题,对吧?):

使用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小时内得到这个效果 但是必须说这个问题不是在崩溃,新线程没有创建。

你可以发布你正在得到的例外吗?

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

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

您发布的调用堆栈看起来并不特别具有启发性。

由于你正在使用VS6和SP6,所以它的STL是OK的。

你能告诉生产系统上的应用程序是否泄漏了任何资源? 运行perfmon可以帮助这个。

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

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

如果在具有真实工作负载的多处理器机器上运行这个工具,那么它将在您的代码中找到在单个数据的线程之间存在主动争用的地方。 您将需要分析这种方式发现的探查器热点,并尝试找到没有被适当的互斥锁包装的东西。

我不是CPU架构专家,但是我的理解是,当CPU要访问一段数据时,系统将检查是否有其他CPU正在访问相同的数据,这是通过观察内存从每个CPU中读取和写入,这个过程称为“窥探”。 Snooping可以确保,如果两个或更多CPU在其每个高速缓存中具有相同的数据,则在修改其中一个数据时,会删除数据的复制副本。 HIT修改事件意味着系统检测到这种情况,并不得不刷新其中一个CPU缓存行。

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

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

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

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

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

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

调试这种类型的崩溃的最简单的方法是使用调试帮助,从窗口,通过pageheap或appverifier,但根据应用程序可能会减慢太多,或增加内存使用率太高,无法使用,这似乎是案子。 你可以尝试使用light pageheap,这会影响较小。

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

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

我将重点讨论如何使用适当的调试符号来构建这个问题,至少对于您的主应用程序来说。 你似乎用“抱歉,我们没有符号”来掩盖这个问题,但是当符号被应用时,栈轨迹可能会显示更多的信息。

这意味着什么:“我们不能生成符号,因为我们正在链接一个图书馆,如果我们正在使用它们,这个图书馆没有链接。”? 这似乎很奇怪。