C++ C++;内联汇编(英特尔编译器):LEA和MOV在Windows和Linux中的行为不同
我正在将一个巨大的Windows dll转换为在Windows和Linux上工作。dll有很多用于视频操作的汇编(和SS2指令) 现在,使用Windows上的英特尔Composer XE-2011和Linux上的英特尔Composer XE-2013 SP1中包含的英特尔编译器,该代码在Windows和Linux上都可以很好地编译 但是,在Linux中,当试图调用函数指针时,执行会崩溃。我在gdb中跟踪了代码,实际上函数指针没有指向所需的函数(而在Windows中则指向)。几乎其他一切都很好 这是代码的顺序:C++ C++;内联汇编(英特尔编译器):LEA和MOV在Windows和Linux中的行为不同,c++,c,linux,assembly,intel,C++,C,Linux,Assembly,Intel,我正在将一个巨大的Windows dll转换为在Windows和Linux上工作。dll有很多用于视频操作的汇编(和SS2指令) 现在,使用Windows上的英特尔Composer XE-2011和Linux上的英特尔Composer XE-2013 SP1中包含的英特尔编译器,该代码在Windows和Linux上都可以很好地编译 但是,在Linux中,当试图调用函数指针时,执行会崩溃。我在gdb中跟踪了代码,实际上函数指针没有指向所需的函数(而在Windows中则指向)。几乎其他一切都很好 这
...
mov rdi, this
lea rdx, [rdi].m_sSomeStruct
...
lea rax, FUNCTION_NAME # if replaced by 'mov', works in Linux but crashes in Windows
mov [rdx].m_pfnFunction, rax
...
call [rdx].m_pfnFunction # crash in Linux
其中:
1) “this”有一个结构成员m_sSomeStruct
2) m_sSomeStruct有一个成员m_pfnFunction,它是指向函数的指针
3) 函数_NAME是同一编译单元中的自由函数
4) 所有这些纯汇编函数都声明为裸函数
5) 64位环境
最让我困惑的是,如果我用“mov”指令替换将函数地址加载到rax中的“lea”指令,它在Linux上运行良好,但在Windows上崩溃。我在VisualStudio和gdb中跟踪了代码,显然在Windows中,“lea”给出了正确的函数地址,而在Linux中“mov”给出了正确的函数地址
我试着查看英特尔汇编参考资料,但没有找到多少帮助(除非我找的地方不对)
感谢您的帮助。谢谢
编辑更多详细信息: 1) 我试着用方括号
lea rax, [FUNCTION_NAME]
但这并没有改变Windows和Linux中的行为
2) 我查看了gdb和Windows中的反汇编程序,它们似乎都给出了与我实际编写的相同的指令。更糟糕的是,我试着把这两个lea
/mov
一个接一个地放在一起,当我在gdb的反汇编中查看它们时,在一个#符号后面的指令后面打印的地址(我假设是要存储在寄存器中的地址)实际上是相同的,并且不是函数的正确地址
在gdb反汇编程序中看起来是这样的
lea 0xOffset1(%rip), %rax # 0xSomeAddress
mov 0xOffset2(%rip), %rax # 0xSomeAddress
其中两个(SomeAddress)相同,并且两个偏移量在lea和mov指令之间相差相同,
但不知何故,每次执行后,当我检查寄存器的内容时,mov
似乎输入了正确的值
3) 成员变量m_pfnFunction属于LOAD_FUNCTION类型,定义为
typedef void (*LOAD_FUNCTION)(const void*, void*);
4) 函数名在.h(命名空间内)中声明为
并在.cpp中作为
__declspec(naked) void namespace_name::FUNCTION_NAME(const void* , void*)
{
...
}
5) 我尝试通过添加
#pragma optimize("", off)
但我仍然有同样的问题,我怀疑在后一种情况下链接到DLL的方式是函数名是一个内存位置,它实际上将被设置为函数的加载地址。也就是说,它是对函数的引用(或指针),而不是入口点 我对Win很熟悉(不是另一个),我也看到了调用函数的方式 (1) 生成对该地址的调用,该地址在链接时填写。对于同一模块中的函数来说已经足够正常了,但如果在链接时发现它位于不同的DLL中,则导入库是一个存根,链接器将其视为与任何正常函数相同,但只不过是JMP[??]。导入函数的地址表被安排为在保存地址的字段之前有编码JMP指令的字节。该表在DLL加载时填充 (2) 如果编译器知道函数将位于不同的DLL中,它可以生成更高效的代码:它对位于导入表中的地址的间接调用进行编码。(1)中所示的存根函数有一个与之关联的符号名,而包含地址的实际字段也有一个符号名。它们都是以功能命名的,但有不同的“装饰”。通常,一个程序可能包含对这两者的修正引用 因此,我猜想您使用的符号名与一个编译器上的存根函数匹配,并且(它以类似的方式工作)与另一个平台上的指针匹配。可能汇编器会根据是否声明为已导入而将未混合的名称分配给其中一个或另一个,并且两个工具链上的选项不同
希望有帮助。我想您可以在调试器中查看运行时,看看上面的内容是否有助于解释地址及其周围的内容。在阅读了mov和lea之间的差异后,我觉得在Linux上,函数指针中添加了一个额外的间接级别。
mov
指令导致额外的间接级别被传递,而在没有额外间接级别的Windows上,您将使用lea
您是否碰巧在Linux上使用
PIC
编译?我可以看到添加了额外的间接层。如果使用lea-rax[函数名]
?这在两个平台上得到的结果是否相同?您是否比较了生成的机器代码?优化器可能在做一些有趣的事情。这也是我打赌的,你正在与Linux上的优化器抗争(并且失败),特别是因为你使用了不同版本的编译器。作为一个(非常丑陋和脆弱的)工作环境,您可以使用#if
来使用lea
/mov
,具体取决于在哪个平台上工作。如果mov
工作,而lea
不工作,则意味着函数名
符号是指向Linux中函数的指针。向我们展示函数名的声明
。这是一个导出的函数吗?看到这个问题源于英特尔的旗舰编译器和IDE,我会问他们这个问题(也许会查看一些版本号)
#pragma optimize("", off)