如何执行一个64位绝对地址的call指令?

How to execute a call instruction with a 64-bit absolute address?

我正在尝试从机器代码调用一个函数——编译和链接时应该有一个绝对地址。我正在创建一个指向所需函数的函数指针,并试图将其传递给调用指令,但我注意到调用指令最多需要一个 16 位或 32 位地址。有没有办法调用绝对64位地址?

我正在为 x86-64 架构部署并使用 NASM 生成机器代码。

如果可以保证可执行文件肯定会映射到底部 4GB 内存,我可以使用 32 位地址,但我不确定在哪里可以找到该信息。

编辑:我不能使用 callf 指令,因为它需要我禁用 64 位模式。

第二次编辑:我也不想将地址存储在寄存器中并调用寄存器,因为这对性能至关重要,我不能承受开销和性能损失间接函数调用。

最终编辑:通过确保我的机器代码映射到前 2GB 内存,我能够使用 rel32 调用指令。这是通过带有 MAP_32BIT 标志的 mmap 实现的(我正在使用 linux):

MAP_32BIT (since Linux 2.4.20, 2.6) Put the mapping into the first 2 Gigabytes of the process address space. This flag is supported only on x86-64, for 64-bit programs. It was added to allow thread stacks to be allocated somewhere in the first 2GB of memory, so as to improve context- switch performance on some early 64-bit processors. Modern x86-64 processors no longer have this per‐ formance problem, so use of this flag is not required on those systems. The MAP_32BIT flag is ignored when MAP_FIXED is set.

相关: 有更多关于 JITing 的内容,特别是在它要调用的代码附近分配 JIT 缓冲区,因此您可以使用高效的 call rel32。或者如果没有怎么办。

另外 Call an absolute pointer in x86 machine code 是关于 calljmp 到绝对地址的一个很好的规范问答。


TL:DR: 要按名称调用函数,只需像正常人一样使用 call func,让汇编器 + 链接器来处理它。既然你说你正在使用 NASM,我猜你实际上是在用汇编程序生成机器代码。这听起来像是一个更复杂的问题,但我认为你只是想问一下正常的方法是否安全。


Indirect call r/m64 (FF /2) 在 64 位模式下采用 64 位寄存器或内存操作数。

所以你可以做到

func equ  0x123456789ab
; or if func is a regular label

mov   rax, func          ; mov r64, imm64,  or mov r32, imm32 if it fits
call  rax

通常情况下,您会使用 lea rax, [rel func] 将标签地址放入寄存器中,但如果它是可编码的,那么您只需使用 call rel32.


或者,如果你知道你的机器码将存储在什么地址,你可以使用正常的直接call rel32编码,在你计算出地址差异之后从目标到 call 指令结束。

如果您不想使用间接调用,那么 rel32 编码是您唯一的选择。确保您的机器代码进入低 2GiB,以便它可以到达低 4GiB 中的任何地址。


if I could be guaranteed that the executable would be for sure mapped to the bottom 4GB of memory

是的,这是Linux、Windows和OS X的默认代码模型。AMD64调用/跳转指令,以及RIP-相对寻址,只使用rel32 编码,因此所有系统默认为 "small" 代码模型,其中代码和静态数据位于低 2GiB 中,因此可以保证链接器只需填充 rel32 即可达到前向 2G 或后向 2G .

x86-64 System V ABI 确实讨论了 Large / Huge 代码模型,但如果有人使用过它,IDK,因为寻址数据和进行调用的效率低下。


回复:效率:是的,mov / call rax 效率较低。我认为如果分支预测未命中并且无法从 BTB 提供目标预测,速度会明显变慢。然而,即使 call rel32jmp rel32 仍然需要 BTB 才能发挥全部性能。有关相对 jmp next_insn 的实验结果,请参阅 当巨型循环中有太多时速度变慢。

使用热分支预测器,间接版本只是额外的代码大小和一个额外的 uop(mov)。它可能会消耗更多的预测资源,但也许不会。

另见