我有一段C ++代码(在GNU / Linux环境下用g ++编译)加载一个函数指针(它是如何做的无关紧要),用一些内联汇编将一些参数压入堆栈然后调用该功能,代码如下:
unsigned long stack[] = { 1, 23, 33, 43 };
/* save all the registers and the stack pointer */
unsigned long esp;
asm __volatile__ ( "pusha" );
asm __volatile__ ( "mov %%esp, %0" :"=m" (esp));
for( i = 0; i < sizeof(stack); i++ ){
unsigned long val = stack[i];
asm __volatile__ ( "push %0" :: "m"(val) );
}
unsigned long ret = function_pointer();
/* restore registers and stack pointer */
asm __volatile__ ( "mov %0, %%esp" :: "m" (esp) );
asm __volatile__ ( "popa" );
我想添加某种
#ifdef _LP64
// 64bit inline assembly
#else
// 32bit version as above example
#endif
但我不知道64位机器的内联汇编,有人可以帮助我吗?
由于
答案 0 :(得分:4)
虽然在内联汇编中使用适当的参数调用函数指针应该不会有什么问题,但我不认为在x64中天真地重新编码会对你有帮助,因为要使用的调用约定很可能不同(32位和64位linux的默认值肯定不同)。有关详细信息,请查看here。所以我想,如果你在这种情况下没有内联汇编就可以逃脱(参见另一个答案),那么移植起来会更容易。
编辑:好的,我看到你可能不得不使用汇编。这里有一些指示。
根据Agner Fog的文档,linux x64使用RDI,RSI,RDX,RCX,R8,R9和XMM0-XMM7进行参数传输。这意味着为了实现您想要的(忽略浮点使用),您的函数必须:
(1)保存所有需要保存的寄存器(RBX,RBP,R12-R15):在堆栈上留出空间并将这些寄存器移到那里。这将是(英特尔语法)的重点:
sub rsp, 0xSomeNumber1
mov [rsp+i*8], r# ; insert appropriate i for each register r# to be moved
(2)评估您必须通过堆栈传递给目标函数的参数数量。使用它来预留堆栈上的所需空间(sub rsp, 0xSomeNumber2
),同时考虑0xSomeNumber1
,以便堆栈在末尾对齐16字节,即rsp
必须是16.此后不要修改rsp
,直到被调用函数返回为止。
(3)将函数参数加载到堆栈(如果需要)和用于参数传输的寄存器中。在我看来,如果你最后开始使用堆栈参数和加载寄存器参数,这是最简单的。
;loop over stack parameters - something like this
mov rax, qword ptr [AddrOfFirstStackParam + 8*NumberOfStackParam]
mov [rsp + OffsetToFirstStackParam + 8*NumberOfStackParam], rax
根据您设置例程的方式,第一个堆栈参数等的偏移量可能不一致。然后设置寄存器传递的参数数量(跳过那些你不需要的参数):
mov r9, Param6
mov r8, Param5
mov rcx, Param4
mov rdx, Param3
mov rsi, Param2
mov rdi, Param1
(4)使用与上面不同的寄存器调用目标函数:
call qword ptr [r#] ; assuming register r# contains the address of the target function
(5)恢复保存的寄存器并将rsp
恢复为进入函数时的值。如有必要,将被调用函数的返回值复制到您想要的位置。就是这样。
注意:上面的草图没有考虑要在XMM寄存器中传递的浮点值,但适用相同的原则。 免责声明:我在Win64上做过类似的事情,但从未在Linux上做过,所以我可能会忽略一些细节。请仔细阅读,仔细编写代码并进行测试。
答案 1 :(得分:2)
并未真正回答您的问题,但我认为您可以通过setcontext
(或makecontext
)以独立于平台的方式实现这一目标。
答案 2 :(得分:1)
主要问题:
从x86到x64的更改摘要:
我在Windows上将一些内联x86代码移植到了x64。您一定要花些时间阅读x64指令集,并阅读操作系统的调用约定。 Windows上的变化是激进的,新的调用约定要简单得多。我怀疑GNU / Linux的变化也会有所不同,我绝对不会认为它是一样的。
我同意之前的回答,如果您可以使用替代方法而不是本地编码,请执行此操作。在我的情况下,我无法避免它。