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 标志。
相关:从JIT代码处理对(可能)很远的提前编译函数的调用有更多关于 JIT 的信息,特别是在它想要调用的代码附近分配 JIT 缓冲区,这样你就可以使用高效的call rel32. 或者如果没有怎么办。
也呼吁在x86机器代码的绝对指针是一个很好的典范Q&A有关call或jmp为绝对地址。
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)。它可能会消耗更多的预测资源,但甚至可能不会。