我正试图从机器码调用一个函数--它在编译和链接时应该有一个绝对地址。我正在创建一个指向所需函数的函数指针,并试图将其传递给调用指令,但我注意到调用指令最多使用16或32位地址。有办法调用绝对64位地址吗?
我正在为x86-64架构进行部署,并使用NASM生成机器码。
如果我能保证可执行文件肯定会Map到底部4GB内存,我可以使用32位地址,但我不确定在哪里可以找到这些信息。
编辑:我不能使用callf指令,因为这需要关闭64位模式。
二次编辑:我也不想将地址存储在寄存器中并调用寄存器,因为这对性能至关重要,而且我不能有间接函数调用的开销和性能影响。
最终编辑:通过确保我的机器码Map到第一个2GB内存,我能够使用rel 32调用指令。这是通过带有MAP_32BIT标志的mmap实现的(我使用的是linux):
MAP_32BIT(自Linux 2.4.20,2.6起)将Map放入进程地址空间的前2 GB。此标志仅在x86-64上支持,适用于64位程序。它的加入是为了允许线程堆栈被分配到前2GB内存中的某个地方,以便在一些早期的64位处理器上提高上下文切换性能。现代x86-64处理器不再有这个性能问题,因此在这些系统上不需要使用此标志。当MAP_FIXED被设置时,MAP_32BIT标志被忽略。
2条答案
按热度按时间gorkyyrv1#
相关:Handling calls to (potentially) far away ahead-of-time compiled functions from JITed code有更多关于JIT的内容,特别是在它想要调用的代码附近分配JIT缓冲区,因此您可以使用高效的
call rel32
。或者如果没有该怎么办。Call an absolute pointer in x86 machine code也是一个很好的关于
call
或jmp
到绝对地址的规范Q&A。TL:DR:要通过名称调用函数,只需像普通人一样使用
call func
,并让汇编器+链接器来处理它。既然你说你在使用NASM,我猜你实际上是在用汇编程序生成机器码。这听起来像是一个更复杂的问题,但我认为你只是想问正常的方式是否安全。Indirect
call r/m64
(FF /2
)在64位模式下采用64位寄存器或内存操作数。所以你可以
字符串
通常你会把一个标签地址放入一个带有
lea rax, [rel func]
的寄存器中,但是如果它是可编码的,那么你就可以使用call rel32
。或者,如果你知道你的机器码将被存储在哪个地址,你可以使用正常的直接
call rel32
编码,在你计算从目标到call
指令结束的地址差异之后。如果不想使用间接调用,那么
rel32
编码是唯一的选择。确保机器码进入低2GiB,以便它可以到达低4GiB中的任何地址。如果我能保证可执行文件肯定会Map到底部4GB的内存
是的,这是Linux、Windows和OS X的默认代码模型。AMD 64调用/跳转指令和RIP相对寻址仅使用
rel32
编码,因此所有系统都默认为“小”代码模型,其中代码和静态数据都在低2GiB中,因此可以保证链接器只需填写rel 32即可达到2G正向或2G反向。x86-64 System V ABI确实讨论了大型/巨型代码模型,但如果有人使用过的话,那就是IDK,因为寻址数据和进行调用的效率低下。
re:效率:是的,
mov
/call rax
效率较低。我认为,如果分支预测失败,并且无法从BTB提供目标预测,则会明显变慢。然而,即使call rel32
和jmp rel32
仍然需要BTB来获得完整的性能。参见Slow jmp-instruction,当一个巨大的循环中有太多的时候,相对jmp next_insn
会变慢。使用热分支预测器,间接版本只是额外的代码大小和额外的uop(
mov
)。它可能会消耗更多的预测资源,但甚至可能不会。参见What branch misprediction does the Branch Target Buffer detect?
hc8w905p2#
在新的APXextension中,英特尔添加了一条新的
JMPABS
指令,该指令接收64位立即数作为绝对跳转目标不幸的是,没有
CALLABS
,因此您需要像这样解决它。字符串
我不知道它是否比传统的
mov reg, target64; call reg
序列更快。然而,APX还增加了16个寄存器和3个操作数的整数指令(即非破坏性目的地),因此寄存器和I/O压力可能不再存在,您可以只为绝对地址留出一个寄存器并直接使用call reg