简体   繁体   English

C ++内联汇编(Intel编译器):LEA和MOV在Windows和Linux中的行为不同

[英]C++ inline assembly (Intel compiler): LEA and MOV behaving differently in Windows and Linux

I am converting a huge Windows dll to work on both Windows and Linux. 我正在转换一个巨大的Windows dll以在Windows和Linux上均可使用。 The dll has a lot of assembly (and SS2 instructions) for video manipulation. 该dll具有许多用于视频处理的程序集(和SS2指令)。

The code now compiles fine on both Windows and Linux using Intel compiler included in Intel ComposerXE-2011 on Windows and Intel ComposerXE-2013 SP1 on Linux. 现在,使用Windows上的Intel ComposerXE-2011和Linux上的Intel ComposerXE-2013 SP1中包含的Intel编译器,可以在Windows和Linux上很好地编译代码。

The execution, however, crashes in Linux when trying to call a function pointer. 但是,在尝试调用函数指针时,执行会在Linux中崩溃。 I traced the code in gdb and indeed the function pointer doesn't point to the required function (whereas in Windows in does). 我在gdb中跟踪了代码,实际上函数指针没有指向所需的函数(而Windows中是)。 Almost everything else works fine. 几乎所有其他东西都可以正常工作。

This is the sequence of code: 这是代码序列:

...
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

where: 哪里:

1) 'this' has a struct member m_sSomeStruct. 1)'this'具有结构成员m_sSomeStruct。

2) m_sSomeStruct has a member m_pfnFunction, which is a pointer to a function. 2)m_sSomeStruct具有成员m_pfnFunction,它是指向函数的指针。

3) FUNCTION_NAME is a free function in the same compilation unit. 3)FUNCTION_NAME是同一编译单元中的自由函数。

4) All those pure assembly functions are declared as naked. 4)所有这些纯汇编函数都声明为裸函数。

5) 64-bit environment. 5)64位环境。

What is confusing me the most is that if I replace the 'lea' instruction that is supposed to load the function's address into rax with a 'mov' instruction, it works fine on Linux but crashes on Windows. 最让我感到困惑的是,如果我用“ mov”指令替换应该将函数地址加载到rax的“ lea”指令,则在Linux上可以正常工作,但在Windows上会崩溃。 I traced the code in both Visual Studio and gdb and apparently in Windows 'lea' gives the correct function address, whereas in Linux 'mov' does. 我在Visual Studio和gdb中都跟踪了代码,显然在Windows中“ lea”给出了正确的函数地址,而在Linux中“ mov”给出了正确的地址。

I tried looking into the Intel assembly reference but didn't find much to help me there (unless I wasn't looking in the right place). 我尝试查看Intel汇编参考,但是在这里没有什么帮助(除非我没有在正确的位置查找)。

Any help is appreciated. 任何帮助表示赞赏。 Thanks! 谢谢!


Edit More details: 编辑更多详细信息:

1) I tried using square brackets 1)我尝试使用方括号

lea    rax, [FUNCTION_NAME]

but that didn't change the behaviour in Windows nor in Linux. 但这并没有改变Windows和Linux中的行为。

2) I looked at the disassembler in gdb and Windows, seem to both give the same instructions that I actually wrote. 2)我在gdb和Windows中查看了反汇编程序,似乎都给出了与我实际编写的相同的指令。 What's even worse is that I tried putting both lea / mov one after the other, and when I look at them in disassembly in gdb, the address printed after the instruction after a # sign (which I'm assuming is the address that's going to be stored in the register) is actually the same, and is NOT the correct address of the function. 更糟糕的是,我尝试将lea / mov放在另一个位置,当我在gdb中反汇编它们时,在指令后的#号后面打印了地址(我假设这是将要发送的地址)。被存储在寄存器中)实际上是相同的,并且不是该函数的正确地址。

It looked like this in gdb disassembler 在gdb反汇编器中看起来像这样

lea  0xOffset1(%rip), %rax   # 0xSomeAddress
mov  0xOffset2(%rip), %rax   # 0xSomeAddress

where both (SomeAddress) were identical and both offsets were off by the same amount of difference between lea and mov instructions, But somehow, the when I check the contents of the registers after each execution, mov seem to put in the correct value!!!! 两者(SomeAddress)相同,并且两个偏移量因lea和mov指令之间的相同差异而偏移,但是不知何故,当我在每次执行后检查寄存器的内容时​​, mov似乎输入了正确的值! !!

3) The member variable m_pfnFunction is of type LOAD_FUNCTION which is defined as 3)成员变量m_pfnFunction的类型为LOAD_FUNCTION,其定义为

typedef void (*LOAD_FUNCTION)(const void*, void*);

4) The function FUNCTION_NAME is declared in the .h (within a namespace) as 4)函数FUNCTION_NAME在.h(在命名空间内)声明为

void FUNCTION_NAME(const void* , void*);

and implemented in .cpp as 并在.cpp中实现为

__declspec(naked) void namespace_name::FUNCTION_NAME(const void* , void*)
{
...
}

5) I tried turning off optimizations by adding 5)我尝试通过添加来关闭优化

#pragma optimize("", off)

but I still have the same issue 但我仍然有同样的问题

Off hand, I suspect that the way linking to DLLs works in the latter case is that FUNCTION_NAME is a memory location that actually will be set to the loaded address of the function. 临时而言,我怀疑链接到DLL的方式在后一种情况下有效,因为FUNCTION_NAME是实际上将被设置为函数的加载地址的内存位置。 That is, it's a reference (or pointer) to the function, not the entry point. 也就是说,它是对函数的引用(或指针),而不是入口点。

I'm familiar with Win (not the other), and I've seen how calling a function might either 我熟悉Win(而不是其他),并且我已经知道如何调用函数

(1) generate a CALL to that address, which is filled in at link time. (1)生成对该地址的CALL,该地址在链接时填写。 Normal enough for functions in the same module, but if it's discovered at link time that it's in a different DLL, then the Import Library is a stub that the linker treats the same as any normal function, but is nothing more than JMP [????]. 对于同一模块中的函数来说,足够正常,但是如果在链接时发现它位于不同的DLL中,则导入库是一个存根,链接器将其视为与任何正常函数相同,但仅不过是JMP [? ??]。 The table of addresses to imported functions is arranged to have bytes that code a JMP instruction just before the field that will hold the address. 导入函数的地址表安排有一些字节,这些字节在将保存该地址的字段之前编码一个JMP指令。 The table is populated at DLL Load time. 该表在DLL加载时填充。

(2) If the compiler knows that the function will be in a different DLL, it can generate more efficient code: It codes an indirect CALL to the address located in the import table. (2)如果编译器知道该函数将在其他DLL中,则它可以生成更有效的代码:将间接CALL编码为导入表中的地址。 The stub function shown in (1) has a symbol name associated with it, and the actual field containing the address has a symbol name too. (1)中显示的存根函数具有与其关联的符号名称,并且包含地址的实际字段也具有符号名称。 They both are named for the function, but with different "decorations". 它们都以函数命名,但是具有不同的“装饰”。 In general, a program might contain fixup references to both. 通常,程序可能包含对两者的修正引用。

So, I conjecture that the symbol name you used matches the stub function on one compiler, and (that it works in a similar way) matches the pointer on the other platform. 因此,我推测您使用的符号名称与一个编译器上的存根函数匹配,并且(以类似的方式工作)与另一平台上的指针匹配。 Maybe the assembler assigns the unmangled name to one or the other depending on whether it is declared as imported, and the options are different on the two toolchains. 也许汇编程序会根据未声明的名称将其分配给另一个,这取决于它是否被声明为已导入,并且两个工具链上的选项有所不同。

Hope that helps. 希望能有所帮助。 I suppose you could look at run-time in a debugger and see if the above helps you interpret the address and the stuff around it. 我想您可以在调试器中查看运行时,看看上面的内容是否可以帮助您解释地址及其周围的内容。

After reading the difference between mov and lea here What's the purpose of the LEA instruction? 在阅读了mov和lea之间的区别之后,这里的LEA指令的目的是什么? it looks to me like on Linux there is one additional level of indirection added into the function pointer. 在我看来,在Linux上,函数指针中又增加了一个间接级别。 The mov instruction causes that extra level of indirection to be passed through, while on Windows without that extra indirection you would use lea . mov指令会导致传递额外的间接级别,而在没有该额外间接的Windows上,您将使用lea

Are you by any chance compiling with PIC on Linux? 您是否有机会在Linux上使用PIC进行编译? I could see that adding the extra indirection layer. 我可以看到添加了额外的间接层。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM