Windows 10特定崩溃时调用LeaveCriticalSection

时间:2016-09-22 09:52:57

标签: c++ multithreading winapi windows-10 critical-section

我遇到了Windows 10上的线程同步和关键部分的问题。

在这种情况下,应用程序将崩溃:

  • 应用程序有两个主题。
  • 线程1使用对象m_CS
  • 调用EnterCriticalSection
  • 线程2然后尝试进入相同的关键部分
  • 线程1使用TerminateThread终止线程2
  • 线程1调用LeaveCriticalSection

在以前我能够测试的Windows版本(7,8,8.1)中,这可以正常工作。线程2终止,线程1毫无例外地离开临界区。

在Windows 10上,当线程1离开临界区时,应用程序因访问冲突而崩溃。只有在等待EnterCriticalThread时终止另一个线程时才会发生这种情况。

查看堆栈跟踪它看起来如此(顶部的最新帧):

RtlpWakeByAddress
RtlpUnWaitCriticalSection
RtlLeaveCriticalSection

我花了很多时间来调试这个问题。在我的情况下,调用LeaveCriticalSection时m_CS完全没问题。我调试并花了一些时间来分析ntdll.dll函数的反汇编代码。在执行RtlpUnWaitCriticalSection期间,似乎某些对象损坏,然后在发生崩溃时传递给RtlpWakeByAddress。基本上,ntdll.dll能够修改CRITICAL_SECTION对象的属性,例如RtlLeaveCriticalSection中的锁定计数。

在网络上,我没有找到任何关于这个或声明的答案在Windows 10中发生了什么变化。在过去的一个月中,只有reddit和~1800的Mozilla Firefox崩溃报告与相同的调用堆栈。我联系了reddit上的帖子的作者,到目前为止他无法解决这个问题。

所以任何人都对此问题进行了处理,可能会对此进行修复或建议吗?作为现在的解决方案,我只看到重新考虑WinAPI TerminateThread的使用并尽可能地避免使用它。另一种可能是进行代码重构并考虑应用程序架构的方法。

任何回复都表示赞赏。 提前致谢

3 个答案:

答案 0 :(得分:4)

从版本到版本的CRITICAL_SECTION非常不稳定的实现。当在最后一个Windows版本线程开始等待CRITICAL_SECTION时,他调用WaitOnAddress函数。好吧,真的是ntdll内部实现 - RtlpWaitOnAddress,但这不会改变主旨。此函数内部调用RtlpAddWaitBlockToWaitList - 这里是关键点 - 在线程堆栈上分配WaitBlock,并将指向此等待块的指针添加到List。然后,当CRITICAL_SECTION的所有者离开时,他会调用WakeByAddressSingle(实际上是内部实现RtlpWakeByAddress)并且此函数从列表中弹出第一个WaitBlock,从中提取线程ID并调用NtAlertThreadByThreadId (来自win 8.1的新api) - 唤醒某些线程在EnterCriticalSection中等待。但当你终止线程时,等待EnterCriticalSection - 他的堆栈被释放。所以WaitBlock块的地址变得无效。因此,当尝试从WaitBlock读取线程ID(死线程堆栈)时,调用RtlpWakeByAddres s(作为LeaveCriticalSection的一部分)的线程获得访问冲突。 结论 - 如果你调用TerminatedThread - 进程已经处于不稳定状态,bug可以随时随地进行。所以 - 不要调用此函数,尤其是自我处理。

答案 1 :(得分:4)

  

线程1使用TerminateThread终止线程2

不要那样做。看起来它可能适用于其他Windows版本,但是你无法确切地知道正在发生什么副作用并向你隐瞒。

来自https://msdn.microsoft.com/en-us/library/windows/desktop/ms686717(v=vs.85).aspx

  

TerminateThread是一个危险的函数,只能用于   最极端的情况。只有你这样才能调用TerminateThread   确切知道目标线程正在做什么,并控制所有   目标线程当时可能正在运行的代码   终止。例如,TerminateThread可以导致   以下问题:

     
      
  • 如果目标线程拥有临界区,则不会释放临界区。
  •   
  • 如果目标线程正在从堆中分配内存,则不会释放堆锁。
  •   
  • 如果目标线程在终止时正在执行某些kernel32调用,则线程进程的kernel32状态可能是   不一致。
  •   
  • 如果目标线程正在操纵共享DLL的全局状态,则DLL的状态可能会被破坏,从而影响其他用户   DLL。
  •   

你应该做的是与线程2进行通信,让线程2正确安全地关闭自己。

答案 2 :(得分:1)

我会更改线程2的代码以使用TryEnterCriticalSection

if(!TryEnterCriticalSection(&m_CS)) {
    return 0;    // Terminate thread
}
//code
LeaveCriticalSection(&m_CS);

这样做的好处是线程2不会在临界区等待,它可以正常终止。通常不建议使用TerminateThread,正如评论中其他人已经提到的那样。