Windows 10特定的崩溃调用leaveccriticalsection

Windows 10 specific crash on call LeaveCriticalSection

本文关键字:崩溃 调用 leaveccriticalsection Windows      更新时间:2023-10-16

我遇到了Windows 10的线程同步和临界区问题。

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

  • 应用程序有两个线程
  • 线程1调用EnterCriticalSection对象m_CS
  • 线程2试图进入相同的临界区
  • 线程1使用TerminateThread终止线程2
  • 线程1调用leaveccriticalsection

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

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

看堆栈跟踪,它看起来是这样的(最新帧在顶部):

RtlpWakeByAddress
RtlpUnWaitCriticalSection
RtlLeaveCriticalSection

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

在网上我没有找到任何关于这个问题的答案,也没有找到关于Windows 10发生了什么变化的说法。上个月只有reddit上的帖子和1800个Mozilla Firefox的崩溃报告使用了相同的调用栈。我联系了reddit上帖子的作者,他到目前为止还不能解决这个问题。

所以有人处理这个问题,可能有一个解决方案或建议?作为一个解决方案,现在我只看到重新考虑WinAPI TerminateThread的使用,并尽量避免它。另一种方法可能是进行代码重构并考虑应用程序的体系结构。

感谢任何回应。提前感谢

CRITICAL_SECTION的实现在版本之间非常不稳定。当在最后一个Windows版本线程开始等待CRITICAL_SECTION他调用WaitOnAddress函数。好的,它实际上是内部实现- 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可以在任何时间和任何点出现。不要调用这个函数,尤其是在self进程中。

线程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的代码改为使用TryEnterCriticalSection

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

这样做的好处是线程2没有等待临界区,它可以正确地终止自己。一般不建议使用TerminateThread,正如其他人在评论中已经提到的那样。

是的,我可以确认这种行为,并花了3天多的时间在我们的代码中找到内存泄漏,这破坏了我的CRITICAL_SECTION。问题是一个旧的TerminateThread调用。这个程序工作得很好,但现在在Windows10上,我们显然在EnterCriticalSectionLeaveCriticalSection中发生了访问违规。非常感谢你,这让我很开心。