我正在尝试调用一个函数 - 在编译和链接时应该有一个绝对地址 - 来自机器代码。我正在创建一个指向所需函数的函数指针并尝试将其传递给调用指令,但我注意到调用指令最多需要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 进程地址空间。此标志仅受支持 在x86-64上,对于64位程序。它被添加到 允许在某处分配线程堆栈 第一个2GB的内存,以改善上下文 - 在一些早期的64位处理器上切换性能。 现代x86-64处理器不再具备此功能 形成问题,所以使用这个标志不是 这些系统需要。 MAP_32BIT标志是 设置MAP_FIXED时忽略。
答案 0 :(得分:3)
TL:DR:要按名称调用函数,只需像普通人一样使用call func
,让汇编程序+链接器处理它。既然你说你正在使用NASM,我想你实际上是用汇编程序生成机器代码。这听起来像是一个更复杂的问题,但我认为你只是想问一下这种方法是否安全。
另请参阅Call an absolute pointer in x86 machine code,了解有关call
或jmp
绝对地址的规范答案。
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
或者,如果您知道机器代码将存储在中的地址,则可以在计算从目标到目标的地址差异后,使用常规直接call rel32
编码call
指令的结尾。
如果您不想使用间接通话,则rel32
编码是您唯一的选择。确保您的机器代码进入低2GiB,以便它可以达到低4GiB中的任何地址。
如果可以保证可执行文件肯定会映射到底部的4GB内存
是的,这是Linux,Windows和OS X的默认代码模型.AMD64呼叫/跳转指令和RIP相关寻址仅使用rel32
编码,因此所有系统默认为&#34 ;小"代码模型,其中代码和静态数据都在低2GiB中,因此它保证链接器可以只填充rel32以达到2G前向或2G后向。
x86-64 System V ABI确实讨论了大型/大型代码模型,但IDK是否曾经使用过它,因为寻址数据和拨打电话效率低下。
re:效率:是的,mov
/ call rax
的效率较低。如果分支预测未命中并且无法从BTB提供目标预测,我认为它会明显变慢。但是,即使call rel32
和jmp rel32
仍需要BTB才能获得全面的效果。如果巨型循环中存在过多的相对jmp next_insn
减速,请参阅Slow jmp-instruction。
使用热分支预测器,间接版本只是额外的代码大小和额外的uop(mov
)。它可能消耗更多的预测资源,但可能甚至没有。
另见What branch misprediction does the Branch Target Buffer detect?