CreateThread()在64位Windows上失败,在32位Windows上运行。为什么?

时间:2010-06-15 15:06:57

标签: windows 64-bit multithreading alignment 32bit-64bit

操作系统:Windows XP 64位,SP2。

我有一个不寻常的问题。我正在将一些代码从32位移植到64位。 32位代码工作得很好。但是当我为64位版本调用CreateThread()时,调用失败。我有三个失败的地方。 2调用CreateThread()。 1调用beginthreadex()调用CreateThread()。

所有三个调用都失败,错误代码为0x3E6,“对内存位置的访问无效”。

问题是所有输入参数都是正确的。

HANDLE  h;
DWORD   threadID;

h = CreateThread(0,            // default security
                 0,            // default stack size
                 myThreadFunc, // valid function to call
                 myParam,      // my param
                 0,            // no flags, start thread immediately
                 &threadID);

对CreateThread()的所有三次调用都是从我在程序执行开始时注入目标程序的DLL中生成的(这是在程序到达main()/ WinMain()之前) 。如果我通过说菜单从目标程序(相同的参数)调用CreateThread(),它就可以工作。相同的参数等奇怪。

如果我传递NULL而不是& threadID,它仍然会失败。

如果我将NULL作为myParam传递,它仍然会失败。

我没有从DllMain()内部调用CreateThread,所以这不是问题。我很困惑,在谷歌等搜索没有显示任何相关的答案。

如果有人以前见过这个或有任何想法,请告诉我。

感谢阅读。

ANSWER

简答:x64上的堆栈帧需要16字节对齐。

更长的回答: 在对调试器墙猛烈抨击并发布对各种建议的响应之后(所有这些都有所帮助,促使我尝试新的方向)我开始在调用CreateThread()之前探索堆栈内容的假设。事实证明这是一个红鲱鱼,但它确实导致了解决方案。

向堆栈添加额外数据会更改堆栈帧对齐。迟早,其中一个测试会使您进行16字节堆栈帧对齐。那时代码工作了。所以我回溯了我的步骤并开始将NULL数据放入堆栈而不是我认为正确的值(我一直在推送返回地址以伪造一个调用帧)。它仍然有效 - 所以数据并不重要,它必须是实际的堆栈地址。

我很快意识到堆栈的16字节对齐。以前我只知道数据的8字节对齐。这microsoft document explains all the alignment requirements

如果堆栈帧在x64上没有16字节对齐,则编译器可能会在将数据推入堆栈时将大(8字节或更多)数据放在错误的对齐边界上。

因此我面临的问题 - 使用未在16字节边界上对齐的堆栈调用挂钩代码。

对齐要求的快速摘要,表示为大小:对齐

  • 1:1
  • 2:2
  • 4:4
  • 8:8
  • 10:16
  • 16:16

大于8个字节的任何内容在2边界的下一个幂上对齐。

我认为微软的错误代码有点误导。最初的STATUS_DATATYPE_MISALIGNMENT可以表示为STATUS_STACK_MISALIGNMENT,这将更有帮助。但随后将STATUS_DATATYPE_MISALIGNMENT转换为ERROR_NOACCESS - 这实际上掩盖并误导了问题是什么。非常无益。

感谢所有发布建议的人。即使我不同意这些建议,它们也促使我在各方面进行测试(包括我不同意的方向)。

在此处写一篇关于数据类型错位问题的更详细说明:64 bit porting gotcha #1! x64 Datatype misalignment.

4 个答案:

答案 0 :(得分:1)

64位会产生影响的唯一原因是64位线程需要64位对齐值。如果threadID不是64位对齐,则可能导致此问题。


好的,这个想法不是它。你确定在main / WinMain之前调用CreateThread是有效的吗?它可以解释为什么它在菜单中起作用 - 因为它在main / WinMain之后。

另外,我会三次检查myParam的生命周期。在传入函数之前很久就会返回CreateThread(我从经验中知道)。


发布线程例程的代码(或只是几行)。


我突然想到:您确定要将64位代码注入64位进程吗?因为如果你有一个64位的CreateThread调用,并试图将其注入到在WOW64下运行的32位进程中,那么可能会发生不好的事情。


开始认真地用尽想法。编译器是否报告任何警告?


该错误可能是由于主机程序中的错误而不是DLL造成的?还有一些其他代码,例如在使用__declspec(导入/导出)时加载DLL,它发生在main / WinMain之前。例如, DLLMain,其中有一个错误。

答案 1 :(得分:0)

我从事在Windows下使用并行线程的业务 用于计算。没有有趣的事情,没有dll调用,当然 没有回电。以下在32位窗口中工作。我在要为程序保留的区域内设置用于计算的堆栈。 有关区域和起始地址的所有相关数据都包含在 作为参数3传递给CreateThread的数据结构。 被调用的地址包含一个小的汇编程序 使用此数据结构。 实际上,此例程找到了要返回到堆栈的地址, 然后是数据结构的地址。 没有理由对此进行深入探讨。它可以正常工作并计算 在一个线程中,低于2,000,000,000的素数就可以了, 分为两个线程或20个线程。

现在使用64位的CreateThread不会推送数据的地址 结构体。这似乎难以置信,所以我给你看抽烟的枪, 调试会话的转储。 enter image description here

在右下角的子窗口中,您可以看到堆栈,并且 在零的海洋中,只有返回地址。 我用来填充参数的机制在32位和64位之间是可移植的。 没有其他呼叫显示出字大小之间的差异。 此外,为什么代码地址可以工作,但数据地址不能工作?

最重要的是:可以期望CreateThread以64位和32位相同的方式在堆栈上传递数据参数,然后执行子例程调用。在汇编程序级别,它不是那样工作的。如果有任何隐藏的要求,例如在C ++中自动填充的RSP非常讨厌。

P.S。不,没有16字节对齐问题。那已经是我多年了。

答案 2 :(得分:0)

我今天遇到了这个问题。然后,我通过rohitab的Windows API Monitor v2将所有参数输入_beginthread / CreateThread / NtCreateThread中进行了检查。每个参数都正确对齐(AFAIK)。

API Monitor Screenshot


那么STATUS_DATATYPE_MISALIGNMENT是哪里来的?

NtCreateThread的前几行验证从用户模式传递的参数。

ProbeForReadSmallStructure (ThreadContext, sizeof (CONTEXT), CONTEXT_ALIGN);

对于i386

#define CONTEXT_ALIGN   (sizeof(ULONG))

针对amd64

#define STACK_ALIGN (16UI64)
...
#define CONTEXT_ALIGN STACK_ALIGN

在amd64上,如果ThreadContext指针未对齐16个字节,则NtCreateThread将返回STATUS_DATATYPE_MISALIGNMENT

CreateThread(实际上是CreateRemoteThread)从堆栈中分配了ThreadContext,并且没有做任何特别的事情来保证满足对齐要求。如果您的每一段代码都遵循Microsoft x64调用约定,那么一切将顺利进行,不幸的是,这对我而言并非如此。

PS:相同的代码可能在更新的Windows(例如Vista和更新的Windows)上运行。我没有检查。我在Windows Server 2003 R2 x64上遇到此问题。

答案 3 :(得分:-2)