操作系统: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,所以这不是问题。我很困惑,在谷歌等搜索没有显示任何相关的答案。
如果有人以前见过这个或有任何想法,请告诉我。
谢谢阅读。
回答
简短回答:x64上的堆栈帧需要16字节对齐。
更长的答案:经过多次撞击调试器墙并发布对各种建议的响应(所有这些都有所帮助,促使我尝试新方向)我开始在调用CreateThread之前探讨堆栈上的内容是什么()。事实证明这是一个红鲱鱼,但它确实导致了解决方案。
向堆栈添加额外数据会更改堆栈帧对齐。迟早,其中一个测试会使您进行16字节堆栈帧对齐。那时代码工作了。所以我回溯了我的步骤并开始将NULL数据放入堆栈,而不是我认为正确的值(我一直在推送返回地址来伪造一个调用帧)。它仍然有效 - 所以数据并不重要,它必须是实际的堆栈地址。
我很快意识到堆栈的16字节对齐。以前我只知道数据的8字节对齐。这microsoft document explains all the alignment requirements。
如果堆栈帧在x64上没有16字节对齐,则编译器可能会在将数据推入堆栈时将大(8字节或更多)数据放在错误的对齐边界上。
因此我面临的问题 - 使用未在16字节边界上对齐的堆栈调用挂钩代码。
对齐要求的快速摘要,表示为大小:对齐
大于8字节的任何内容在2边界的下一个幂上对齐。
我认为微软的错误代码有点误导。最初的STATUS_DATATYPE_MISALIGNMENT可以表示为STATUS_STACK_MISALIGNMENT,这将更有帮助。但随后将STATUS_DATATYPE_MISALIGNMENT转换为ERROR_NOACCESS - 这实际上掩盖并误导了问题是什么。非常无益。
感谢大家发布的建议。即使我不同意这些建议,它们也促使我在各方面进行测试(包括我不同意的方向)。
写下数据类型错位问题的更详细描述:64 bit porting gotcha #1! x64 Datatype misalignment.
64位产生差异的唯一原因是64位线程需要64位对齐值。如果threadID不是64位对齐,则可能导致此问题。
好的,这个想法不是它。你确定在main / WinMain之前调用CreateThread是有效的吗?它可以解释为什么它在菜单中起作用 - 因为它在main / WinMain之后。
另外,我会三次检查myParam的生命周期。 CreateThread在调用传入的函数之前很久就会返回(我从经验中知道)。
发布线程例程的代码(或只是几行)。
它突然发生在我身上:您确定要将64位代码注入64位进程吗?因为如果你有一个64位的CreateThread调用并尝试将其注入到在WOW64下运行的32位进程中,则可能会发生错误。
开始认真地用尽了想法。编译器是否报告任何警告?
该错误可能是由于主机程序中的错误,而不是DLL?还有一些其他代码,例如在使用__declspec(导入/导出)时加载DLL,它发生在main / WinMain之前。例如,如果该DLLMain有错误。
我正在使用Windows下的并行线程进行计算。没有有趣的业务,没有dll通话,当然也没有回拨。以下在32位窗口中工作。我为我的计算设置了堆栈,完全在为我的程序保留的区域内。有关区域和起始地址的所有相关数据都包含在作为参数3传递给CreateThread的数据结构中。调用的地址包含使用此数据结构的小型汇编程序。实际上,这个例程找到了返回堆栈的地址,然后是数据结构的地址。没有理由深入研究这个问题。它只是工作,它计算低于2,000,000,000的素数,在一个线程,两个线程或20个线程中。
现在64位的CreateThread不会推送数据结构的地址。这似乎难以置信,所以我告诉你吸烟枪,一个调试会议的转储。
在右下方的子窗口中,您可以看到堆栈,并且只有返回地址,在零海中。我用来填充参数的机制可以在32到64位之间移植。没有其他电话显示字大小之间的差异。此外,为什么代码地址工作而不是数据地址?
底线:可以预期CreateThread以64位和32位相同的方式传递堆栈上的数据参数,然后进行子程序调用。在汇编程序级别,它不会那样工作。如果有任何隐藏的要求,例如在C ++中自动填充的RSP非常讨厌。
附:没有没有16字节对齐问题。这远远落后于我。
尝试使用_beginthread()或_beginthreadex()代替,不应该直接使用CreateThread。