获取/检查进程 win32 的内部 kernel32 状态(为了安全使用 TerminateThread )

Posted

技术标签:

【中文标题】获取/检查进程 win32 的内部 kernel32 状态(为了安全使用 TerminateThread )【英文标题】:get/check inner kernel32 state for process win32 (for safe usage of TerminateThread ) 【发布时间】:2020-08-03 04:27:49 【问题描述】:

我为用户可用的线程编写了一个带有终止选项的线程池。如中所述 Documentation of API terminateThread(),

如果目标线程在终止时正在执行某些 kernel32 调用,则线程进程的 kernel32 状态可能不一致。

我可以自己验证这个问题:在这种情况下终止线程会导致内存分配问题(以及其他问题),但修复该情况同时解决了问题。

问题

    所以,我想在每次使用 terminateThread() 后检查这个内部状态。如果terminateThread() 导致 kernel32.dll 中进程的内部状态出现问题,我想引发异常 - 并在登录到用户后终止进程(除非仍然可以修复内部状态)。

    这可行吗?也许通过找到相关状态变量的地址(或类似的东西 - 通过匹配 kernel32 的 pdb 文件或其他方式)?这种情况对我来说很糟糕——如果我无法解决它,我要么必须省略 threapool 的终止选项,要么只将线程留给自己。任何提示将不胜感激!

    还有其他win32函数会导致类似问题吗?

    一个。当一个线程调用了一个绝对不会返回的阻塞 kernel32 函数时,为它自己留下一个线程是否安全?

    b.如果win32函数返回,lambda函数被销毁了怎么办?

我为什么要问这个? (补充资料)

我的项目中有一个自定义线程池,我在其中调用了一些有时可能会永远阻塞的 win32 API。因此,我使用超时来调用它们。当达到该超时时,我调用terminateThread() 并让我的线程池返回“不成功的调用状态”。

有时,我当前的应用程序会遇到死锁。我发现这个死锁发生在线程池中,所以我正在寻找terminateThread() 的替代方案(例如按照我在问题中描述的那样离开线程)或尝试修复内部状态,或者至少验证是否terminateThread() 是我陷入僵局的根源。

我也想在其他项目中重用这个线程池,所以我应该确保它安全。

更新:问题已解决。

我在我的应用程序中发现了错误: 当我的线程池中的超时时间已经很低(大约 200 毫秒)时,它实际上是对terminateThread() 的调用。 线程在它没有阻塞的时候被杀死(即,如果有更长的超时时间,它会工作并正确返回)。 从内核堆栈跟踪中,我发现在内核模式下,线程终止时互斥锁被锁定,而线程退出时,其他线程已经在等待该互斥锁。

通过将最小超时时间增加到 1000 毫秒,问题似乎首先消失了,但我对此并不满意: 我的解决方案是在达到超时时在堆上创建 lambda,将 lambda 和线程留给自己而不终止,并将其添加到_ToTerminateThreads 的列表中。 列表每 10 分钟终止一次(等待 10 分钟,复制列表,再等待一分钟,然后终止线程并删除 lambda)。

不过,经过测试和数小时的调试,我还是遇到了堆损坏。 最后我发现了以下内容: 我留待删除的线程 写入用户函数已使用的内存(已传递给线程池) - 因为线程池已经返回,他们被释放了。 这导致了最终的问题,因此最终的解决方案是将超时增加到安全的数量。

我建议所有需要此类功能的人将其部署到子进程,并终止该进程而不是使用线程。

我没有回答这个问题,因为主要的 4 个问题尚未得到解答。对于我的问题,我不再需要他们的答案,但他们可能对 *** 的其他成员很感兴趣。

【问题讨论】:

更多阅读 - devblogs.microsoft.com/oldnewthing/?p=91811 当你终止一个线程时,你不能再做任何有意义的事情了。进程已进入无法恢复的状态。事后试图检查情况不会成为解决方案。也许您应该询问您正在尝试解决的真正问题。很明显,您的代码存在与线程池无关的问题。 您在寻找halting problem 的解决方案吗?至于验证您的代码是导致死锁的原因,这很简单。您的流程处于无法向前推进的状态。此时,启动任务管理器,并让它收集一个小型转储。在调试器(如 WinDbg)中加载它,并检查等待周期。 WinDbg 为此提供了方便的 !locks 扩展名。 还要确保考虑到这一点:Must be This Tall to Write Multi-Threaded Code(链接到article)。 @emaditaj - 阅读带有少量大写字母和很长句子的问答并不容易。我选择提出修改建议。请检查重新编写的版本是否仍能正确描述您的问题并正确报告(我相信确实如此,但您永远不知道)。我希望这有助于其他读者使用此问答。 【参考方案1】:

我的问题已解决,尽管它与帖子中的 3 个问题无关。 我尝试以相反的顺序回答它们:

ad 3.b.) 如果外部函数返回并且您的本地 lambda 已被删除,cpu 将不知道这一点,并将尝试将该偏移处的字节处理为CPU 指令。这肯定会搞砸你,所以永远不要那样做!

ad 3.a.) 是的,如果你 100% 确定外部函数永远不会返回,那么离开是安全的(否则返回时会弄乱你的应用程序

    如果您按照 b. 中说明的相同方式删除了其余代码 如果您没有删除 lambda 或者它是一个全局函数,它将运行其余函数,这些函数可能正在编辑已被释放并导致堆损坏的动态分配内存(堆,而不是堆栈),或者只是编辑一些全局变量)。

ad 2.) 我搜索了危险的 winapi 函数,除了TerminateThread() 之外没有找到任何结果。 如果你知道一个,请添加评论或写另一个答案。

ad 1.) 我没有任何解决方案来检查/修复 Microsoft 所指进程的内部 kernel32 状态。 我认为读过 kernel32.dll 源代码的微软人应该回答这个问题。

除了这个 kernel32 状态之外,TerminateThread() 会导致许多其他问题(如资源/堆分配、互斥锁、泄漏等),因此除非您 100% 确定自己在做什么,否则切勿使用它。

阅读文章@RichardCritten 链接在 cmets:TerminateThread()

我的代码中有什么错误?

我正在呼叫TerminateThread(),超时时间很短(300 毫秒)。 随机当机器资源不足时,该功能仍在运行(我的意思是非阻塞调用!)。 我终止了该函数,从而导致内核互斥锁被锁定。 这个锁定的互斥体使所有其他线程等待 - 当它们返回时不会退出。

备注

在没有收到任何答案后,我根据发现的内容回答了我自己的问题。因此,它可能包含一些错误信息。如有错误请指正。

【讨论】:

你可以accept your own answer。 @RitaHan-MSFT 做到了,请您为我的问题投票,我无法创建任何新问题,因为包括此在内的一些旧问题的投票为零或反对票 您可以参考this 和this 来了解“提供一两个更优质的答案并获得支持”或“编辑以改进您的问题”。 "How do I ask a good question?"希望这会有所帮助。

以上是关于获取/检查进程 win32 的内部 kernel32 状态(为了安全使用 TerminateThread )的主要内容,如果未能解决你的问题,请参考以下文章

使用 Wmi win32_Process 执行远程进程 - 获取进程的标准输出

C++/Win32 - 如何迭代特定进程的线程列表并将起始地址解析为模块?

Delphi遍历进程-Win32API

使用win32api检查背景上是否按下了键

如何在 Win32 中定位进程的全局和堆栈区域?

在Win32(c ++)中的另一个进程中写入文本框