2

我有一段 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 位机器的内联汇编,任何人都可以帮助我吗?

谢谢

4

3 回答 3

4

虽然在内联汇编中使用适当的参数调用函数指针应该不是什么大问题,但我认为在 x64 中天真地重新编码它不会对您有所帮助,因为要使用的调用约定很可能不同(默认对于 32bit 和 64bit linux 肯定是不同的)。在这里查看详细信息。所以我想,如果在这种情况下你可以在没有内联汇编的情况下逃脱(见另一个答案),移植会更容易。

编辑:好的,我看到您可能必须使用程序集。这里有一些指示。

根据 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 上做过,所以我可能忽略了一些细节。好好阅读,仔细编写代码并好好测试。

于 2010-03-25T19:29:06.787 回答
2

没有真正回答你的问题,但我认为你可以通过使用setcontext(或makecontext)以独立于平台的方式实现这一点。

于 2010-03-25T18:47:01.820 回答
1

主要问题:

  • x64 上缺少 pushad/poppad,您必须推送要保存的各个寄存器
  • 您需要将您的 rsp(64 位堆栈指针)保存在合适的 64 位寄存器(rax?、r8?等)中
  • 调用约定几乎肯定从 32 位更改为 64 位

从 x86 到 x64 的变化总结:

  • 以 E 开头的寄存器现在具有以 R 开头的 64 位等效项。RAX、RBX、RCX、RDX、RDI、RSI、RIP、RSP、RBP。
  • 新寄存器:R8 ... R15
  • 没有推杆,没有推杆

我已将一些内联 x86 代码移植到 Windows 上的 x64。您绝对应该花一些时间阅读 x64 指令集并阅读操作系统的调用约定。Windows 上的变化是彻底的,新的调用约定要简单得多。我怀疑 GNU/Linux 的变化也会有所不同,我绝对不会假设它是相同的。

我同意之前的回答,如果您可以使用其他方法而不是本机编码,请这样做。就我而言,我无法避免它。

于 2010-03-25T19:49:43.440 回答