【问题标题】:CreateThread() fails on 64 bit Windows, works on 32 bit Windows. Why?CreateThread() 在 64 位 Windows 上失败,在 32 位 Windows 上工作。为什么?
【发布时间】:2011-03-04 01:14:07
【问题描述】:

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

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

所有三个调用都失败,错误代码为 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,所以这不是问题。我很困惑,在 Google 等上搜索没有显示任何相关答案。

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

感谢阅读。

回答

简答: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.

【问题讨论】:

  • myParam 到底是什么(即它的声明和初始化)?
  • myParam 在所有情况下都是指向某个内存的指针(通常是“this”)。指针在传递时始终是有价值的。为什么这很重要?它不会访问它的内容,它只是一个要传递给线程函数的值。
  • @Michael,我尝试将 NULL 作为 myParam 传递。仍然失败。
  • 你能看到错误信息是什么吗?您可以为此目的使用此功能pastebin.com/h72GM9fJ
  • @Luke,值:相同。绝对地。我现在已经扩展了检查。即使我传入一个在 16 字节边界上对齐的过程、一个 16 字节对齐的参数和一个指向 16 字节边界上的 DWORD 的 DWORD 指针,我仍然会失败。我认为 8 字节对齐就足够了,但发现对某些需要 16 字节对齐的结构(例如:CONTEXT)的引用,所以我这样做太过分了。没有快乐,仍然失败。

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


【解决方案1】:

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


好吧,不是这个想法。您确定在 main/WinMain 之前调用 CreateThread 有效吗?它将解释为什么它在菜单中工作 - 因为那是在 main/WinMain 之后。

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


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


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


开始严重耗尽想法。编译器是否报告任何警告?


错误可能是由于宿主程序中的错误,而不是 DLL 造成的吗?还有一些其他代码,例如如果您使用 __declspec(import/export) 加载一个 DLL,发生在 main/WinMain 之前。例如,如果 那个 DLLMain 中存在错误。

【讨论】:

  • 我刚刚重新测试了这个。在其中一个调用中,threadID 是 64 位对齐的。它仍然失败。 threadID 无论如何都不是问题,因为它是一个 DWORD 值,指向它的参数是一个指针(一个 64 位值)。
  • 对齐有最小的sizeof,但在某些情况下必须扩展。许多线程值必须具有(地址宽度)对齐。 msdn.microsoft.com/en-us/library/ms684122(v=VS.85).aspx 编辑:没看到你重新测试过。
  • @Stephen - 进行快速测试,尝试为线程 ID 指针传递 NULL 并查看问题是否消失。
  • 如果 threadID 参数设置为 NULL 而不是 threadID 的地址,它仍然会失败。
  • myParam 有效。绝对确定。它在目标应用程序的生命周期内有效。它不能超出范围或被删除。在 main/winMain 之前?我不知道。它对 32 位有效,我看不出为什么它不适用于 64 位。 WINAPI 让我可以毫无问题地做比 CreateThread 更危险的事情(例如加载我的 DLL 来完成我需要做的工作)。因此,我怀疑这是问题所在。我确实想知道 Windows 是否添加了一些额外的安全检查,而这些检查对我来说是失败的。
【解决方案2】:

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


那么,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 Server 2003 R2 x64 上遇到了这个问题。

【讨论】:

    【解决方案3】:

    我的业务是在 windows 下使用并行线程 用于计算。没有有趣的事情,没有 dll 调用,当然 没有回电的。以下适用于 32 位窗口。我为我的计算设置了堆栈,正好在为我的程序保留的区域内。 所有关于区域和起始地址的相关数据都包含在 作为参数 3 传递给 CreateThread 的数据结构。 被调用的地址包含一个小的汇编程序 使用这种数据结构。 事实上,这个例程在堆栈上找到要返回的地址, 然后是数据结构的地址。 没有理由在这方面走得太远。它只是工作,它计算 一个线程中低于 2,000,000,000 的素数数量就好了, 在两个线程或 20 个线程中。

    现在 64 位的 CreateThread 不会推送数据的地址 结构体。这似乎难以置信,所以我向你展示确凿证据, 调试会话的转储。

    在右下角的子窗口中,您可以看到堆栈,并且 只有返回地址,在零的海洋中。 我用来填充参数的机制在 32 位和 64 位之间是可移植的。 没有其他调用显示字长之间的差异。 还有,为什么代码地址有效,数据地址无效?

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

    附:不,不存在 16 字节对齐问题。这在我身后很久了。

    【讨论】:

      【解决方案4】:

      尝试改用 _beginthread() 或 _beginthreadex(),您不应该直接使用 CreateThread。

      See this previous question.

      【讨论】:

      • 克里斯,这是错误的答案。我知道我是否应该使用 beginthreadex() 的 CreateThread,答案是 CreateThread()。如果你阅读了这个问题,你会看到最终我回答了这个问题本身(堆栈错位)。最后,调用 beginthread() 仍然会失败,因为正如我在答案中指出的那样,堆栈未对齐(并且在调用 beginthread 时调用 CreateThread 时仍然会未对齐)。
      • 发布您的解决方案评论并将其设置为答案,以便人们可以真正找到它(我的评论仍然有效,不要使用 CreateThread,您可以用谷歌搜索为什么 ;-) - 非常感谢。
      • “你不应该”。你不能使用那个不合格的。 CreateThread 是一个原始调用,Microsoft 已详细记录它可以在任何语言中使用。 beginthread() 对运行时间库很好。要实现非 c 的东西,它只是笨拙的,最终在后台调用 CreateThread。
      • 我遇到了这个问题,_beginthread 只是返回 EINVAL 而没有任何细节。这个答案没有帮助。
      猜你喜欢
      • 1970-01-01
      • 2012-04-17
      • 2015-11-15
      • 2017-01-22
      • 2018-01-25
      • 2010-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多