我们有一个Windows 32应用程序,其中一个线程可以通过执行SuspendThread/GetThreadContext/ResumeThread来停止另一个线程以检查其状态[PC等]。
if (SuspendThread((HANDLE)hComputeThread[threadId])<0) // freeze thread
ThreadOperationFault("SuspendThread","InterruptGranule");
CONTEXT Context, *pContext;
Context.ContextFlags = (CONTEXT_INTEGER | CONTEXT_CONTROL);
if (!GetThreadContext((HANDLE)hComputeThread[threadId],&Context))
ThreadOperationFault("GetThreadContext","InterruptGranule");
在极少数情况下,在多核系统上,GetThreadContext返回错误代码5(Windows系统错误代码“拒绝访问”)。
SuspendThread文档似乎清楚地表明,如果没有返回错误,目标线程将被挂起。我们正在检查SuspendThread和ResumeThread的返回状态;他们从不抱怨
为什么我可以挂起一个线程,但不能访问它的上下文?
此博客http://www.dcl.hpi.uni-potsdam.de/research/WRK/2009/01/what-does-suspendthread-really-do/
提示SuspendThread在返回时可能已经开始挂起另一个线程,但该线程尚未挂起。在这种情况下,我可以看到GetThreadContext是有问题的,但这似乎是一种定义SuspendThread的愚蠢方法。(SuspendThread的调用如何知道目标线程实际上何时被挂起?)
编辑:* 我撒谎了 * 我说这是Windows。
好吧,奇怪的事实是,我在Windows XP 64下没有看到这种行为(至少在上周没有,我真的不知道在那之前发生了什么)。但我们一直在Ubuntu 10.x上的Wine下测试这个Windows应用程序。GetThreadContext核心的Wine源代码在第819行包含一个Access Denied返回响应,当尝试获取线程状态由于某种原因失败时。我猜,但似乎Wine GetThreadStatus认为线程可能无法重复访问。我不明白为什么在一个悬浮头之后会是这样,但这是代码。想法呢?
第2章:我又说谎了我说我们只看到Wine的行为。不...我们现在已经找到了一个Vista终极系统,似乎产生同样的错误(再次,很少)。因此,Wine和Windows似乎在一个模糊的案例上达成了一致。它也似乎只是启用Sysinternals进程监视程序的情况下,并导致问题出现在Windows XP 64;我怀疑是海森堡虫。(进程监视器甚至不存在于Wine-tasting(:-)机器或我用于开发的XP 64系统中)。
这到底是什么?
编辑3:2010年9月15日。我已经为SuspendThread、ResumeThread和GetContext添加了对错误返回状态的仔细检查,而不会干扰代码。自从我这样做以来,我还没有在Windows系统上看到这种行为的任何暗示。我还没有回到葡萄酒实验。
2010年11月:奇怪。看起来如果我在VisualStudio 2005下编译这个,它在Windows Vista和7上失败,但在更早的操作系统上不会。如果我在VisualStudio 2010下编译,它不会在任何地方失败。有人可能会把矛头指向VisualStudio 2005,但我怀疑这是一个位置敏感的问题,VS 2005和VS 2010中的不同优化器将代码放置在稍微不同的地方。
2012年11月: Saga 继续。我们在许多XP和Windows 7机器上都看到了这种故障,发生率相当低(每运行几千次就有一次)。我们的Suspend活动应用于主要执行纯计算代码但有时会调用Windows的线程。我不记得在我们的计算代码中线程的PC时看到过这个问题。当然,我看不到线程挂起时的PC,因为GetContext不会给予给我,所以我不能直接确认问题只发生在执行系统调用时。但是,我们所有的系统调用都是通过一个点进行的,到目前为止,证据是当我们挂起时,该点被执行。因此,间接证据表明,线程上的GetContext只有在该线程正在执行系统调用时才会失败。我还没有足够的精力来建立一个批判性的实验来验证这个假设。
5条答案
按热度按时间n3ipq98p1#
让我引用Richter/Nassare的“Windows via C++ 5Ed”,这可能会带来一些启发:
String s(String s);
任何线程都可以调用这个函数来挂起另一个线程(只要你有这个线程的句柄)。不用说(但我还是要说),线程可以挂起自己,但不能恢复自己。与ResumeThread类似,SuspendThread返回线程的前一个挂起计数。一个线程可以被挂起最多127次(在WinNT. h中定义为127次)。请注意,SuspendThread相对于内核模式执行是异步的,但是用户模式执行直到线程恢复才发生。
在真实的生活中,应用程序在调用SuspendThread时必须小心,因为当您试图挂起线程时,您不知道线程可能正在做什么。例如,如果线程试图从堆中分配内存,则线程将在堆上锁定。当其他线程尝试访问堆时,它们的执行将被暂停,直到第一个线程恢复。SuspendThread只有在您确切地知道目标线程是什么(或可能正在做什么),并且您采取极端措施来避免挂起线程导致的问题或死锁时才是安全的。
...
Windows实际上允许您查看线程的内核对象并获取其当前的CPU寄存器集。为此,您只需调用GetThreadContext:
BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);
要调用这个函数,只需分配一个CONTEXT结构,初始化一些标志(结构的ContextFlags成员),指示您想要取回哪些寄存器,并将结构的地址传递给GetThreadContext。然后,该函数填充您请求的成员。
你应该在调用GetThreadContext之前调用SuspendThread;否则,线程可能会被调度,并且线程的上下文可能与您返回的内容不同。一个线程实际上有两个上下文:用户模式和内核模式。GetThreadContext只能返回线程的用户模式上下文。如果您调用SuspendThread来停止线程,但该线程当前正在内核模式下执行,则即使SuspendThread尚未实际挂起该线程,其用户模式上下文也是稳定的。但是线程在恢复之前不能再执行任何用户模式代码,因此您可以放心地认为线程已挂起,GetThreadContext将正常工作。
我的猜测是,如果您只是调用SuspendThread,而线程处于内核模式,并且内核此时正在锁定线程上下文块,则GetThreadContext可能会失败。
也许在多核系统上,一个核心正在处理线程的内核模式执行,它的用户模式刚刚挂起,保持锁定线程的CONTEXT结构,正好在另一个核心调用GetThreadContext的时候。
由于这种行为没有记录,我建议联系微软。
chhkpiq42#
旧的问题,但很高兴看到你仍然保持它更新的状态变化后,经历了另一个超过2年的问题。
您的问题的原因是WoW 64的x64版本的翻译层中存在一个错误,如下所示:
http://social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/1558e9ca-8180-4633-a349-534e8d51cf3a
在WoW 64下的GetThreadContext中有一个相当严重的错误,这使得它返回陈旧的内容,这使得它在许多情况下无法使用。内容以用户模式存储这就是为什么你认为值不是null,但在陈旧的内容中它仍然是null。
这就是为什么它在较新的操作系统上失败,而不是旧的,尝试在Windows 7 32位操作系统上运行它。
至于为什么这个bug在Visual Studio 2010 / 2012上构建的解决方案中似乎不太经常发生,很可能是编译器正在做一些事情来缓解大部分问题,为此,您应该检查2005和2010生成的IL,看看有什么区别。例如,如果项目构建时没有进行优化,问题会发生吗?
最后,一些进一步的阅读:
http://www.nynaeve.net/?p=129
6za6bjd03#
挂起拥有
CriticalSection
的线程有一些特殊的问题。我现在找不到一个很好的参考,但有is one mention of it on Raymond Chen's blog和another mention on Chris Brumme's blog。基本上,如果您不幸在线程访问OS锁(例如堆锁、DllMain
锁等)时调用SuspendThread
,那么可能会发生非常奇怪的事情。我想这是你遇到的情况 * 非常罕见 *。在处理器产生类似
Sleep(0)
的输出后,重试对GetThreadContext
的调用是否有效?rta7y2nd4#
可能是线程安全问题。你确定hComputeThread结构没有在你下面改变吗?也许当你调用suspend的时候线程正在退出?这可能会导致suspend成功,但是当您调用get context时,它已经消失,句柄无效。
huus2vyu5#
在拥有同步对象(如互斥体或临界区)的线程上调用SuspendThread,如果调用线程试图获取挂起线程拥有的同步对象,则可能导致死锁。- MSDN