如何执行具有 64 位绝对地址的调用指令?

Ale*_*sky 5 assembly jit x86-64 nasm function-call

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

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

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

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

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

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

MAP_32BIT (自 Linux 2.4.20, 2.6) 将映射放入进程地址空间的前 2 GB。对于 64 位程序,此标志仅在 x86-64 上受支持。添加它是为了允许在前 2GB 内存中的某处分配线程堆栈,以提高某些早期 64 位处理器上的上下文切换性能。现代 x86-64 处理器不再具有此功能?形式问题,因此在这些系统上不需要使用此标志。设置 MAP_FIXED 时,将忽略 MAP_32BIT 标志。

Pet*_*des 6

相关:JIT代码处理对(可能)很远的提前编译函数的调用有更多关于 JIT 的信息,特别是在它想要调用的代码附近分配 JIT 缓冲区,这样你就可以使用高效的call rel32. 或者如果没有怎么办。

呼吁在x86机器代码的绝对指针是一个很好的典范Q&A有关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
Run Code Online (Sandbox Code Playgroud)

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


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

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


如果我可以保证可执行文件肯定会映射到内存的底部 4GB

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

X86-64 System V的ABI是否讨论大/巨大的代码模型,但IDK一旦有人使用了,因为处理数据和拨打电话的低效率的。


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

对于热分支预测器,间接版本只是额外的代码大小和额外的 uop (the mov)。它可能会消耗更多的预测资源,但甚至可能不会。

另请参阅分支目标缓冲区检测到哪些分支错误预测?